理解进程的退出(基于linux0.11)

当一个进程调用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 found\n\r");
    	// 释放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");
    }

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值