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

转载地址:http://blog.youkuaiyun.com/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;
} 
<think>好的,我现在需要回答用户的问题:“内核启动初期console驱动准备好了吗?” 用户之前已经询问过U-Boot如何传递终端参数给内核,现在可能是在深入理解内核启动过程中控制台的初始化时机。 首先,我需要确认用户的问题核心:在内核启动的早期阶段,console驱动是否已经加载并可用。根据之前的回答,用户已经知道参数是如何传递的,现在想知道内核何时初始化这些驱动。 接下来,我应该回忆Linux内核启动过程中控制台初始化的阶段。内核启动分为多个阶段,比如早期初始化、子系统初始化、设备驱动加载等。控制台作为关键组件,需要尽早初始化以便输出调试信息。 需要明确的是,console的初始化是分阶段的。例如,早期控制台(earlycon)在内核启动的最初阶段就设置,而完整的console驱动可能在稍后阶段加载。这里需要区分earlycon和普通的console驱动。 另外,用户可能关心的是,如果内核启动初期遇到问题,比如驱动未加载,是否能有控制台输出。这时候earlyprintk或earlycon的参数可能会被用到,这些属于早期调试手段。 需要查阅内核启动流程的文档或源码,确认各个阶段。比如,start_kernel函数中的console_init()调用,可能是在某个早期阶段执行。但在此之前,可能需要earlycon的支持。 还要注意设备树(DTB)或ATAGS中传递的参数如何影响控制台的初始化顺序。比如,是否需要在bootargs中指定earlycon参数来启用早期控制台。 此外,不同架构可能有不同的处理方式,比如ARM和x86在控制台初始化上的差异。不过用户之前的例子是ARM,可能需要侧重于此。 可能会遇到的问题是,用户可能混淆了不同阶段的控制台初始化,或者不清楚为什么某些早期信息能够输出。需要解释清楚各个阶段的支持情况。 最后,要确保回答结构清晰,分点说明各个阶段,并举例说明配置方法,以及可能的问题和解决方法。</think>内核启动初期的控制台(console)驱动初始化是一个分阶段逐步完成的过程,其准备情况需要结合启动阶段具体分析。以下是内核控制台初始化的关键阶段解析: --- ### **1. 极早期控制台(Early Console)** **阶段定位**:内核入口代码 → `start_kernel()` 函数之前 **驱动状态**: - 部分架构支持 **`earlycon`**(早期控制台),通过设备树或命令行参数激活 - 依赖硬件直接操作(如直接写串口寄存器),**不依赖完整驱动框架** - 典型应用场景:调试内核启动最开始的代码(如解压、页表初始化) **配置示例**: ```bash # 在U-Boot的bootargs中添加 earlycon=uart8250,mmio32,0x3f8,115200n8 ``` --- ### **2. 控制台框架初始化** **阶段定位**:`start_kernel()` → `console_init()`(位于`init/main.c`) **驱动状态**: 1. **控制台子系统注册**: - 调用`console_init()`初始化控制台框架 - 解析`bootargs`中的`console=`参数 - 遍历已注册的`console_driver`列表(如`serial8250_console`) 2. **驱动匹配与绑定**: - 根据参数匹配正确的驱动(如`ttyS0`对应8250串口驱动) - **此时驱动可能尚未完全初始化**(依赖后续设备探测) --- ### **3. 驱动设备探测阶段** **阶段定位**:`device_initcall`或`subsys_initcall`阶段 **驱动状态**: - 真实硬件驱动(如`8250串口驱动`)通过`module_init()`或设备树探测完成初始化 - **关键依赖链**: ``` 时钟控制器初始化 → 外设总线初始化 → 串口控制器探测 → 驱动与设备绑定 ``` - **此时控制台驱动才完全就绪**,可支持完整操作(中断、DMA等) --- ### **4. 阶段对比与调试手段** | 阶段 | 驱动状态 | 输出能力 | 典型调试方法 | |---------------------|--------------------------|-----------------------|---------------------------| | 极早期(earlycon) | 直接硬件操作 | 仅输出 | `earlycon=...` | | console_init() | 框架就绪,驱动未绑定 | 部分缓冲输出 | `console=ttyS0` | | 设备探测完成后 | 驱动完全可用 | 完整输入/输出 | 正常日志输出 | --- ### **常见问题与解决方案** 1. **无任何控制台输出**: - 确认`earlycon`参数正确性(地址、波特率与硬件一致) - 检查设备树中串口节点是否包含`status = "okay"` 2. **驱动初始化顺序问题**: - 使用`initcall_debug`内核参数观察驱动初始化顺序: ```bash bootargs="console=ttyS0,115200 initcall_debug" ``` 3. **早期调试增强**: - 启用`CONFIG_DEBUG_LL`(ARM架构)实现汇编级调试 - 结合`earlyprintk`参数(旧内核兼容方案): ```bash earlyprintk=serial,ttyS0,115200 ``` --- ### **代码流程示例(ARM架构)** ```c start_kernel()setup_arch(&command_line) // 解析设备树/ATAGS → setup_command_line(command_line) // 处理bootargs → console_init() // 初始化控制台框架 → rest_init() // 创建init进程 → kernel_init() // 用户空间初始化 → do_basic_setup()do_initcalls() // 调用各驱动模块的initcall → serial8250_init() // 串口驱动实际初始化 ``` --- ### **总结** - **极早期阶段**:通过`earlycon`直接操作硬件,驱动未加载 - **框架初始化阶段**(`console_init()`):控制台框架就绪,但驱动设备可能未完成探测 - **完整可用阶段**:设备驱动初始化完成后,控制台完全功能就绪 通过这种分阶段初始化机制,内核既能保证极早期的调试输出,又能正确处理驱动依赖关系,最终实现控制台的完整功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值