MINIX内存管理
系统调用EXIT分析
一、系统调用EXIT简介
系统调用exit(status)通过进程向MM发送EXIT类型的消息完成,它是终止一个进程的一般手段,exit关闭进程的所有文件,并且如果其父进程执行了wait则通知父进程。这个调用不能返回。
exit(status)的实现请参见LIB的38504行。
38504 PUBLIC void _exit(status)
38505 int status;
38506 {
38507 message m;
38508
38509 m.m1_i1 = status;
38510 _syscall(MM, EXIT, &m);
38511 }
调用exit(status)的终止进程向MM发类型为EXIT的消息,然后等待MM的应答而阻塞(永远不能收到相应的应答)。
自由空间或别的程序
堆栈段(进程2)
空 隙
数据段(进程2)
正文段(进程2)
自由空间或别的程序
堆栈段(进程1)
空 隙
数据段(进程1)
正文段(进程1)
自由空间或别的程序
图1 结合的I和D空间的内存分配
在下列两个事件都已经发生的情况下进程才会完全终止:(1)进程自己已经退出(或已经被一个信号杀死),(2)它的父进程已经执行了WAIT系统调用以观察发生了什么。已经退出或被杀死而它的父进程还没有为它执行WAIT的进程将进入某种状态,有时被称为僵死状态。这种进程不再参与调度,它的报警时钟如果原来是开着的,那么将被关闭,但它仍将留在进程表中。它的内存被释放。僵死是一种临时状态,很少会持续较长的时间,当父进程最后执行WAIT时,将释放进程表项,并通知文件系统和内核。
EXIT是由内存管理器处理的。过程do_mm_exit(16912行)接收这个调用,但大部分的工作是调用mm_exit(16927行)完成的,这样划分工作是因为mm_exit也被用来处理被信号终止运行的进程,两者工作相同,但参数不同。MM收到EXIT类型的消息后,执行do_mm_exit(),而它再调用mm_exit()来完成终止进程的工作。MM执行完do_mm_exit()后不需向终止进程发应答。mm_exit()的工作如下:
1. 关闭终止进程的定时器。
2. 通过tell_fs()通知FS处理进程exit()时与文件有关的工作。
3. 通过sys_xit()通知SYSTASK处理进程exit()时与内核有关的工作。
4. 释放终止进程的内存空间。这分为两种情况来处理:a、内存分配采用结合的I和D空间,如图1;b、采用独立的I和D空间,如图2。
自由空间或别的程序
堆栈段(进程2)
空 隙
数据段(进程2)
自由空间或别的程序
堆栈段(进程1)
空 隙
数据段(进程1)
正文段(进程1、2共享)
自由空间或别的程序
图2 独立的I和D空间的内存分配
5. 若有父进程等待终止进程终止,则通知父进程,并释放终止进程的mproc。否则,进入僵死状态(HANGING),等待父进程处理后再释放mproc。
6. 将所有的子进程变成init的子进程。
在作完上述工作后,进程就正常终止,其proc、mproc等均为空闲,可供创建新进程使用。
二、和系统调用EXIT有关的头文件和数据结构
内存管理器有一个主头文件mm.h(15800行),每个文件编译时都要用到它,它包含了每个内存目标模块要用到的内核头文件和系统范围的头文件及其它一些头文件。const.h(15900行)定义了内存管理器用的变量,其中有些是特别为16位机器设置的。
内存管理器的全局变量在glo.h中(16200行)说明,其中mp是指向一个mproc结构的指针。它是某进程表的MM部分,而该进程的系统调用正在被处理。dont_reply,在每一个新的请求到来是被初始化为FALSE,但在调用的执行过程中如果发现没有应答信息应该发送,它就被设置为TRUE。例如成功的EXEC就没有应答发送。proc_in_use跟踪当前使用了多少个进程表项,使确定FORK是否可行非常简单。消息缓冲区mm_in和mm_out是分别用于请求和应答消息的。who是当前进程的索引,它与mp的关系是mp=&mproc[who];当一条消息到达的时候,系统调用号被抽取出来放到mm_call中。三个变量err_code、result2和res_ptr用来保存在应答消息中返回给调用者的值。其中最重要的变量是err_code,在调用没有错误而结束时,它被设置为OK。最后两个变量在发生问题是使用。在进程非正常结束时,MINIX把进程的映像写到一个core文件中,core_name定义了这个文件将取的名字。core_sset是一个定义哪些信号应该产生core转储文件的位图。
进程表的内存管理器部分在文件mproc.h中(16300行),其中定义了与进程内存分配有关的全部域和一些附加的信息。最重要的域是mp_seg数组,它有三个表项,分别用于正文、数据和堆栈段,各个表项是一个由虚地址、物理地址和段长度组成的结构,他们都是用块而不是字节来量度的,主要是用于把虚地址映射成物理地址。mproc还保存着进程自己的进程号(pid)以及父进程的进程号、用户号(uid)和组号(gid)。几个域和信号处理有关,mp_ignore、mp_catch、mp_sigmask、mp_sigmask2和mp_sigpending是位图,每个位代表一个可以送往进程的信号。类型sigset_t是32位整数。数组mp_sigact对处理信号非常重要。对每个信号类型都有一个数组元素,每个元素是一个sigaction结构(00769_00773行),每个sigaction结构由三个域组成:1、sa_handler域定义信号的处理方式,是缺省还是专门处理;2、sa_msak的类型是sigset_t,定义了信号处理时,哪些信号将被阻塞。3、sa_flags域是一些信号处理用的标志。mp_flags域是一个无符号整数,用来保存一些位,在386及以上的CPU上是32位的,在低端CPU上则是16位。进程表的最后一个域是mp_procargs,在一个新进程启动时,系统将建立一个类似图4_39的堆栈,指向新进程args数组起始地址的指针被存放在这里,它被ps命令使用。
param.h(16400行)包含了许多用于请求信息中的系统调用参数的宏和4个用于应答消息中域的宏。
table.c(16500行)的编译将为glo.h和mproc.h中的各种EXTERN变量和结构保留空间。在这个文件中有一个重要的数组call_vec(16515行),当一个请求消息到达时,其中的系统调用号将被取出作为call_vec的索引,以找到处理这个系统调用的过程,不是合法调用的系统调用号都会引起执行no_sys,返回一个错误代码。
三、系统调用EXIT涉及的函数
1、set_alarm()(18067行),这个函数被do_alarm()用来设置定时器,也用来关闭正在退出进程的仍在运行的定时器。
2、tell_fs()(19192行),这个函数仅仅被内存管理器用来向文件系统发消息。对于EXIT调用,其格式如下:tell_fs(EXIT,proc,0,0)。
3、sys_xit(),其实这是一个系统任务的系统调用,其工作是由函数do_xit()(15027行)完成的。因为在MINIX中,进程表被分为三部分,内核、内存管理器、文件系统各占其一,一个进程的退出需要三个进程表状态的同时更新。 do_xit()的主要功能是通过查找内核进程表,把内存管理器报告处于退出状态的进程标志为不可运行,使它不会再被调度。
4、 find_share()(17535行),这个函数通过把要执行文件的i结点、设备和修改时间与现有进程比较,在内存管理器的进程表中寻找可共享正文的进程。
5、free_mem()(18879行),这个函数的主要功能是释放内存块,并把它加入空洞表。这要进行一些处理。如果释放的内存块和空洞表中相邻的内存块是连续的,则要进行合并。否则,可直接插入到内存空洞表中。
6、cleanup()(17061行),这个函数的主要功能是完成进程的退出过程,即释放进程表项,并唤醒父进程。
7、check_sig()(18265行),这个函数当系统调用KILL或内核俘获DEL等信号时被激活。其主要功能是查看是否可以发送信号,而这个信号往往被发给一组进程。
四、内存管理EXIT系统调用代码分析
16909 /*=====================================================*
16910 * do_mm_exit *
16911 *=====================================================*/
16912 PUBLIC int do_mm_exit()
16913 {
16914 /* Perform the exit(status) system call. The real work is done by mm_exit(),
16915 * which is also called when a process is killed by a signal.
16916 */
/*执行EXIT系统调用,真正的终止进程的工作由mm_exit()来完成,mm_exit()也用来处理被信号终止运行的进程*/
16917
16918 mm_exit(mp, status);
/*调用函数mm_exit执行退出功能,终止进程的工作由这个函数来完成*/
16919 dont_reply = TRUE; /* don't reply to newly terminated process */
/*不向刚终止的进程发送应答, dont_reply在调用的执行过程中如果发现没有应答信息发送,它就被设置为TRUE。 */
16920 return(OK); /* pro forma return code */
/* EXIT过程执行顺利,返回OK */
16921 }
16924 /*===================================================*
16925 * mm_exit *
16926 *===================================================*/
16927 PUBLIC void mm_exit(rmp, exit_status)
16928 register struct mproc *rmp; /* pointer to the process to be terminated */
/*指向被终止进程的指针,结构mproc定义在16307行*/
16929 int exit_status; /* the process' exit status (for parent) */
/*变量exit_status用来记录进程的退出状态*/
16930 {
16931 /* A process is done. Release most of the process' possessions. If its
16932 * parent is waiting, release the rest, else hang.
16933 */
/*这个函数用来终止进程的运行,释放占有的内存和进程表项*/
16934
16935 register int proc_nr;
16936 int parent_waiting, right_child;
16937 pid_t pidarg, procgrp;
/*pid_t定义在01655 行*/
16938 phys_clicks base, size, s; /* base and size used on 68000 only */
/*phys_clicks定义在03108行*/
16939
16940 proc_nr = (int) (rmp - mproc); /* get process slot number */
/* 得到进程在进程表中的索引号*/
16941
16942 /* Remember a session leader's process group. */
16943 procgrp = (rmp->mp_pid == mp->mp_procgrp) ? mp->mp_procgrp : 0;
/* 判断是否有进程组,进程组是用来存储消息的。如有进程组,记下进程组号,用来接收等会儿返回的消息 */
16944
16945 /* If the exited process has a timer pending, kill it. */
16946 if (rmp->mp_flags & ALARM_ON) set_alarm(proc_nr, (unsigned) 0);
/* 如果进程有一个定时器在运行,就停止它 */
16947
16948 /* Tell the kernel and FS that the process is no longer runnable. */
/* 通知核心和文件系统这个进程不可再运行 */
16949 tell_fs(EXIT, proc_nr, 0, 0); /* file system can free the proc slot */
/*发一条消息给文件系统告诉它此进程已处于退出状态,可以释放文件系统中的进程表项*/
16950 sys_xit(rmp->mp_parent, proc_nr, &base, &size);
/*通过系统调用sys_xit发一条消息给系统任务告诉它标志该进程不可运行,使它不会再被调度*/
16951
16952 /* Release the memory occupied by the child. */
/* 释放内存 */
16953 if (find_share(rmp, rmp->mp_ino, rmp->mp_dev, rmp->mp_ctime) == NULL) {
16954 /* No other process shares the text segment, so free it. */
16955 free_mem(rmp->mp_seg[T].mem_phys, rmp->mp_seg[T].mem_len);
16956 }
/*搜索进程表,确定正文段是否正与另一进程共享,因为有的系统采用结合的I和D空间,如图1,此情况下可直接释放所占有的内存。但具有独立的I和D空间的程序可以共享正文,如图2。若没有别的进程共享正文段,释放它*/
16957 /* Free the data and stack segments. */
16958 free_mem(rmp->mp_seg[D].mem_phys,
16959 rmp->mp_seg[S].mem_vir + rmp->mp_seg[S].mem_len - rmp->mp_seg[D].mem_vir);
/*在独立的I和D空间的程序中,由于数据段和堆栈段独立于正文段,两者处在不同的空间中(可以不连续),因此无论正文段是否被共享,都可以释放此进程的数据段和堆栈段 */
16960
16961 /* The process slot can only be freed if the parent has done a WAIT. */
/*在下列两个事件都已经发生的情况下进程才会完全终止:(1)进程自己已经退出(或已经被一个信号杀死),(2)它的父进程已经执行了WAIT系统调用以观察发生了什么。因此只有在父进程处于等待状态时才能释放子进程的进程表项*/
16962 rmp->mp_exitstatus = (char) exit_status;
/*存储进程退出状态,把exit_status赋给当前进程的mp_exitstatus*/
16963 pidarg = mproc[rmp->mp_parent].mp_wpid; /* who's being waited for? */
/*获得父进程正在等待的进程的pid,存储在变量pidarg中*/
16964 parent_waiting = mproc[rmp->mp_parent].mp_flags & WAITING;
/*看父进程的标志位mp_flags是否被设置成WAITING*/
16965 if (pidarg == -1 || pidarg == rmp->mp_pid || -pidarg == rmp->mp_procgrp)
16966 right_child = TRUE; /* child meets one of the 3 tests */
/* pidarg=-1表示WAITPID系统调用使调用进程阻塞直到子进程中的任一个结束;pidarg=rmp->mp_pid(pidarg>0)表示指向一个特定的正在等待的进程;-pidarg=rmp->mp_procgrp表示等待任何进程组号=-pidarg的子进程,pidarg是在系统调用WAITPID中设置的*/
16967 else
16968 right_child = FALSE; /* child fails all 3 tests */
/*不能得到父进程正在等待进程的pid,即父进程没有处于等待状态*/
16969 if (parent_waiting && right_child)
16970 cleanup(rmp); /* tell parent and release child slot */
/*父进程在等待,调用cleanup()释放子进程的进程表项 */
16971 else
16972 rmp->mp_flags |= HANGING; /* parent not waiting, suspend child */
/* 否则,让进程进入僵死状态 */
16973
16974 /* If the process has children, disinherit them. INIT is the new parent. */
/* 搜索进程表,寻找刚才终止进程的子进程,把它们变为init的子进程*/
16975 for (rmp = &mproc[0]; rmp < &mproc[NR_PROCS]; rmp++) {
16976 if (rmp->mp_flags & IN_USE && rmp->mp_parent == proc_nr) {
16977 /* 'rmp' now points to a child to be disinherited. */
/*rmp指向刚才被终止进程的子进程*/
16978 rmp->mp_parent = INIT_PROC_NR;
/*把它置为init的子进程*/
16979 parent_waiting = mproc[INIT_PROC_NR].mp_flags & WAITING;
/*看init是否处于等待状态*/
16980 if (parent_waiting && (rmp->mp_flags & HANGING)) cleanup(rmp);
/*如果init正在等待且子进程进入了HANGING状态,调用cleanup()处理这个子进程*/
16981 }
16982 }
16983
16984 /* Send a hangup to the process' process group if it was a session leader. */
16985 if (procgrp != 0) check_sig(-procgrp, SIGHUP);
/* 若进程组存在,送一个信号SIGHUP给进程组,使系统调用WAITPID能够对这个进程进行相应的处理*/
16986 }