内核启动源码 do_basic_setup 及其 devtmpfs_init()

转载地址:http://blog.csdn.net/qing_ping/article/details/17354725
Linux内核的启动的流程如下:
start_kernel->rest_init->kernel_init->do_basic_setup->driver_init

/*好了, 设备现在已经初始化完成。 但是还没有一个设备被初始化过,
 但是 CPU 的子系统已经启动并运行,
 且内存和处理器管理系统已经在工作了。
 现在我们终于可以开始做一些实际的工作了..
 */
static void __init do_basic_setup(void)
{
    cpuset_init_smp();//针对SMP系统,初始化内核control group的cpuset子系统。如果非SMP,此函数为空。
    /*创建一个单线程工作队列khelper。运行的系统中只有一个,主要作用是指定用户空间的程序路径和环境变量, 最终运行指定的user space的程序,属于关键线程,不能关闭*/
    usermodehelper_init();

    shmem_init();
    driver_init();//初始化驱动模型中的各子系统,可见的现象是在/sys中出现的目录和文件
    init_irq_proc();//在proc文件系统中创建irq目录,并在其中初始化系统中所有中断对应的目录。
    do_ctors();//调用链接到内核中的所有构造函数,也就是链接进.ctors段中的所有函数。
    usermodehelper_enable();
    do_initcalls();//调用所有编译内核的驱动模块中的初始化函数。
}

上面的函数调用了driver_init函数,作用是驱动模型子系统的初始化,对于内核驱动工程师来说比较重要,代码位置: driver/base/init.c 详解如下:

void __init driver_init(void)
{
    /* These are the core pieces */
    devtmpfs_init();//初始化devtmpfs文件系统,驱动核心设备将在这个文件系统中添加它们的设备节点。
    /*初始化驱动模型中的部分子系统和kobject:
    devices
    dev
    dev/block
    dev/char
    */
    devices_init();
    buses_init();//初始化驱动模型中的bus子系统
    classes_init();//1.初始化驱动模型中的class子系统
    firmware_init();//1.初始化驱动模型中的firmware子系统
    hypervisor_init();//1.初始化驱动模型中的hypervisor子系统

    /* These are also core pieces, but must come after the
     * core core pieces.     
     这些也是核心部件, 但是必须在以上核心中的核心部件之后调用。
     */
    platform_bus_init();//1.初始化驱动模型中的bus/platform子系统
    cpu_dev_init();//1.初始化驱动模型中的devices/system/cpu子系统
    memory_dev_init();//初始化驱动模型中的devices/system/memory子系统
}

而另外一个很主要的函数do_initcalls()调用所有编译内核的驱动模块中的初始化函数。其中按照各个内核模块初始化函数所自定义的启动级别(1~7),按顺序调用器初始化函数。对于同一级别的初始化函数,安装编译是链接的顺序调用,也就是和内核Makefile的编写有关。

driver/base/devtmpfs.c

int __init devtmpfs_init(void)
{
	int err = register_filesystem(&dev_fs_type);//注册dev_fs_type文件系统,即将dev_fs_type添加到内核全局总链表中file_systems
	if (err) {
		printk(KERN_ERR "devtmpfs: unable to register devtmpfs "
		       "type %i\n", err);
		return err;
	}

	thread = kthread_run(devtmpfsd, &err, "kdevtmpfs");//创建并启动一个内核线程devtmpfsd
	if (!IS_ERR(thread)) {
		wait_for_completion(&setup_done);//进行一个不可打断的等待,允许一个线程告诉另一个线程工作已经完成
	} else {
		err = PTR_ERR(thread);
		thread = NULL;
	}

	if (err) {
		printk(KERN_ERR "devtmpfs: unable to create devtmpfs %i\n", err);
		unregister_filesystem(&dev_fs_type);
		return err;
	}

	printk(KERN_INFO "devtmpfs: initialized\n");
	return 0;
}

//内核线程devtmpfsd

