共计 2382 个字符,预计需要花费 6 分钟才能阅读完成。
本篇文章为大家展示了如何分析 Linux 内核源码 do_fork,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。
我们都知道进程是 Linux 内核中最为重要的一个抽象概念,那么我们平时在 fork 一个进程时,该进程究竟是怎么产生的呢?
推送会浅谈一下在进程创建过程中扮演着重要角色的 do_fork 函数。
内核如何来抽象一个进程
内核通过一个叫做 task_struct 的结构体来抽象一个进程,该结构体的定义 (以内核 2.6 为例) 在 include/linux.sched.h 中。
截取部分 task_struct 如下:
上述 task_struct 属性是我节选出的部分其结构体中的属性,我们从中可以大致了解到标识一个进程的属性大致会有该用以表示该进程所处的状态, 进程的标志,以及进程是否被其他进程跟踪,进程锁的深度,进程的优先级,进程的 pid,进程的父母,进程的孩子链表,进程所打开的文件描述符表,进程所处的文件系统,进程的信号。。。。等等一堆我们平时可能遇到的和进程相关的东西。
do_fork 简单分析
接触 linuxC 编程的人都知道,创建一个进程我们需要调用 fork 函数,fork 其实又是调用了 clone 函数来实现的,而 clone 函数中最关键的函数就是 do_fork 函数。
在分析 do_fork 前我们脑海中可以大致想象一下,进程究竟是如何被创建出来的,假如让你来创建一个进程你会咋么做?
我们可以这样去分析,既然原来的进程被抽象成一个 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 指针:
struct task_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)子进程着手使自己与父进程区别开来,从父进程那继承过来的许多属性都要被清 0 或设置一个初始值,但 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 参数就可以知道怎么拷贝了。
上述内容就是如何分析 Linux 内核源码 do_fork,你们学到知识或技能了吗?如果还想学到更多技能或者丰富自己的知识储备,欢迎关注丸趣 TV 行业资讯频道。