您的位置:首页 > 运维架构 > Linux

Linux内核分析 笔记六 进程的描述和进程的创建 ——by王玥

2016-03-29 14:49 567 查看

一、知识点总结

(一)进程的描述

1.操作系统内核里有三大功能:

进程管理

内存管理

文件系统

2.进程描述符:task_struct

2.进程描述符——structtask_struct

1.pid_tpid又叫进程标识符,唯一地标识进程
2.双向循环链表链接起了所有的进程,也表示了父子、兄弟等进程关系
3.structmm_struct指的是进程地址空间,涉及到内存管理(对于X86而言,一共有4G的地址空间)
4.thread_structthread与CPU相关的状态结构体
5.struct*file表示打开的文件链表
6.Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈

3.进程状态转换图

数据结构分析:

structtask_struct{
1236	volatilelongstate;	//运行状态
1237	void*stack;//进程的内核堆栈
1238	atomic_tusage;
1239	unsignedintflags;//每个进程的标识符

1240	unsignedint

#ifdefCONFIG_SMP//这部分是条件编译
1243	structllist_nodewake_entry;
1244	inton_cpu;
1245	structtask_struct*last_wakee;
1246	unsignedlongwakee_flips;
1247	unsignedlongwakee_flip_decay_ts;
.....

structlist_headtasks;//这部分很关键,是进程的列表
1296#ifdefCONFIG_SMP
1297	structplist_nodepushable_tasks;
1298	structrb_nodepushable_dl_tasks;
1299#endif
1300
1301	structmm_struct*mm,*active_mm;//和地址的内存空间,内存管理有关的,每个地址有独立的地址空间。
1302#ifdefCONFIG_COMPAT_BRK
1303	unsignedbrk_randomized:1;
1304#endif
1305	/*per-threadvmacaching*/
1306	u32vmacache_seqnum;
1307	structvm_area_struct*vmacache[VMACACHE_SIZE];
1308#ifdefined(SPLIT_RSS_COUNTING)
1309	structtask_rss_stat	rss_stat;
1310#endif

链表的数据结构如下:



它是一个双向链表,参见include/linux/list.h


1330	pid_tpid;//进程的pid,来标识某一个进程
1331	pid_ttgid;
....

/*
1338	*pointersto(original)parentprocess,youngestchild,youngersibling,//进程的父子关系
1339	*oldersibling,respectively.(p->fathercanbereplacedwith
1340	*p->real_parent->pid)
1341	*/
1342	structtask_struct__rcu*real_parent;/*realparentprocess*/
1343	structtask_struct__rcu*parent;/*recipientofSIGCHLD,wait4()reports*/
1344	/*
1345	*children/siblingformsthelistofmynaturalchildren
1346	*/
1347	structlist_headchildren;	/*listofmychildren*/
1348	structlist_headsibling;	/*linkageinmyparent'schildrenlist*/
1349	structtask_struct*group_leader;	/*threadgroupleader*/
1350


...1411/*CPU-specificstateofthistask*/

1412	structthread_structthread;//当前CPU相关的状态,在进程切换时起关键作用。

(二)进程的创建

1.进程的状态以及fork一个进程的用户态代码

2.fork系统调用在父进程和子进程各返回一次

3.TASK_RUNNING具体是就绪还是执行,要看系统当前的资源分配情况

4.TASK_ZOMBIE也叫僵尸进程

fork一个子进程的代码

fork代码

1.#include<stdio.h>
2.#include<stdlib.h>
3.#include<unistd.h>
4.intmain(intargc,char*argv[])
5.{
6.intpid;
7./*forkanotherprocess*/
8.pid=fork();
9.if(pid<0)
10.{
11./*erroroccurred*/
12.fprintf(stderr,"ForkFailed!");
13.exit(-1);
14.}
15.elseif(pid==0)//pid==0和下面的else都会被执行到(一个是在父进程中即pid==0的情况,一个是在子进程中,即pid不等于0)
16.{
17./*childprocess*/
18.printf("ThisisChildProcess!\n");
19.}
20.else
21.{
22./*parentprocess*/
23.printf("ThisisParentProcess!\n");
24./*parentwillwaitforthechildtocomplete*/
25.wait(NULL);
26.printf("ChildComplete!\n");
27.}
28.}


iret与int0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值

如果将系统调用类比于fork();那么就相当于系统调用创建了一个子进程,然后子进程返回之后将在内核态运行,而返回到父进程后仍然在用户态运行

回顾:系统调用的进程创建过程



建一个新进程在内核中的执行过程

1.一个新创建的子进程,(当它获得CPU之后)是从哪一行代码进程执行的?

与之前写过的my_kernel相比较,kernel中是可以指定新进程开始的位置(也就是通过eip寄存器指定代码行)。fork中也有相似的机制

这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_threadincopy_process

fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;

Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

复制一个PCB——task_struct

err=arch_dup_task_struct(tsk,orig);

要给新进程分配一个新的内核堆栈

ti=alloc_thread_info_node(tsk,node);

tsk->stack=ti;

setup_thread_stack(tsk,orig);//这里只是复制thread_info,而非复制内核堆栈

要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。

从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_threadincopy_process

*childregs=*current_pt_regs();//复制内核堆栈

childregs->ax=0;//为什么子进程的fork返回0,这里就是原因!

p->thread.sp=(unsignedlong)childregs;//调度到子进程时的内核栈顶

p->thread.ip=(unsignedlong)ret_from_fork;//调度到子进程时的第一条指令地址

dup_thread复制父进程的PCB



copy_process修改复制的PCB以适应子进程的特点,也就是子进程的初始化



分配一个新的内核堆栈(用于存放子进程数据)

内核堆栈的一部分也要从父进程中拷贝



-


根据拷贝的内核堆栈情况设置eip,esp寄存器的值


使用gdb跟踪创建新进程的过程

更新menu内核,然后删除test_fork.c以及test.c(以减少对之后实验的影响)



编译内核,可以看到fork命令



启动gdb调试,并对主要的函数设置断点





在MenuOS中执行fork,就会发现fork函数停在了父进程中



继续执行之后,停在了do_fork的位置。然后n单步执行,依次进入copy_process、dup_task_struct。按s进入该函数,可以看到dst=src(也就是复制父进程的struct)



3.在copy_thread中,可以看到把task_pg_regs(p)也就是内核堆栈特定的地址找到并初始化


4.到了159、160行的代码就是把压入的代码再放到子进程中:

*children=*current_pt_regs();
childregs->ax=0;


164行,是确定返回地址

p->thread.ip=(unsignedlong)ret_from_fork;


最后
,可以输入finish使得进程运行完。[/code]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: