您的位置:首页 > 运维架构 > Linux

Linux内核源码分析-安装实际根文件系统- prepare_namespace

2016-10-18 16:47 896 查看
Linux内核源码分析-安装实际根文件系统- prepare_namespace
内容说明:

1、不描述内核同步、错误处理、参数合法性验证相关的内容

2、源码摘自Linux内核2.6.11.1版

3、阅读本文请结合《深入理解Linux内核》第三版相关章节

4、本文会不定时更新

本文主要参考《深入理解Linux内核》,结合2.6.11.1版的内核代码,分析内核文件子系统中的安装实际根文件系统函数。

1、prepare_namespace

函数功能:

从设备文件路径名到处设备标识符,调用mount_root安装根文件系统

函数源码:

/*

 * Prepare the namespace - decide what/where tomount, load ramdisks, etc.

 */

void __init prepare_namespace(void)

{

    intis_floppy;

 

    mount_devfs();

 

    if(root_delay) {

       printk(KERN_INFO"Waiting %dsec before mounting root device...\n",

              root_delay);

       ssleep(root_delay);

    }

 

    md_run_setup();

 

    if(saved_root_name[0]) {

       root_device_name= saved_root_name;

       ROOT_DEV= name_to_dev_t(root_device_name);

       if(strncmp(root_device_name, "/dev/", 5) == 0)

           root_device_name+= 5;

    }

 

    is_floppy= MAJOR(ROOT_DEV) == FLOPPY_MAJOR;

 

    if(initrd_load())

       gotoout;

 

    if(is_floppy && rd_doload && rd_load_disk(0))

       ROOT_DEV= Root_RAM0;

 

    mount_root();

out:

    umount_devfs("/dev");

    sys_mount(".","/", NULL, MS_MOVE, NULL);

    sys_chroot(".");

    security_sb_post_mountroot();

    mount_devfs_fs();

}

函数处理流程:

1、调用函数mount_devfs安装devfs文件系统

2、如果启动参数中设置了安装根文件系统的延迟参数rootdelay,则调用ssleep延迟安装root_delay秒

3、md_run_setup初始化MD设备,MD设备主要包含了LINUX内核的软RAID实现

4、如果保持的设备文件路径名不为空,在调用函数name_to_dev_t获得设备文件的设备标识符,存入ROOT_DEV全局变量中;如果设备路径名以“/dev/”开头,则跳过前5个字节

5、通过设备文件标识符ROOT_DEV判断启动设备文件是否是软盘,并把判断结果存入局部变量is_floppy中

6、调用initrd_load函数,具体分析参见本文

7、如果从rd启动,加载rd,并设置ROOT_DEV为Root_RAM0

8、调用函数mount_root安装实际根文件系统,参见本文分析

2、mount_root

函数功能:

 

函数源码:

/////////////////////init/Do_mounts.c////////////////////////////

void __init mount_root(void)

{

#ifdef CONFIG_ROOT_NFS

    if(MAJOR(ROOT_DEV) == UNNAMED_MAJOR) {

       if(mount_nfs_root())

           return;

 

       printk(KERN_ERR"VFS: Unable to mount root fs via NFS, trying floppy.\n");

       ROOT_DEV= Root_FD0;

    }

#endif

#ifdef CONFIG_BLK_DEV_FD

    if(MAJOR(ROOT_DEV) == FLOPPY_MAJOR) {

       /*rd_doload is 2 for a dual initrd/ramload setup */

       if(rd_doload==2) {

           if(rd_load_disk(1)) {

              ROOT_DEV= Root_RAM1;

              root_device_name= NULL;

           }

       }else

           change_floppy("rootfloppy");

    }

#endif

    create_dev("/dev/root",ROOT_DEV, root_device_name);

    mount_block_root("/dev/root",root_mountflags);

}

函数处理流程(这里暂不描述网络文件系统和软盘文件系统):

1、调用create_dev函数在/dev目录中创建root设备文件,设备标识符为ROOT_DEV,设备文件名称为root_device_name

2、调用函数mount_block_root安装根文件系统,具体分析见下文

3、name_to_dev_t

函数功能:

把设备路径名转换成设备标识符

函数参数:

name:设备路径名

函数源码:

/*

 *  Converta name into device number.  We accept thefollowing variants:

 *

 *  1)device number in hexadecimal representsitself

 *  2)/dev/nfs represents Root_NFS (0xff)

 *  3)/dev/<disk_name> represents the device number of disk

 *  4)/dev/<disk_name><decimal> represents the device number

 *        of partition - device number of disk plus the partition number

 *  5)/dev/<disk_name>p<decimal> - same as the above, that form is

 *     used when disk name of partitioned disk endson a digit.

 *

 *  Ifname doesn't have fall into the categories above, we return 0.

 *  Driverfsis used to check if something is a disk name - it has

 *  allknown disks under bus/block/devices.  Ifthe disk name

 *  containsslashes, name of driverfs node has them replaced with

 *  bangs.  try_name() does the actual checks, assumingthat driverfs

 *  ismounted on rootfs /sys.

 */

dev_t __init name_to_dev_t(char *name)

{

    chars[32];

    char*p;

    dev_tres = 0;

    intpart;

 

#ifdef CONFIG_SYSFS

    intmkdir_err = sys_mkdir("/sys", 0700);

    if(sys_mount("sysfs", "/sys", "sysfs", 0, NULL)< 0)

       gotoout;

#endif

 

    if(strncmp(name, "/dev/", 5) != 0) {

       unsignedmaj, min;

 

       if(sscanf(name, "%u:%u", &maj, &min) == 2) {

           res= MKDEV(maj, min);

           if(maj != MAJOR(res) || min != MINOR(res))

              gotofail;

       }else {

           res= new_decode_dev(simple_strtoul(name, &p, 16));

           if(*p)

              gotofail;

       }

       gotodone;

    }

    name+= 5;

    res= Root_NFS;

    if(strcmp(name, "nfs") == 0)

       gotodone;

    res= Root_RAM0;

    if(strcmp(name, "ram") == 0)

       gotodone;

 

    if(strlen(name) > 31)

       gotofail;

    strcpy(s,name);

    for(p = s; *p; p++)

       if(*p == '/')

           *p= '!';

    res= try_name(s, 0);

    if(res)

       gotodone;

 

    while(p > s && isdigit(p[-1]))

       p--;

    if(p == s || !*p || *p == '0')

       gotofail;

    part= simple_strtoul(p, NULL, 10);

    *p= '\0';

    res= try_name(s, part);

    if(res)

       gotodone;

 

    if(p < s + 2 || !isdigit(p[-2]) || p[-1] != 'p')

       gotofail;

    p[-1]= '\0';

    res= try_name(s, part);

done:

#ifdef CONFIG_SYSFS

    sys_umount("/sys",0);

out:

    if(!mkdir_err)

       sys_rmdir("/sys");

#endif

    returnres;

fail:

    res= 0;

    gotodone;

}

函数处理流程:

1、编译内核时设置了sys文件系统,则在根目录下创建sys目录,并安装sysfs文件系统

2、如果设备文件路径名不以“/dev/”为前缀,则从设备文件路径名中直接取主设备号和次设备号,组成设备文件标识符并返回

3、如果设备路径名中的设备文件名等于”nfs”,说明根文件系统是网络文件系统,返回网络文件系统的设备文件标识符

4、如果设备路径名中的设备文件名等于“ram”,说明根文件系统是ram文件系统,返回内存文件系统的设备文件标识符

5、把设备文件名中的” /”替换为”!”

6、以设备文件名为参数调用try_name获取设备的标识符,如果成功则跳到最后一步

7、try_name返回失败,则解析设备文件名最后的数字做为分区索引,以设备文件名和分区索引调用try_name函数获取分区的设备标识符(注意索引号可能是1或2个字符)

8、编译内核时设置了sys文件系统,卸载sysfs文件系统,删除根目录下的sys目录

4、try_name

函数功能:

把设备文件名转换成设备文件标识符(含主设备号和次设备号)

函数参数:

name:设备文件名

part:0代表设备,大于0代表设备分区

函数源码:

static dev_t __init try_name(char*name, int part)