static int devtmpfsd(void *p)
{
	char options[] = "mode=0755";
	int *err = p;
	*err = sys_unshare(CLONE_NEWNS);
	if (*err)
		goto out;
	 //挂载devtmpfs文件系统
	 //devtmpfs是待安装设备的路径名,“/”是安装点路径名,”devtmpfs“表示文件系统类型,MS_SILENT=32768,即0x8000
	*err = sys_mount("devtmpfs", "/", "devtmpfs", MS_SILENT, options); //第一次mount
	if (*err)
		goto out;
	sys_chdir("/.."); /* will traverse into overmounted root */ //将进程的当前工作目录(pwd)设定为devtmpfs文件系统的根目录
	sys_chroot(".");
	complete(&setup_done);//允许一个线程告诉另一个线程工作已经完成
	while (1) {
		spin_lock(&req_lock);
		while (requests) {//扫描请求链表,每当要创建一个设备节点时,都需要向requests链表中添加请求
			struct req *req = requests;
			requests = NULL;
			spin_unlock(&req_lock);
			while (req) {
				struct req *next = req->next;
				req->err = handle(req->name, req->mode,//对链表中的每一个请求调用handle函数
						  req->uid, req->gid, req->dev);
				complete(&req->done);
				req = next;
			}
			spin_lock(&req_lock);
		}
		__set_current_state(TASK_INTERRUPTIBLE);//睡眠该进程:等待create或delete方法来激活进程
		spin_unlock(&req_lock);
		schedule();//系统切换
	}
	return 0;
out:
	complete(&setup_done);
	return *err;
}

该进程在fs初始化时创建。主要完成了fs的第一次mount工作,然后进入while循环,在循环体内部,设置进程状态为TASK_INTERRUPTIBLE,换出进程,等待被唤醒。
kdevtmpfs进程被唤醒离不开数据结构req:

static struct req {

      struct req *next;

      struct completion done;

      int err;

      const char *name;

      umode_t mode;  /* 0 => delete */

      kuid_t uid;

      kgid_t gid;

      struct device *dev;

} *requests;

定义了struct req类型的requests变量;客户进程通过构建req,并插入request链表来请求建立设备文件的服务。
req结构体的name成员即为设备文件的路径名,然而路径名是不带/dev前缀。比如”/dev/input/eventX”文件建立时,传递给devtmpfs的路径名却是”input/eventX”。理解这点涉及到vfs和进程的概念

创建设备文件:
当有客户进程需要创建设备文件,就会唤醒devtmpfsd进程。该进程会执行handle(req->name, req->mode,req->uid, req->gid, req->dev)操作。最终调用static int handle_create()函数。

