当前位置: 首页 > 科技观察

Linux内核源码do_fork分析_0

时间:2023-03-15 16:48:35 科技观察

我们都知道进程是Linux内核中最重要的抽象概念,那么我们平时fork一个进程的时候,这个进程是怎么产生的呢?这个帖子会简单说一下这个过程do_fork函数在创建过程中扮演着重要的角色。内核是如何抽象进程的内核是通过一个名为task_struct的结构来抽象进程的,该结构定义在include/linux.sched.h中(以kernel2.6为例)。截取的task_struct部分如下:上面的task_struct属性是我在它的结构中选择的部分属性。由此我们可以大致了解到,标识一个进程的属性会大致表示进程的状态,进程的标志,进程是否被其他进程跟踪,进程锁的深度,进程的优先级进程,进程的pid,进程的父进程,进程的子链表,进程打开的文件描述符表,进程所在的文件系统,进程的Signal。...等等一堆我们平时可能遇到的进程相关的东西。do_fork的简单分析接触过linuxC编程的人都知道,要创建一个进程,我们需要调用fork函数,实际上是通过调用clone函数来实现的,而clone函数中最关键的函数就是do_fork函数.在分析do_fork之前,我们可以大致想象一下进程是如何创建的。如果要求您创建流程,您会怎么做?我们可以这样分析。既然原来的进程被抽象成了一个task_struct,那么新的进程也是一个task_struct,只是里面的一些属性会和原来的task_struct不同,那么创建新进程的工作就是分配一个相同的task_struct结构作为原始进程,然后将新进程的task_struct更改为与原始task_struct不同。可以修改原始task_struct的属性。do_fork在kernel/fork.c文件中定义。在分析函数之前,我们先分析一下其函数的参数。参数如下:1.clone_flags:该参数是该函数中最重要的参数,该值中的每一位代表子进程task_struct中各个属性的设置;2.stack_start:子进程起始地址的用户态栈;3.regs:当系统发生系统调用时,需要从用户态切换到内核态。这个结构体用于此时在用户态进程中保存通用寄存器中的值,并存储在内核态栈中;4.stack_size:当前未使用,通常设置为0;5.parent_tidptr:父进程在用户态的pid地址;6.child_tidptr:子进程在用户态的pid地址;其中,clone_flags的标志位宏定义如下:举个简单的例子,当我们在参数中设置宏CLONE_VM时,我们认为我们新创建的进程和它的父进程会共享VM。当我们设置CLONE_FILES时,意味着父子进程共享打开的文件描述符。do_fork开始执行后,首先要做的就是为子进程定义一个新的task_struct指针:structtask_struct*p;接下来,它会检查一些clone_flags不允许的位组合,例如:if(clone_flags&CLONE_NEWUSER){ if(clone_flags&CLONE_THREAD) return-EINVAL;}不允许设置CLONE_NEWUSER标志和CLONE_THREAD在上面同时标记,会报错。和上面类似,当一系列安全检查完成后,就会出现copy_process函数。copy_process函数的工作流程如下:1)调用dup_task_struct函数为新进程创建内核栈、thread_info结构体和task_struct。当然此时的值都是和父进程一模一样的dup_task_struct函数,定义如下:2)检查确保当前用户拥有的进程数不超过后子进程新建后分配给它的资源限制,代码如下:3)子进程开始使自己与父进程不同,从父进程继承的很多属性必须清零或设置为一个初始值,但task_struct中的大部分数据没有被修改,部分代码如下:4)分配一个子进程CPU,代码如下:sched_fork(p,clone_flags);5)然后子进程复制父进程的一些资源,如下,调用copy_files函数复制父进程打开的文件描述符:调用copy_fs继承父进程所属的文件系统。调用copy_signal函数复制并设置一个新的signal_struct,signal_struct包含了很多进程运行的信息,调用copy_mm函数处理新进程的内存问题。调用copy_io函数复制父进程的I/O情况:还有调用copy_namespaces和copy_thread等,这里不再赘述。6)调用alloc_pid为新进程分配一个pid。pid=alloc_pid(p->nsproxy->pid_ns);7)copy_process做一些收尾工作,返回新进程的task_struct指针。此时再次返回do_fork,将新创建的子进程先唤醒并投入运行。.总结一下对进程创建源码的理解,我觉得抓住两点就够了。***内核对进程的抽象是什么?它的数据结构(task_struct)是什么?我们必须明白这一点。创建进程最重要的就是复制父进程的task_struct中的属性,但关键是复制哪些,子进程和父进程之间有哪些不同。这很简单。我们只需要掌握进程创建函数中的clone_flags参数就知道如何复制了。