linux内核中销毁进程

Posted 西邮菜

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核中销毁进程相关的知识,希望对你有一定的参考价值。

一、进程的销毁

        进程的销毁是调用do_exit()函数来完成的,以下是这个函数的代码:

        1、在函数中进程首先释放当前进程代码段和数据段所占的内存页。

        2、遍历所有进程寻找退出进程的子进程,如果有子进程,则将其父进程设为1号进程,如果子进程的状态为僵死状态,则向子进程发送SIGCHLD信号。

        3、关闭当前进程打开的所有文件。

        4、对当前进程的工作目录pwd,根目录root以及执行程序文件的i节点进行同步操作,放回各个i节点并分别置空(释放)。

        5、如果进程是会话的头领则释放其终端、如果进程用过协处理器则将其置空、如果他是leader进程则终止其所有进程。

        6、将当前进程设为僵死状态表示已经释放了占有资源,保存退出码。

        7、向父进程发送信号,该进程停止,并开始调度。

 程序退出处理函数。
// 该函数将把当前进程置为TASK_ZOMBIE状态,然后去执行调度函数schedule(),不再返回。
// 参数code是退出状态码,或称为错误码。
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) 
			task[i]->father = 1;
			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);
	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;
    // 通知父进程,也即向父进程发送信号SIGCHLD - 子进程将停止或终止。
	tell_father(current->father);
	schedule();                     // 重新调度进程运行,以让父进程处理僵死其他的善后事宜。
	return (-1);	/* just to suppress warnings */

二、通知父进程

使用tell_father函数:

        遍历进程,找到了父进程的pid号码,向该pid进程发送SIGCHLD信号,如果没有找到父进程,则自己直接调用release(current)来把自己的资源释放。

 通知父进程 - 向进程pid发送信号SIGCHLD;默认情况下子进程将停止或终止。
// 如果没有找到父进程,则自己释放。但根据POSIX.1要求,若父进程已先行终止,
// 则子进程应该被初始进程1收容。
static void tell_father(int pid)

	int i;

	if (pid)
        // 扫描进城数组表,寻找指定进程pid,并向其发送子进程将停止或终止信号SIGCHLD。
		for (i=0;i<NR_TASKS;i++) 
			if (!task[i])
				continue;
			if (task[i]->pid != pid)
				continue;
			task[i]->signal |= (1<<(SIGCHLD-1));
			return;
		
	printk("BAD BAD - no father found\\n\\r");
	release(current);               // 如果没有找到父进程,则自己释放

三、释放资源

        调用release()函数:

        找到进程清空其task_struct,释放他的内存页

void release(struct task_struct * p)

	int i;

	if (!p)                         // 如果进程数据结构指针是NULL,则什么也不做,退出。
		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");       // 指定任务若不存在则死机

四、父进程接收到SIGCHLD后

        因为父进程一直在waitpid时为挂起状态,收到SIGCHLD后执行sys_waitpid函数执行子进程收尸,前面是负责过滤,主要看switch后面。当进程状态为TASK_ZOMBIE,子进程的用户时间和内核时间加到父进程中,取子进程错误码返回子进程pid。

 系统调用waipid().挂起当前进程,直到pid指定的子进程退出(终止)或收到要求终止该进程的信号,
