1 为什么要initramfs?
知道为什么后,往往事情干起来就…
2 制作简易initramfs
制作简易版本,我们以hello为基础,熟练制作过程。简易版本目录结构和制作过程介绍的比较详细,后面有的过程就不具体介说明了。
2.1 目录结构
为了归纳文件,我创建了一个initramfs-hello目录,在initramfs-hello目录下又创建了一个inird,和在initrd目录下又创建bin和lib64目录。
mkdir initramfs-hello
mkdir initramfs-hello/inird
mkdir initramfs-hello/inird/bin
mkdir initramfs-hello/inird/lib64
2.2 创建init文件和添加内容
在initramfs-hello/inird目录下创建init文件并编辑。
touch initramfs-hello/inird/init
vi initramfs-hello/inird/init
在init文件里面添加内容如下。export这栏也可以不写。
#!/bin/bash
echo "Hello Linux"
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
exec /bin/bash
添加执行权限
chmod +x initramfs-hello/inird/init
2.3 移植bash和库文件
移植bash
cp -d /bin/bash initramfs-hello/inird/bin
使用ldd命令查看bash依赖库
ldd /bin/bash
移植bash依赖库文件
cp -d /lib64/libtinfo.so.5 initramfs-hello/inird/lib64
cp -d /lib64/libtinfo.so.5.9 initramfs-hello/inird/lib64
cp -d /lib64/libdl.so.2 initramfs-hello/inird/lib64
cp -d /lib64/libdl-2.17.so initramfs-hello/inird/lib64
cp -d /lib64/libc.so.6 initramfs-hello/inird/lib64
cp -d /lib64/libc-2.17.so initramfs-hello/inird/lib64
cp -d /lib64/ld-linux-x86-64.so.2 initramfs-hello/inird/lib64
cp -d /lib64/ld-2.17.so initramfs-hello/inird/lib64
2.4 制作打包文件
在initramfs-hello目录下写了个cpio打包命令的文件generate-initramfs。此文件为测试方便,免得每次都要输入很长的命令。文件内容如下:
cd inird
find . | cpio -o -H newc | gzip > ../initramfs-hello.img
生成的initramfs-hello.img文件就是我们需要使用的initramfs文件。
2.5 说明
(1) 把initramfs-hello.img拷贝到启动目录,准备使用。
(2) 这里主要讲生成initrams镜像文件,其它配置不详细写出来,只写文字备注,以及注意事项。
(3) 需要在配置内核,并且要支持initramfs。
(4) 需要在grab.cfg里面添加配置initrd。
(5) 需要移植bash命令以及相关库到根目录盘。
(5) 制定脚本的解释器的字符串“#!/bin/bash”,一定要从第一个行的左侧第一个字符开始。因为内核中的脚本加载器将根据脚本文件的前面两个字符判断使用什么解释器。
3 制作完整版initramfs文件
在移植initramfs文件的根目录下创建init文件,init中没有模块相关的内容,所以前面需要的内容全面都打包到bzImage里面了,此是一个完整版了,我们不能再命名initramfs-hello了,而使用initramfs-rootfs。
3.1 编写init文件
init文件添加如下内容:
#!/bin/bash
echo "Hello Linux"
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
export ROOTMNT=/root
export ROFLAG=-r
mount -n -t devtmpfs udev /dev
mount -n -t proc proc /proc
mount -n -t sysfs sysfs /sys
for x in $(cat /proc/cmdline);do
case $x in
root=*)
ROOT=${x#root=}
;;
ro)
ROFLAG=-r
;;
rw)
ROFLAG=-w
;;
esac
done
mount $(ROFLAG) ${ROOT} ${ROOTMNT}
#exec /bin/bash
mount -n --move /dev ${ROOTMNT}/dev
mount -n --move /proc ${ROOTMNT}/proc
mount -n --move /sys ${ROOTMNT}/sys
exec pro_switch_root ${ROOTMNT} /sbin/init
当前需要启动硬盘上面的根文件系统,所以写了pro_switch_root取代/bin/bash,busybox里面也有根文件切换的命令switch_root。此时我们没有使用busybox里面的命令,而时参考了深度探索Linux操作系统,自己写的切换文件,为了区别,我们在前面加了pro_。当执行pro_switch_root时,前面需要加上exec,保证PID一直为1,我们参考的书中,王柏生大神没有添加此项,可能是漏写了。
3.2 编写pro_switch_root.c文件
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/mount.h>
int switch_root_delete_dir(char* dir);
void switch_root_delete(char* directory)
{
// 返回值:成功返回0;失败返回-1,其错误存在全局变量errno中.
if(unlink(directory)){
// 一个目录
if(errno == EISDIR){
printf("unlink: EISDIR!\n");
if(!switch_root_delete_dir(directory)){
// 使用rmdir函数时,目录必须为空,否则调用失败,函数返回-1.成功时,函数返回0.
rmdir(directory);
}
}
}else
{
printf("unlink: delete file success.\n");
}
}
/*
* 删除目录
*
*/
int switch_root_delete_dir(char* directory)
{
DIR *dir;
struct dirent *d;
struct stat st1, st2;
char path[PATH_MAX];
if(lstat(directory, &st1)){
return errno;
}
if(!(dir = opendir(directory))){
return errno;
}
while((d=readdir(dir))){
// 跳过目录下的.和..文件
if(d->d_name[0] == '.'
&& (d->d_name[1] == '\0' || (d->d_name[1] == '.' && d->d_name[2] == '\0'))){
printf("skip %s/%s\n", directory, d->d_name);
continue;
}
printf("file name: %s/%s\n", directory, d->d_name);
sprintf(path, "%s/%s", directory, d->d_name);
lstat(path, &st2);
// t2.st_dev文件所在设备的ID
// 这两个设备ID有可能相等 ???
if(st2.st_dev != st1.st_dev){
continue;
}
switch_root_delete(path);
}
closedir(dir);
return 0;
}
int main(int argc, char* argv[])
{
pid_t pid = getpid();
// 此时检查进程的pid是否是1
printf("[CHD] PID:%d\n", pid);
int console_fd;
chdir(argv[1]);
switch_root_delete_dir("/");
mount(".", "/", NULL, MS_MOVE, NULL);
chroot(".");
chdir("/");
console_fd = open("/dev/console", O_RDWR);
dup2(console_fd, 0);
dup2(console_fd, 1);
dup2(console_fd, 2);
close(console_fd);
// execlp函数会从PATH环境变量所指得目录中查找符合参数file的文件名,找到后便执行该文件
// 然后将第二个以后的参数当作该文件的argv[0]、argv[1]......,最后一个参数必须用空指针(NULL)结束。
execlp(argv[2], argv[2], NULL);
return 0;
}
3.3. 编写makefile文件,编译pro_switch_root.c文件
pro_switch_root: pro_switch_root.c:
clean:
rm -rf *.o
rm -rf pro_switch_root
3.4 移植相关文件
移植pro_switch_root到initramfs根目录的bin文件夹中。
在initramfs根目录创建bin, lib64, proc, sys, dev目录。
移植 bash, ls, mount, umount到initramfs根目录的bin目录。
移植相关库文件到initramfs根目录的lib64目录。
参考
1 深度探索Linux操作系统
2 还有其它网络博客,记不清了,抱歉。