static int handle_create(const char *nodename, umode_t mode, kuid_t uid,
			 kgid_t gid, struct device *dev)
{
	struct dentry *dentry;
	struct path path;
	int err;
//查找节点名称的路径以及返回节点对应的父目录dentry结构,即在此目录下创建一个设备节点,即是/dev目录对应的dentry结构
	dentry = kern_path_create(AT_FDCWD, nodename, &path, 0);
	if (dentry == ERR_PTR(-ENOENT)) {
		create_path(nodename); //负责构建目录;
		dentry = kern_path_create(AT_FDCWD, nodename, &path, 0);
	}
	if (IS_ERR(dentry))
		return PTR_ERR(dentry);
//创建设备节点
	err = vfs_mknod(d_inode(path.dentry), dentry, mode, dev->devt);//负责构建目标设备文件
	if (!err) {
		struct iattr newattrs;

		newattrs.ia_mode = mode;
		newattrs.ia_uid = uid;
		newattrs.ia_gid = gid;
		newattrs.ia_valid = ATTR_MODE|ATTR_UID|ATTR_GID;
		inode_lock(d_inode(dentry));
		notify_change(dentry, &newattrs, NULL);
		inode_unlock(d_inode(dentry));

		/* mark as kernel-created inode */
		d_inode(dentry)->i_private = &thread;
	}
	done_path_create(&path, dentry);//与前边kern_path_create对应,减少path和dentry的计数等
	return err;
}
int vfs_mknod(struct inode*dir, struct dentry*dentry, umode_t mode, dev_t dev)
{
    int error= may_create(dir, dentry);//检查是否可以创建设备文件节点
    
    if (error)
        return error;
    
    //必须是字符设备或者块设备,且具有创建节点的权限
    if ((S_ISCHR(mode)|| S_ISBLK(mode))&& !capable(CAP_MKNOD))
        return -EPERM;
    
    if (!dir->i_op->mknod)
        return -EPERM;
    
    error = devcgroup_inode_mknod(mode, dev);
    if (error)
        return error;
    
    error = security_inode_mknod(dir, dentry, mode, dev);
    if (error)
        return error;
    
    //调用具体文件系统的mknod()函数
    //mount时调用shmem_fill_super()-->shmem_get_inode()分配inode节点时做出的初始化
    /*那么在shmem_get_inode中
        caseS_IFDIR:
        inc_nlink(inode);
        inode->i_size= 2* BOGO_DIRENT_SIZE;
        inode->i_op=&shmem_dir_inode_operations;
        inode->i_fop=&simple_dir_operations;
        由于mountpoint是dev这个目录,所以dev对应的inode的i_op就是shmem_dir_inode_operations。
        staticconst struct inode_operations shmem_dir_inode_operations = {
            #ifdefCONFIG_TMPFS
            .create =shmem_create,
            .lookup =simple_lookup,
            .link=shmem_link,
            .unlink =shmem_unlink,
            .symlink =shmem_symlink,
            .mkdir =shmem_mkdir,
            .rmdir =shmem_rmdir,
            .mknod =shmem_mknod,
            .rename =shmem_rename,
            #endif
            #ifdefCONFIG_TMPFS_POSIX_ACL
            .setattr =shmem_notify_change,
            .setxattr =generic_setxattr,
            .getxattr =generic_getxattr,
            .listxattr =generic_listxattr,
            .removexattr =generic_removexattr,
            .check_acl =generic_check_acl,
            #endif
            };
        */
    error = dir->i_op->mknod(dir, dentry, mode, dev);//所以这里调用的就是shmem_mknod
    if (!error)
        fsnotify_create(dir, dentry);
    return error;
}
shmem_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
{
    struct inode *inode;
    int error= -ENOSPC;
    
    inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE);//获得一个要创建的设备节点的inode,并初始化
    if (inode){
        error = security_inode_init_security(inode, dir,&dentry->d_name,shmem_initxattrs,NULL);
        if (error){
            if (error !=-EOPNOTSUPP){
                iput(inode);
                return error;
            }
        }
#ifdef CONFIG_TMPFS_POSIX_ACL
        error = generic_acl_init(inode, dir);
        if (error){
            iput(inode);
            return error;
        }
#else
        error = 0;
#endif
        dir->i_size+= BOGO_DIRENT_SIZE;
        dir->i_ctime= dir->i_mtime= CURRENT_TIME;
        d_instantiate(dentry, inode);//与dentry建立关,此时就可以在/dev下看到这个字符设备节点了
        dget(dentry);//递减dentry的计数
    }
    return error;
}
shmem_mknod(struct inode *dir, struct dentry *dentry, umode_t mode, dev_t dev)
{
    struct inode *inode;
    int error= -ENOSPC;
    
    inode = shmem_get_inode(dir->i_sb, dir, mode, dev, VM_NORESERVE);//获得一个要创建的设备节点的inode,并初始化
    if (inode){
        error = security_inode_init_security(inode, dir,&dentry->d_name,shmem_initxattrs,NULL);
        if (error){
            if (error !=-EOPNOTSUPP){
                iput(inode);
                return error;
            }
        }
#ifdef CONFIG_TMPFS_POSIX_ACL
        error = generic_acl_init(inode, dir);
        if (error){
            iput(inode);
            return error;
        }
#else
        error = 0;
#endif
        dir->i_size+= BOGO_DIRENT_SIZE;
        dir->i_ctime= dir->i_mtime= CURRENT_TIME;
        d_instantiate(dentry, inode);//与dentry建立关,此时就可以在/dev下看到这个字符设备节点了
        dget(dentry);//递减dentry的计数
    }
    return error;
}

三、文件系统的mount
内核主要是通过kernel_init调用prepare_namespace()函数执行安装实际根文件系统的操作