// 或者是需要调用一个信号句柄(信号处理程序)。如果pid所指向的子进程早已退出(已成所谓的僵死进程),
// 则本调用将立刻返回。子进程使用的所有资源将释放。
// 如果pid > 0,表示等待进程号等于pid的子进程。
// 如果pid = 0, 表示等待进程组号等于当前进程组号的任何子进程。
// 如果pid < -1,表示等待进程组号等于pid绝对值的任何子进程。
// 如果pid = -1,表示等待任何子进程。
// 如 options = WUNTRACED,表示如果子进程是停止的,也马上返回(无须跟踪)
// 若 options = WNOHANG, 表示如果没有子进程退出或终止就马上返回。
// 如果返回状态指针 stat_addr不为空,则就将状态信息保存到那里。
// 参数pid是进程号,*stat_addr是保存状态信息位置的指针,options是waitpid选项。
int sys_waitpid(pid_t pid,unsigned long * stat_addr, int options)

	int flag, code;             // flag标志用于后面表示所选出的子进程处于就绪或睡眠态。
	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;
        // 此时扫描选择到的进程p肯定是当前进程的子进程。
        // 如果等待的子进程号pid>0,但与被扫描子进程p的pid不相等,说明它是当前进程另外的
        // 子进程,于是跳过该进程,接着扫描下一个进程。
		if (pid>0) 
			if ((*p)->pid != pid)
				continue;
        // 否则,如果指定等待进程的pid=0,表示正在等待进程组号等于当前进程组号的任何子进程。
        // 如果此时被扫描进程p的进程组号与当前进程的组号不等,则跳过。
		 else if (!pid) 
			if ((*p)->pgrp != current->pgrp)
				continue;
        // 否则,如果指定的pid < -1,表示正在等待进程组号等于pid绝对值的任何子进程。如果此时
        // 被扫描进程p的组号与pid的绝对值不等,则跳过。
		 else if (pid != -1) 
			if ((*p)->pgrp != -pid)
				continue;
		
        // 如果前3个对pid的判断都不符合,则表示当前进程正在等待其任何子进程,也即pid=-1的情况,
        // 此时所选择到的进程p或者是其进程号等于指定pid,或者是当前进程组中的任何子进程,或者
        // 是进程号等于指定pid绝对值的子进程,或者是任何子进程(此时指定的pid等于-1).接下来根据
        // 这个子进程p所处的状态来处理。
		switch ((*p)->state) 
            // 子进程p处于停止状态时,如果此时WUNTRACED标志没有置位,表示程序无须立刻返回,于是
            // 继续扫描处理其他进程。如果WUNTRACED置位,则把状态信息0x7f放入*stat_addr,并立刻
            // 返回子进程号pid.这里0x7f表示的返回状态是wifstopped()宏为真。
			case TASK_STOPPED:
				if (!(options & WUNTRACED))
					continue;
				put_fs_long(0x7f,stat_addr);
				return (*p)->pid;
            // 如果子进程p处于僵死状态,则首先把它在用户态和内核态运行的时间分别累计到当前进程
            // (父进程)中,然后取出子进程的pid和退出码,并释放该子进程。最后返回子进程的退出码和pid.
			case TASK_ZOMBIE:
				current->cutime += (*p)->utime;
				current->cstime += (*p)->stime;
				flag = (*p)->pid;                   // 临时保存子进程pid
				code = (*p)->exit_code;             // 取子进程的退出码
				release(*p);                        // 释放该子进程
				put_fs_long(code,stat_addr);        // 置状态信息为退出码值
				return flag;                        // 返回子进程的pid
            // 如果这个子进程p的状态既不是停止也不是僵死,那么就置flag=1,表示找到过一个符合
            // 要求的子进程,但是它处于运行态或睡眠态。
			default:
				flag=1;
				continue;
		
	
    // 在上面对任务数组扫描结束后,如果flag被置位,说明有符合等待要求的子进程并没有处于退出或
    // 僵死状态。如果此时已设置WNOHANG选项(表示若没有子进程处于退出或终止态就立刻返回),就
    // 立刻返回0,退出。否则把当前进程置为可中断等待状态并重新执行调度。当又开始执行本进程时,
    // 如果本进程没有收到除SIGCHLD以外的信号,则还是重复处理。否则,返回出错码‘中断系统调用’
    // 并退出。针对这个出错号用户程序应该再继续调用本函数等待子进程。
	if (flag) 
		if (options & WNOHANG)                  // options = WNOHANG,则立刻返回。
			return 0;
		current->state=TASK_INTERRUPTIBLE;      // 置当前进程为可中断等待态
		schedule();                             // 重新调度。
		if (!(current->signal &= ~(1<<(SIGCHLD-1))))
			goto repeat;
		else
			return -EINTR;                      // 返回出错码(中断的系统调用)
	
    // 若没有找到符合要求的子进程,则返回出错码(子进程不存在)。
	return -ECHILD;

5、额外sys_kill(int pid,int sig)

        系统调用kill()可用于向任何进程或进程组发送任何信号,而并非只是杀死进程。参数pid是进程号;sig是需要发送的信号。   

        1、如果pid > 0, 则信号被发送给进程号是pid的进程。

        2、如果pid = 0, 那么信号就会被发送给当前进程的进程组中的所有进程。

        3、如果pid = -1,则信号sig就会发送给除第一个进程(初始进程init)外的所有进程

        4、如果pid < -1,则信号sig将发送给进程组-pid的所有进程。

int sys_kill(int pid,int sig)

	struct task_struct **p = NR_TASKS + task;
	int err, retval = 0;

	if (!pid) while (--p > &FIRST_TASK) 
		if (*p && (*p)->pgrp == current->pid) 
			if ((err=send_sig(sig,*p,1)))           // 强制发送信号
				retval = err;
	 else if (pid>0) while (--p > &FIRST_TASK) 
		if (*p && (*p)->pid == pid) 
			if ((err=send_sig(sig,*p,0)))
				retval = err;
	 else if (pid == -1) while (--p > &FIRST_TASK) 
		if ((err = send_sig(sig,*p,0)))
			retval = err;
	 else while (--p > &FIRST_TASK)
		if (*p && (*p)->pgrp == -pid)
			if ((err = send_sig(sig,*p,0)))
				retval = err;
	return retval;

以上是关于linux内核中销毁进程的主要内容,如果未能解决你的问题,请参考以下文章

Linux进程管理进程的创建与销毁

《Linux内核设计与实现》读书笔记- Linux的进程

《内核设计与实现》读书笔记- 进程管理

Linux系统启动流程内核及模块管理

linux僵死进程的产生与避免

Linux设备驱动程序设备驱动程序简介