6_Linux系统中的进程创建分析

来源:互联网 发布:知乎怎么添加关注话题 编辑:程序博客网 时间:2024/05/21 20:56
版权声明:陈诚
原创作品转载请注明出处
《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 
==========================================================================


这周的实验是通过gdb跟踪调试Linux内核是如何fock产生一个进程并从哪开始执行这个子进程的。

下面就进入实验楼的环境开始实验,输入以下命令重新下载编译,如图:

rm menu -rf  git  clone https://github.com/mengning/menu.git  cd menu  mv test_fork.c test.c  make rootfs  



查看test.c里面的代码如下:



启动menuOS后停止等待,开启gdb调试:



打好断点:


接着在menuOS界面输入fork运行:



切换回shell命令行界面,输入continue命令,走起:







fork执行完成后,运行结果如下图:




分析

1.进程间的状态转换



2.新进程的产生

     话说,道生一(start_kernel、cpu_idle)、一生二(kernel_init和kthreadd)、二生三(即前面0,1和2三个进程)

1号进程是所有用户态进程祖先、0号进程是所有内核线程的祖先。

进程描述符PCB的数据结构是task_struct类型,它包括了进程相关的所有信息,task_struct结构中主要有:
-进程状态(记录进程等待、运行或死锁三种状态)
-调度信息
-标识符
-进程间的通讯情况
-进程链接信息(进程链表的插入等操作信息)
-时间和定时器信息
-文件系统信息
-页面管理信息
-和处理器相关的环境上下文信息
-...
其中:
1)state字段。用于描述进程当前的状态,由一组标志组成,每个标志描述一种进程可能的状态,这些状态的是互斥的。
2)thread_info字段。Linux内核用共用体结构将一个进程的线程描述符和内核堆栈
union thread_union{
    struct thread_info thread_info;
    unsigned long stack[2048];
}
3)task字段。所有的进程的描述符组成了一个双向链表,task字段的类型为list_head。
struct list_head{
    struct list_head *next,*prev;
}


    start_kernel中创建0号进程,由0号进程创建1号进程,1号进程是所有用户态进程的祖先,0号进程是所有内核进程的祖先。用户态程序通过调用 fork创建一个有父子关系的新进程。  通过copy_thread复制现有进程实现创建新进程,先复制task_struct,再为其分配一个内核栈,接着copy_process进行修改,如进程pid、进程链表等。fork、vfork和clone三个系统调用都可创建一个新进程,但都是通过do_fork实现的。
复制过程为:
1)err = arch_dup_task_struct(tsk, orig); // 复制一个task_struct数据结构的PCB
2)ti = alloc_thread_info_node(tsk, node); tsk->stack = ti; // 给新进程分配一个内核堆栈
3)setup_thread_stack(tsk, orig);   // 这里只是复制thread_info,而非复制内核堆栈
4)*childregs = *current_pt_regs(); // 复制内核堆栈 
5)childregs->ax = 0; // 子进程的fork返回0的原因
     创建好后执行到ret_from_fork,在ret_from_fork中会跳到syscall_exit,此时父进程的内核栈中保存了执行fork前的上下文,子进程的内核栈中也从父进程那里复制到的自己的上下文,所以现在父子进程可以等待被调度后正常执行啦。


总结:
     在Linux中创建新进程中,用fork得到的子进程是父进程的复制,它从父进程处复制了整个进程的地址空间,包括进
程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设定、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等。子进程所独有的只是它的进程号、资源使用和计时器等。
        1.无论用三种系统调用clone、fork、vfork中的哪种来创建一个新进程,都是通过调用do_fork来实现的
        2.通过复制父进程PCB的task_struct创建一个新进程
        3.子进程修改复制后的PCB,如pid、进程链表等
        4.fork()系统调用产生的子进程从ret_from_fork处开始执行,p->thread.ip = (unsigned long)ret_from_fork
        5.可通过返回值判断当前进程是父进程还是子进程,父进程处返回进程号,子进程自己返回0


0 0
原创粉丝点击