{

    charpath[64];

    charbuf[32];

    intrange;

    dev_tres;

    char*s;

    intlen;

    intfd;

    unsignedint maj, min;

 

    /*read device number from .../dev */

 

    sprintf(path,"/sys/block/%s/dev", name);

    fd= sys_open(path, 0, 0);

    if(fd < 0)

       gotofail;

    len= sys_read(fd, buf, 32);

    sys_close(fd);

    if(len <= 0 || len == 32 || buf[len - 1] != '\n')

       gotofail;

    buf[len- 1] = '\0';

    if(sscanf(buf, "%u:%u", &maj, &min) == 2) {

       /*

        * Try the %u:%u format -- see print_dev_t()

        */

       res= MKDEV(maj, min);

       if(maj != MAJOR(res) || min != MINOR(res))

           gotofail;

    }else {

       /*

        * Nope. Try old-style "0321"

        */

       res= new_decode_dev(simple_strtoul(buf, &s, 16));

       if(*s)

           goto fail;

    }

 

    /*if it's there and we are not looking for a partition - that's it */

    if(!part)

       returnres;

 

    /*otherwise read range from .../range */

    sprintf(path,"/sys/block/%s/range", name);

    fd= sys_open(path, 0, 0);

    if(fd < 0)

       gotofail;

    len= sys_read(fd, buf, 32);

    sys_close(fd);

    if(len <= 0 || len == 32 || buf[len - 1] != '\n')

       gotofail;

    buf[len- 1] = '\0';

    range= simple_strtoul(buf, &s, 10);

    if(*s)

       gotofail;

 

    /*if partition is within range - we got it */

    if(part < range)

       returnres + part;

fail:

    return0;

}

函数处理流程:

1、从/sys/block/目录中的设备文件目录即name目录中,读dev文件,从文件中解析出主设备号和次设备号

2、如果part等于0,返回主设备号和次设备号组成的设备标识符

3、如果part大于0,返回分区的主设备号和次设备号组成的设备标识符(次设备号+part)

5、initrd_load

函数源码:

int __init initrd_load(void)

{

    if(mount_initrd) {

       create_dev("/dev/ram",Root_RAM0, NULL);

       /*

        * Load the initrd data into /dev/ram0. Executeit as initrd

        * unless /dev/ram0 is supposed to be ouractual root device,

        * in that case the ram disk is just set uphere, and gets

        * mounted in the normal path.

        */

       if(rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {

           sys_unlink("/initrd.image");

           handle_initrd();

           return1;

       }

    }

    sys_unlink("/initrd.image");

    return0;

}

函数处理流程:

1、如果mount_initrd=0,则跳过initrd机制,直接挂载常规的根文件系统

2、用rd_load_image函数把/initrd.image(也就是initrd的实际数据)挂载到/dev/ram0,接着判断根设备是否就是/dev/ram0,如果是则跳过initrd处理,按正常流程挂载根文件系统;否则用initrd_handle函数进行initrd处理

6、handle_initrd

函数功能:

参见“参考文章2”

函数源码:

static void __init handle_initrd(void)

{

    interror;

    inti, pid;

 

    real_root_dev= new_encode_dev(ROOT_DEV);

    create_dev("/dev/root.old",Root_RAM0, NULL);

    /*mount initrd on rootfs' /root */

    mount_block_root("/dev/root.old",root_mountflags & ~MS_RDONLY);

    sys_mkdir("/old",0700);

    root_fd= sys_open("/", 0, 0);

    old_fd= sys_open("/old", 0, 0);

    /*move initrd over / and chdir/chroot in initrd root */

    sys_chdir("/root");

    sys_mount(".","/", NULL, MS_MOVE, NULL);

    sys_chroot(".");

    mount_devfs_fs();

 

    pid= kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);

    if(pid > 0) {

       while(pid != sys_wait4(-1, &i, 0, NULL))

           yield();

    }

 

    /*move initrd to rootfs' /old */

    sys_fchdir(old_fd);

    sys_mount("/",".", NULL, MS_MOVE, NULL);

    /*switch root and cwd back to / of rootfs */

    sys_fchdir(root_fd);

    sys_chroot(".");

    sys_close(old_fd);

    sys_close(root_fd);

    umount_devfs("/old/dev");

 

    if(new_decode_dev(real_root_dev) == Root_RAM0) {

       sys_chdir("/old");

       return;

    }

 

    ROOT_DEV= new_decode_dev(real_root_dev);

    mount_root();

 

    printk(KERN_NOTICE"Trying to move old root to /initrd ... ");

    error= sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);

    if(!error)

       printk("okay\n");

    else{

       intfd = sys_open("/dev/root.old", O_RDWR, 0);

       printk("failed\n");

       printk(KERN_NOTICE"Unmounting old root\n");

       sys_umount("/old",MNT_DETACH);

       printk(KERN_NOTICE"Trying to free ramdisk memory ... ");

       if(fd < 0) {

           error= fd;

       }else {

           error= sys_ioctl(fd, BLKFLSBUF, 0);

           sys_close(fd);

       }

       printk(!error? "okay\n" : "failed\n");

    }

}

