標籤:

linu0.11父進程等待子進程退出和進程退出原理分析

// 釋放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");
}
/*
發送信號給進程sig是發送的信號,p是接收信號的進程,priv是許可權,
1是代表可以直接設置,比如給自己租發信息,priv為0說明需要一定的許可權
*/
static inline int send_sig(long sig,struct task_struct * p,int priv)
{
if (!p || sig<1 || sig>32)
return -EINVAL;
// 這裡使用euid,即進程設置了suid位的話,可以擴大許可權,即擁有文件屬主的許可權
if (priv || (current->euid==p->euid) || suser())
p->signal |= (1<<(sig-1));
else
return -EPERM;
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);
}
}

/*
* XXX need to check permissions needed to send signals to process
* groups, etc. etc. kill() permissions semantics are tricky!
*/
int sys_kill(int pid,int sig)
{
struct task_struct **p = NR_TASKS + task;
int err, retval = 0;
// pid等於0則給當前進程的整個組發信號,大於0則給某個進程發信號,-1則給全部進程發,小於-1則給某個組發信號
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;
}

// 子進程退出,通知進程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 dont 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

");
// 釋放pcb結構
release(current);
}

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;
// 如果子進程是殭屍進程,即已經退出,則給pid是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);
// 回寫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 */
}

int sys_exit(int error_code)
{
return do_exit((error_code&0xff)<<8);
}
// 等待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,否則進程不可能被喚醒,
即有子進程退出,否則說明是其他信號導致了進程變成可執行狀態,
阻塞的進程被信號喚醒,返回EINTR
*/
if (!(current->signal &= ~(1<<(SIGCHLD-1))))
goto repeat;
else
return -EINTR;
}
return -ECHILD;
}

其中會話是進程組的集合。linux通過下面函數建立一個會話。

// 當前進程新建一個會話並成為會話的領頭進程
int sys_setsid(void)
{
// 已經是領頭進程或者不是超級用戶
if (current->leader && !suser())
return -EPERM;
// 標記該進程是領頭進程
current->leader = 1;
/*
sessionid是進程的id,因為會話是進程組的集合,
所以當前進程的組id也需要更新,否則進程在其他組,但是其他組又不屬於當前的會話,矛盾
*/
current->session = current->pgrp = current->pid;
// 重置終端,會話領頭進程第一個打開終端的時候賦值,一個會話對應一個終端
current->tty = -1;
return current->pgrp;
}

推薦閱讀:

TAG:Linux | Linux內核 |