转载地址: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;
}