7、mount_block_root

函数源码:

void __init mount_block_root(char*name, int flags)

{

    char*fs_names = __getname();

    char*p;

    charb[BDEVNAME_SIZE];

 

    get_fs_names(fs_names);

retry:

    for(p = fs_names; *p; p += strlen(p)+1) {

       interr = do_mount_root(name, p, flags, root_mount_data);

       switch(err) {

           case0:

              gotoout;

           case-EACCES:

              flags|= MS_RDONLY;

              gotoretry;

           case-EINVAL:

              continue;

       }

            /*

        * Allow the user to distinguish between failedsys_open

        * and bad superblock on root device.

        */

       __bdevname(ROOT_DEV,b);

       printk("VFS:Cannot open root device \"%s\" or %s\n",

              root_device_name,b);

       printk("Pleaseappend a correct \"root=\" boot option\n");

 

       panic("VFS:Unable to mount root fs on %s", b);

    }

    panic("VFS:Unable to mount root fs on %s", __bdevname(ROOT_DEV, b));

out:

    putname(fs_names);

}

函数处理流程:

1、调用函数__getname从slab高速缓存names_cachep中分配一页内存空间,地址存入局部变量fs_names中

2、调用函数get_fs_names解析可用的根文件系统类型名称组成的字符串数组

3、对每个根文件系统类型名称,循环调用函数do_mount_root尝试安装根文件系统,如果所有文件类型名都失败则出错终止

4、根文件系统安装成功中调用函数putname释放一页空间的slab高速缓存对象到slab高速缓存names_cachep中

8、get_fs_names

函数功能:

解析可用的根文件系统类型名称组成的字符串数组

函数源码:

static void __init get_fs_names(char*page)

{

    char*s = page;

 

    if(root_fs_names) {

       strcpy(page,root_fs_names);

       while(*s++) {

           if(s[-1] == ',')

              s[-1]= '\0';

       }

    }else {

       intlen = get_filesystem_list(page);

       char*p, *next;

 

       page[len]= '\0';

       for(p = page-1; p; p = next) {

           next= strchr(++p, '\n');

           if(*p++ != '\t')

              continue;

           while((*s++ = *p++) != '\n')

              ;

           s[-1]= '\0';

       }

    }

    *s= '\0';

}

函数处理流程:

1、如果有根文件系统类型参数值,把根文件系统类型中的”,”转换成”\0”,返回新生产的字符串

2、如果没有根文件系统类型参数值,调用函数get_filesystem_list从文件系统类型链表file_systems中搜索所有文件系统类型,组成如下格式的字符串:

文件类型1(空或nodev \t文件系统类型名称\n)文件类型2(空或nodev \t文件系统类型名称\n)…

3、对搜索生成的字符串,滤掉开头为nodev的;开头是\t的,滤掉\t,把\n转换成\0,返回新生成的字符串

9、do_mount_root

函数功能:

安装根文件系统,失败返回错误码;成功设置当前工作目录和ROOT_DEV全局变量

函数源码:

static int __init do_mount_root(char*name, char *fs, int flags, void *data)

{

    interr = sys_mount(name, "/root", fs, flags, data);

    if(err)

       returnerr;

 

    sys_chdir("/root");

    ROOT_DEV= current->fs->pwd.mnt->mnt_sb->s_dev;

    printk("VFS:Mounted root (%s filesystem)%s on device %u:%u.\n",

          current->fs->pwd.mnt->mnt_sb->s_type->name,

          current->fs->pwd.mnt->mnt_sb->s_flags & MS_RDONLY ?

           " readonly" : "",MAJOR(ROOT_DEV), MINOR(ROOT_DEV));

    return0;

}

函数处理流程:

1、调用sys_mount函数安装文件系统,出错则返回

2、改变当前工作目录为”/root”

3、把ROOT_DEV设置为当前进程的当前工作目录所在文件系统的超级块对应的设备标识符

参考文章:

1、Initrd源码分析 

http://blog.chinaunix.net/uid-20355083-id-1963787.html

2、linux2.6内核initrd机制解析

http://blog.csdn.net/lizhiguo0532/article/details/5946690

 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息