资源描述
Click to edit Master title style,Click to edit Master text styles,Second level,Third level,Fourth level,Fifth level,11/7/2009,#,进程管理,Outline,进程,进程描述符及任务结构,进程创建,线程在Linux中的实现,进程终止,小结,2022/4/21,LiangShi:LinuxOperatingSystems,2,上节课内容回顾,操作系统概念介绍,Linux操作系统版本介绍,Linux操作系统特有特征,SourceInsight源码阅读工具介绍,Linux进程描述符,Linux进程状态,2022/4/21,LiangShi:LinuxOperatingSystems,8,进程标识,使用,进程描述符地址,进程和进程描述符之间有非常严格的一一对应关系,,使得用32位进程描述符地址标识进程非常方便,使用,PID,(ProcessID,PID),每个进程的PID都存放在进程描述符的pid域中,2022/4/21,LiangShi:LinuxOperatingSystems,9,进程的PID,进程的pid字段,include/linux/types.h,include/asm-XXX/posix_typesYYY.h,Pid最大值,参见kernel/pid.c,顺序使用,&,循环使用,include/linux/threads.h,2022/4/21,LiangShi:LinuxOperatingSystems,10,用户如何获得一个进程的pid,系统调用getpid,关于进程组,使用组链表,所有进程共享组内第一个进程的pid,数据:tgid,单独一个进程可以看成只有一个进程的组,getpid返回组pid,2022/4/21,LiangShi:LinuxOperatingSystems,11,进程和进程的,内核,堆栈,Linux为每个进程分配一个8KB大小的内存区域,,用于存放该进程两个不同的数据结构:,Thread_info,进程的内核堆栈,进程处于内核态时使用,,不同于用户态堆栈,内核控制路径所用的堆栈,很少,因此对栈和Thread_info,来说,8KB足够了,Thread_info,2022/4/21,LiangShi:LinuxOperatingSystems,12,Thread_union,C语言允许用如下的一个union结构来方便的表示,这样的一个混合体,include/linux/sched.h,thread_info由体系结构相关部分定义,阅读include/asm-x86/thread_info.h,以及include/asm-x86/thread_info_32.h,2022/4/21,LiangShi:LinuxOperatingSystems,13,进程描述符的分配/回收/访问,Thread_info的分配/回收/访问,alloc_thread_info,free_thread_info,2022/4/21,LiangShi:LinuxOperatingSystems,14,2022/4/21,LiangShi:LinuxOperatingSystems,15,current_thread_info,从刚才看到的thread_info和内核态堆栈之间的配,对,内核可以很容易的从esp寄存器的值获得当,前在CPU上运行的进程的描述符指针,因为这个内存区是8KB=2,13,大小,内核必须做的,就是让esp有13位的有效位,以获得进程描述符,的基地址,8191=8192-1=0 x2000-1=0 x1fff,2022/4/21,LiangShi:LinuxOperatingSystems,取反:0 xffffe000(最后13位为0),16,Current宏,2022/4/21,LiangShi:LinuxOperatingSystems,17,Current宏的使用,Current宏可以看成当前进程的进程描述符指针,,在内核中直接使用,举例:,比如current-pid返回在CPU上正在执行的进程的PID,2022/4/21,LiangShi:LinuxOperatingSystems,18,进程的创建,许多进程可以并发的运行同一程序,这些进程共享,内存中程序正文的单一副本,但每个进程有自己的,单独的数据和堆栈区,一个进程可以在任何时刻可以执行新的程序,并且,在它的生命周期中可以运行几个程序,又如,只要用户输入一条命令,shell进程就创建一,个新进程,2022/4/21,LiangShi:LinuxOperatingSystems,20,传统的UNIX操作系统采用统一的方式来创建进程,子进程复制父进程所拥有的资源,缺点:,创建过程慢、效率低,事实上,子进程复制的很多资源是不会使用到的,现代UNIX内核通过引入三种不同的机制来解决这个,问题,2022/4/21,LiangShi:LinuxOperatingSystems,21,1、写时复制技术,Copy-On-Writing,COW,写时复制技术允许父子进程能读相同的物理页。,只要两者有一个进程试图写一个物理页,内核就把这,个页的内容拷贝到一个新的物理页,并把这个新的物,理页分配给正在写的进程,2022/4/21,LiangShi:LinuxOperatingSystems,22,2、轻量级进程允许父子进程共享许多数据结构,页表,打开的文件列表,信号处理,3、vfork,使用vfork创建的新进程能够共享父进程的内存地址空间。,父进程在这个过程中被阻塞,直到子进程退出或者执行一,个新的程序,2022/4/21,LiangShi:LinuxOperatingSystems,23,Linux的进程创建,Linux提供了几个系统调用来创建和终止进程,以及,执行新程序,Fork,vfork和clone系统调用创建新进程,其中,clone创建轻量级进程,必须指定要共享的资源,exec系统调用执行一个新程序,exit系统调用终止进程(进程也可以因收到信号而终止),2022/4/21,LiangShi:LinuxOperatingSystems,24,fork,fork系统调用创建一个新进程,调用fork的进程称为父进程,新进程是子进程,子进程几乎就是父进程的完全复制。它的地址空间是父进,程的复制,一开始也是运行同一程序。,fork系统调用为父子进程返回不同的值,2022/4/21,LiangShi:LinuxOperatingSystems,25,exec,很多情况下,子进程从fork返回后很多会调用exec来开始,执行新的程序,这种情况下,子进程根本不需要读或者修改父进程拥有的,所有资源。,所以fork中地址空间的复制依赖于CopyOnWrite技术,降,低fork的开销,2022/4/21,LiangShi:LinuxOperatingSystems,26,使用fork和exec的例子,If(result=fork()=0),elseif(result0),/*result=子进程的pid,父进程将会从这里继续执行*/,/*子进程代码*/,if(execve(“new_program”,)0),perror(“execvefailed”);,exit(1);,perror(“forkfailed”),2022/4/21,LiangShi:LinuxOperatingSystems,27,分开这两个系统调用是有好处的,比如服务器可以fork许多进程执行同一个程序,有时程序只是简单的exec,执行一个新程序,在fork和exec之间,子进程可以有选择的执行一系列操作以确保,程序以所希望的状态运行,重定向输入输出,关闭不需要的打开文件,改变UID或是进程组,重置信号处理程序,若单一的系统调用试图完成所有这些功能将是笨重而低效的,现有的fork-exec框架灵活性更强,清晰,模块化强,2022/4/21,LiangShi:LinuxOperatingSystems,28,do_fork,不论是fork,vfork还是clone,在内核中最终都调用了,do_fork,arch/x86/kernel/process_32.c,2022/4/21,LiangShi:LinuxOperatingSystems,29,kernel/fork,阅读do_fork;copy-process;了解大致程序流程,?子进程从哪里开始执行,它的返回值是什,么?,阅读copy_thread(arch/arm/kernel/process.c),复制父进程的堆栈?,父进程的堆栈中有些什么?Fork系统调用?,2022/4/21,LiangShi:LinuxOperatingSystems,30,子进程的内核态堆栈,高地址,子进程的8Kunion,用户态堆栈esp的值,用户态下eip的值,子进程的硬件上下文,返回值eax被强制写0,子进程恢复到用户,态时需要的上下文,esp,eip,ret_from_fork,esp,Thread_info,低地址,2022/4/21,LiangShi:LinuxOperatingSystems,31,子进程的执行,fork后,子进程处于可运行状态,由调度器决定,何时把CPU交给这个子进程,进程切换后因为eip指向ret_from_fork,所以CPU,立刻跳转到ret_from_fork()去执行。,接着这个函数调用ret_from_sys_call(),此函数用,存放在栈中的值装载所有寄存器,并强迫CPU返,回用户态,回忆进程的切换,2022/4/21,LiangShi:LinuxOperatingSystems,32,arch/x86/kernel/entry_32.S,2022/4/21,LiangShi:LinuxOperatingSystems,33,内核线程,系统把一些重要的任务委托给周期性执行的进程,刷新磁盘高速缓存,交换出不用的页框,维护网络链接等待,内核线程与普通进程的,差别,每个内核线程执行一个单独指定的内核函数,只运行在内核态,只使用大于PAGE_OFFSET的线性地址空间,2022/4/21,LiangShi:LinuxOperatingSystems,34,例如,0号进程创建1号进程init,2022/4/21,LiangShi:LinuxOperatingSystems,35,线程和进程的比较,Linux内核中没有线程的概念,没有针对所谓线程的调度策略,没有数据结构用来表示一个线程,一般线程的概念在linux中只是表现为一组共享资源的,进程(每个这样的进程都有自己的进程描述符),在其他系统中(比如windows),线程是实实在在的一种运行抽象,提供了比进程更轻,更快的调度单元,在linux中“线程”仅仅是表示多个进程共享资源的一种,说法,2022/4/21,LiangShi:LinuxOperatingSystems,36,创建内核线程,Kerenl_thread(),创建一个内核线程,并且只能由,另一个内核线程来执行这个调用,2022/4/21,LiangShi:LinuxOperatingSystems,37,2022/4/21,LiangShi:LinuxOperatingSystems,38,进程树,进程0,进程1,2022/4/21,LiangShi:LinuxOperatingSystems,39,进程0,所有进程的祖先叫做进程0,在系统初始化阶段由start_kernel()函数从无到有手工创建,的一个内核线程,Init_task,Init_thread_union,进程0最后的初始化工作创建init内核线程,此后运行cpu_idle,,成为idle进程,2022/4/21,LiangShi:LinuxOperatingSystems,40,进程1,又称为init进程,由进程0在start_kernel调用rest_init创建,init进程PID为1,当调度程序选择到init进程时,,init进程开始执行kernel_init()函数,2022/4/21,LiangShi:LinuxOperatingSystems,41,kernel_init()为常规内核任务初始化一些必要的,内核线程,如:,kflushd刷新脏缓冲区中的内容到磁盘以归还内存,kswapd执行内存回收功能的线程,最后kernel_init()函数在init_post中调用execve()系,统调用装入可执行程序init。从此,init内核线程,变成一个普通的进程。但init进程从不终止,因,为它创建和监控操作系统外层的所有进程的活动,2022/4/21,LiangShi:LinuxOperatingSystems,42,2022/4/21,LiangShi:LinuxOperatingSystems,43,撤销进程,进程终止,进程终止的一般方式是exit()系统调用。,这个系统调用可能由编程者明确的在代码中插入,另外,在控制流到达主过程C中的main()函数的最后一条语句时,,执行exit()系统调用,内核可以强迫进程终止,当进程接收到一个不能处理或忽视的信号时,当在内核态产生一个不可恢复的CPU异常而内核此时正代表该进程,在运行,2022/4/21,LiangShi:LinuxOperatingSystems,44,撤销进程,进程终止使用exit系统调用,删除进程,在父进程调用wait()类系统调用检查子进程是否,合法终止以后,就可以删除这个进程,2022/4/21,LiangShi:LinuxOperatingSystems,45,
展开阅读全文