Linux内核分析6:分析Linux内核创建一个新进程的过程
来源:互联网 发布:natapp内网穿透域名 编辑:程序博客网 时间:2024/04/29 16:03
1、阅读理解task_struct数据结构
为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需要了解的进程信息。Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,图为task_struct结构体包含了一个进程所需的所有部分信息:
结构体task_struct中部分成员的用法进行简要说明分析:
行号 成员 分析1237 void *stack; //stack should points to a threadinfo struct1239 unsigned int flags; //进程状态的信息1244 int on_cpu; //当前进程在哪个CPU上运行1253 int prio, static_prio, normal_prio; //优先级信息1254 unsigned int rt_priority; //实时任务的优先级 1255 const struct sched_class *sched_class; //与调度相关的函数1256 struct sched_entity se; //调度实体1257 struct sched_rt_entity rt; //实时任务调度实体1272 unsigned int policy; //调度策略1292 struct sched_info sched_info; //调度相关的信息(CPU上运行时间,队列等待时间等。)1295 struct list_head tasks; //任务队列 1302 struct mm_struct *mm, *active_mm; //mm是进程的内存管理信息1312 int exit_state; //进程退出时的状态1313 int exit_code, exit_signal; //进程退出时发出的信号1330 pid_t pid; //进程ID1331 pid_t tgid; //线程组ID1361 struct list_head thread_group; //该进程所有线程的链表1381 u64 start_time; //线程启动时间1384 u64 real_start_time; //线程启动时间1401 int link_count, total_link_count; //文件系统信息计数1412 struct thread_struct thread; //该进程在CPU下的状态1414 struct fs_struct *fs; //文件系统信息结构体1481 int lockdep_depth; //锁的深度1488 void *journal_info; //文件系统日志信息1491 struct bio_list *bio_list; //IO设备表1624 unsigned long timer_slack_ns; //松弛时间值1625 unsigned long default_timer_slack_ns; //松弛时间值Linux进程的状态与操作系统原理中的描述的进程状态有所不同,比如就绪状态和运行状态都是TASK_TUNNING,一个正在运行的进程,我们调用do_exit(),就进入了TASK_ZOMBIE(进程被终止)。程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系。
2、分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构
2.1 fork()函数的理解一个进程调用fork()函数创建子进程时,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
fork()是在用户态用于创建一个子系统的系统调用。Fork()系统调用在父进程和子进程各返回一次。即:
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
- 在父进程中,fork返回新创建子进程的进程ID;
- 在子进程中,fork返回0;
- 如果出现错误,fork返回一个负值;
在执行fork函数后,如果新进程创建成功,则返回两个进程,一个是父进程,一个是子进程。在父进程中,fork返回新创建子进程的进程ID;在子进程中,fork函数返回0。我们可以通过输出fork返回的值来判断当前进程是子进程还是父进程。
fork出错可能有两种原因:
- 当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN;
- 系统内存不足,这时errno的值被设置为ENOMEM。
所谓道生一(start_kernel......cpu_idle),一生二(kernel_init和kthreadd),二生三(即前面0,1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核进程的祖先)。概述了系统启动,创建了第一个进程后,再分别创建用户态进程和内核态进程的过程。
在系统启动的时候,0号进程是我们手工写进入的,把它的系统描述符的数据结构用代码写死的。1号进程的创建相当于复制了一份0号进程的PCB,然后根据1号进程的需要,把PID等参数修改为1号进程的相应值。
2.2 fork()函数的底层实现
Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
1. 复制一个PCB——task_struct
err = arch_dup_task_struct(tsk, orig);
2. 要给新进程分配一个新的内核堆栈
ti = alloc_thread_info_node(tsk, node);
tsk->stack = ti;
setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
3. 要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
Linux通过clone()系统调用实现fork()。这个调用通过一系列的参数标志来指明父,子进程需要共享的资源。fork(),vfork()和__clone()库函数都根据各自需要的参数标志去调用clone()。然后由clone()去调用do_fork()。do_frok完成了创建中的大部分工作,它的定义在ker/frok.c文件中。该函数调用copy_process()的函数,然后让进程开始运行。copy_process()做扫尾工作并返回一个指向子进程的指针。再回到do_fork()函数,如果copy_process()函数返回成功,新创建的子进程被唤醒并让其投入运行。
3、使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone
3.1 使用实验楼环境进行试验,首先进入“LinuxKernel”文件夹,删除“menu”文件,重新下载“menu”文件,如下图所示:
3.2 进入“menu”文件中,利用mv命令将test.c替换为test_fork.c文件,如下图所示:
3.3 执行qemu命令,运行该程序:
3.4 进入gdb环境中,分别在sys_clone,do_fork,dup_task_struct,copy_process,copy_thread,ret_from_fork处设置断点,然后跟踪调试test_fork.c程序:
3.5单条指令开始执行,逐步跟踪do_fork的运行情况:
4、总结
Linux中,fork()系统调用产生的子进程在系统调用处理过程中从ret_from_fork处开始执行。
Linux进程的创建过程就是内存中进程相关资源产生的过程,就是clone的过程。具体分析过程如下图:
分析:fork()函数创建一份子进程。首先是fork()函数执行系统调用,调用system_call(),system_call进而根据进程调用号找到sys_fork()函数所在的位置,进而执行sys_fork()函数;之后,sys_fork()函数开始调用do_fork()函数,do_frok()函数位于fork.c文件中,它完成了子进程创建的大部分工作;最后do_fork()函数调用copy_process()函数,copy_process()函数完成了很多工作,比如,为新进程分配内核堆栈,检查进程数目是否超限,区分父、子进程,获取子进程PID等操作。
参考资料:
- http://blog.chinaunix.net/uid-24227137-id-3344607.html
- http://www.51develop.net/forum.php?mod=viewthread&tid=8963
- http://blog.csdn.net/yunsongice/article/details/5508242
- http://blog.chinaunix.net/uid-21783276-id-2689028.html
- 6、分析Linux内核创建一个新进程的过程
- Linux内核分析6:分析Linux内核创建一个新进程的过程
- 第6节 分析Linux内核创建一个新进程的过程【Linux内核分析】
- 分析Linux内核创建一个新进程的过程(Linux)
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 6.分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- 分析Linux内核创建一个新进程的过程
- Java虚拟机相关知识(1)—Java内存区域
- 设计模式——代理模式
- javascript中使用MSXML调用我自己电脑上的WEBservice
- node.js Stream
- Apache Shiro去掉URL中的JSESSIONID
- Linux内核分析6:分析Linux内核创建一个新进程的过程
- Hibernte之级联操作inverse、cascade
- 常见的GC垃圾收集算法的思路
- [Gradle]org.gradle.tooling.GradleConnectionException: Could not install Gradle distribution from 'ht
- 解决crontab定时任务不能写入文件的问题
- java中使用switch case报错case expressions must be constant expressions
- oracle函数trunc的使用
- iOS 由于枚举类型导致的1 duplicate symbol for architecture x86_64错误
- 详细讲解Asp_net MVC上传文件与下载文件的方法