理解进程的退出
- 2020 年 3 月 12 日
- 筆記
当一个进程调用exit的时候,就意味着他退出了。我们看一下他退出的时候,都做了什么操作。
int sys_exit(int error_code) { return do_exit((error_code&0xff)<<8); } int do_exit(long code) { int i; // 释放代码段和数据段页表,页目录,物理地址 free_page_tables(get_base(current->ldt[1]),get_limit(0x0f)); free_page_tables(get_base(current->ldt[2]),get_limit(0x17)); for (i=0 ; i<NR_TASKS ; i++) // 找出当前进程的子进程 if (task[i] && task[i]->father == current->pid) { // 子进程的新父进程是进程id为1的进程 task[i]->father = 1; /* 父进程没有调wait,子进程退出了,然后父进程也退出了。没人回收子进程的pcb,给init进程发 */ if (task[i]->state == TASK_ZOMBIE) /* assumption task[1] is always init */ (void) send_sig(SIGCHLD, task[1], 1); } // 关闭文件 for (i=0 ; i<NR_OPEN ; i++) if (current->filp[i]) sys_close(i); // 回写inode到硬盘 iput(current->pwd); current->pwd=NULL; iput(current->root); current->root=NULL; iput(current->executable); current->executable=NULL; // 是会话首进程并打开了终端 if (current->leader && current->tty >= 0) tty_table[current->tty].pgrp = 0; if (last_task_used_math == current) last_task_used_math = NULL; // 是会话首进程,则通知会话里的所有进程会话结束 if (current->leader) kill_session(); // 更新状态 current->state = TASK_ZOMBIE; current->exit_code = code; // 通知父进程 tell_father(current->father); // 重新调度进程 schedule(); return (-1); /* just to suppress warnings */ }
释放地址
// from是线性地址。释放from开始,连续的n个大小为4MB的页面对应的物理地址。最后释放页表、页目录项 int free_page_tables(unsigned long from,unsigned long size) { unsigned long *pg_table; unsigned long * dir, nr; // 判断是否按4MB对齐 if (from & 0x3fffff) panic("free_page_tables called with wrong alignment"); if (!from) panic("Trying to free up swapper memory space"); // 算出size包含多少个MB,比如size是0 - 1>>22,则计算机后是1 size = (size + 0x3fffff) >> 22; /* 页目录在地址0开始的地方,首先右移得到页目录索引, 根据索引得到页目录项内容,因为页目录项的内容占4个字节, 其中高20位是页表地址,低12位是标记位,,所以要乘以4得到 from对应的页目录项的地址。即dir = from >> 22 << 2 = from >> 20, 但是代码里是直接右移20位,所以需要和0xffc与,把低两位置0,最后得到from 对应的页目录项的地址 */ dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */ for ( ; size-->0 ; dir++) { // 低位是1说明该页目录项有效 if (!(1 & *dir)) continue; // *dir为页表首地址,与0xfffff000是因为高二十位是有效地址,低12位是标记位 pg_table = (unsigned long *) (0xfffff000 & *dir); // 释放每个页表指向的物理地址 for (nr=0 ; nr<1024 ; nr++) { // 页表是否有效,有效则释放*pg_table指向物理地址,以4kb对齐 if (1 & *pg_table) // 与0xfffff000是因为高二十位是有效地址,低12位是标记位 free_page(0xfffff000 & *pg_table); // 置页表无效 *pg_table = 0; // 下一个页表 pg_table++; } // 释放页表占据的物理地址 free_page(0xfffff000 & *dir); // 置页目录项为无效 *dir = 0; } invalidate(); return 0; }
结束会话
// 结束会话,给该会话的所有进程发SIGHUP信号,因为子进程会继承父进程的sessionid,所以if可能会多次成立 static void kill_session(void) { struct task_struct **p = NR_TASKS + task; while (--p > &FIRST_TASK) { if (*p && (*p)->session == current->session) (*p)->signal |= 1<<(SIGHUP-1); } }
通知父进程
// 子进程退出,通知进程id是pid的父进程 static void tell_father(int pid) { int i; if (pid) for (i=0;i<NR_TASKS;i++) { if (!task[i]) continue; if (task[i]->pid != pid) continue; // 根据pid找到父进程,设置子进程退出的信号 task[i]->signal |= (1<<(SIGCHLD-1)); return; } /* if we don't find any fathers, we just release ourselves */ /* This is not really OK. Must change it to make father 1 */ printk("BAD BAD - no father foundnr"); // 释放pcb结构 release(current); }
我们发生子进程释放了一系列的资源,但是没有释放pcb。即task_struct结构体。这时候子进程是僵尸进程。需要等待父进程处理。
// 等待pid进程退出,并且把退出码写到stat_addr变量 int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options) { int flag, code; struct task_struct ** p; verify_area(stat_addr,4); repeat: flag=0; for(p = &LAST_TASK ; p > &FIRST_TASK ; --p) { // 过滤不符合条件的 if (!*p || *p == current) continue; // 不是当前进程的子进程则跳过 if ((*p)->father != current->pid) continue; // pid大于0说明等待某一个子进程 if (pid>0) { // 不是等待的子进程则跳过 if ((*p)->pid != pid) continue; } else if (!pid) { // pid等于0则等待进程组中的进程,不是当前进程组的进程则跳过 if ((*p)->pgrp != current->pgrp) continue; } else if (pid != -1) { // 不等于-1说明是等待某一个组的,但不是当前进程的组,组id是-pid的组,不是该组则跳过 if ((*p)->pgrp != -pid) continue; } // else { // 等待所有进程 // } // 找到了一个符合条件的进程 switch ((*p)->state) { // 子进程已经退出,这个版本没有这个状态 case TASK_STOPPED: if (!(options & WUNTRACED)) continue; put_fs_long(0x7f,stat_addr); return (*p)->pid; case TASK_ZOMBIE: // 子进程已经退出,则返回父进程 current->cutime += (*p)->utime; current->cstime += (*p)->stime; flag = (*p)->pid; code = (*p)->exit_code; release(*p); put_fs_long(code,stat_addr); return flag; default: // flag等于1说明子进程还没有退出 flag=1; continue; } } // 还没有退出的进程 if (flag) { // 设置了非阻塞则返回 if (options & WNOHANG) return 0; // 否则父进程挂起 current->state=TASK_INTERRUPTIBLE; // 重新调度 schedule(); /* 在schedule函数里,如果当前进程收到了信号,会变成running状态, 如果current->signal &= ~(1<<(SIGCHLD-1)))为0,即...0000000100000... & ...111111110111111... 说明当前需要处理的信号是SIGCHLD,因为signal不可能为全0,否则进程不可能被唤醒, 即有子进程退出,跳到repeat找到该退出的进程,否则说明是其他信号导致了进程变成可执行状态, 阻塞的进程被信号唤醒,返回EINTR */ if (!(current->signal &= ~(1<<(SIGCHLD-1)))) goto repeat; else return -EINTR; } return -ECHILD; } //父进程在wait_pid调用时释放pcb的一页内存,重新调度进程 void release(struct task_struct * p) { int i; if (!p) return; for (i=1 ; i<NR_TASKS ; i++) if (task[i]==p) { task[i]=NULL; free_page((long)p); schedule(); return; } panic("trying to release non-existent task"); }