void __init prepare_namespace(void)
{
    int is_floppy;
  
    if (root_delay){
        printk(KERN_INFO "Waiting %dsec before mounting root device...\n",
               root_delay);
        ssleep(root_delay);
    }
    wait_for_device_probe();
  
    md_run_setup();
    /* 把root_device_name变量置为从启动参数“root”中获取的设备文件名。
  * 同样,把ROOT_DEV变量置为同一设备文件的主设备号和次设备号。*/
    if (saved_root_name[0]){
        root_device_name = saved_root_name;
        if (!strncmp(root_device_name,"mtd", 3)||
            !strncmp(root_device_name,"ubi", 3)){
            mount_block_root(root_device_name, root_mountflags);
            goto out;
        }
        ROOT_DEV = name_to_dev_t(root_device_name);//转换为设备号/dev/mtdblock2.
        if (strncmp(root_device_name,"/dev/", 5)== 0)
            root_device_name += 5;
    }
  
    if (initrd_load())
        goto out;
  
    /* waitfor any asynchronous scanning to complete */
    if ((ROOT_DEV== 0)&& root_wait){
        printk(KERN_INFO "Waiting for root device %s...\n",
            saved_root_name);
        while (driver_probe_done()!= 0 ||
            (ROOT_DEV = name_to_dev_t(saved_root_name))== 0)
            msleep(100);
        async_synchronize_full();
    }
  
    is_floppy = MAJOR(ROOT_DEV)== FLOPPY_MAJOR;
  
    if (is_floppy&& rd_doload&& rd_load_disk(0))
        ROOT_DEV = Root_RAM0;
  
    mount_root();
out:
    devtmpfs_mount("dev");//挂载devtmpfs文件系统
    sys_mount(".","/", NULL, MS_MOVE,NULL);/* 移动rootfs文件系统根目录上的已安装文件系统的安装点。*/
    sys_chroot(".");
} 
int devtmpfs_mount(const char*mntdir)
{
    int err;
    
    if (!mount_dev)
        return 0;
    
    if (!thread)
        return 0;
    //将devtmpfs文件系统挂载到/dev目录下
    err = sys_mount("devtmpfs",(char *)mntdir,"devtmpfs", MS_SILENT,NULL);
    if (err)
        printk(KERN_INFO "devtmpfs: error mounting %i\n", err);
    else
        printk(KERN_INFO "devtmpfs: mounted\n");
    return err;
}

devtmpfs创建节点
系统在启动过程中,扫描到的设备会通过devtmpfs_create_node()函数来添加设备节点

int devtmpfs_create_node(struct device*dev)
{
    const char *tmp = NULL;
    struct req req;
    
    if (!thread)
    return 0;
    
    req.mode = 0;
    req.name = device_get_devnode(dev,&req.mode,&tmp);//获得设备名
    if (!req.name)
        return -ENOMEM;
    
    if (req.mode== 0)
        req.mode = 0600;
    if (is_blockdev(dev))
        req.mode |= S_IFBLK;//块设备
    else
        req.mode |= S_IFCHR;//字符设备
    
    req.dev = dev;
    
    init_completion(&req.done);
    
    spin_lock(&req_lock);
    req.next= requests;//请求添加到requests链表
    requests = &req;
    spin_unlock(&req_lock);
    
    wake_up_process(thread);//唤醒内核线程devtmpfsd添加设备节点
    wait_for_completion(&req.done);
    
    kfree(tmp);
    
    return req.err;
}
const char *device_get_devnode(struct device*dev,umode_t*mode, const char **tmp)
{
    char *s;
    
    *tmp =NULL;
    
    /* the device type may provide a specific name*/
    if (dev->type&& dev->type->devnode)
        *tmp = dev->type->devnode(dev, mode);
    if (*tmp)
        return *tmp;
    
    /* theclass may provide a specific name */
    if (dev->class&& dev->class->devnode)
        *tmp = dev->class->devnode(dev, mode);
    if (*tmp)
        return *tmp;
    
    /* return name without allocation, tmp== NULL */
    if (strchr(dev_name(dev),'!')== NULL)
        return dev_name(dev);
    
    /* replace '!'in the name with '/'*/
    *tmp = kstrdup(dev_name(dev), GFP_KERNEL);
    if (!*tmp)
        return NULL;
    while ((s= strchr(*tmp,'!')))
        s[0]= '/';
    return *tmp;
} 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值