在Unix/Linux系统中,正常情况下,子进程是通过父进程创建的,子进程可以再继续创建新的进程。在Linux中,除了进程0(即PID=0的进程,init进程)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对应的被其创建出的进程则为子进程,因而除了进程0以外的进程都只有一个父进程,但一个进程可以有多个子进程。
fork
函数包含在
unistd.h
库中,其最主要的特点是,调用一次,返回两次,当父进程fork()创建子进程失败时,fork()返回-1,当父进程fork()创建子进程成功时,此时,父进程会返回子进程的pid,而子进程返回的是0。所以可以根据返回值的不同让父进程和子进程执行不同的代码。
上图中,当fork()函数调用后,父进程中的变量pid赋值成子进程的pid(pid>0),所以父进程会执行else里的代码,打印出"
This is the parent
",而子进程的变量pid赋值成0,所以子进程执行
if(pid == 0)
里的代码,打印出"
This is the child
"子进程的结束和父进程的运行是一个
异步
过程,即
父进程永远无法预测子进程到底什么时候结束
。 当一个 进程完成它的工作终止之后,它的父进程需要调用
wait()
或者
waitpid()
系统调用取得子进程的终止状态。因此,主进程挂了之后对子进程是没有影响的,让我们继续往下看。
当一个子进程结束运行(一般是调用exit、运行时发生致命错误或收到终止信号所导致)时,子进程的退出状态(返回值)会回报给操作系统,系统则以SIGCHLD信号将子进程被结束的事件告知父进程,此时子进程的进程控制块(PCB)仍驻留在内存中。一般来说,收到SIGCHLD后,父进程会使用wait系统调用以获取子进程的退出状态,然后内核就可以从内存中释放已结束的子进程的PCB;而如若父进程没有这么做的话,子进程的PCB就会一直驻留在内存中,也即成为僵尸进程。
总的来说,僵尸进程可以理解为一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,此时子进程得不到释放就变成了僵尸进程。
僵尸进程是有危害的。进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,当一个进程一直处于Z状态,那么它的PCB也就一直都要被维护。因为PCB本身就是一个结构体会占用空间,僵尸进程也就会造成资源浪费,所以我们应该避免僵尸进程的产生。
Unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是 在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息(包括进程号
the process ID
、退出状态
the termination status of the process
、运行时间
the amount of CPU time taken by the process
等)。直到父进程通过
wait / waitpid
来取时才释放。 但这样就导致了问题,如果进程不调用
wait / waitpid
的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“
Z
”。如果父进程能及时处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。 如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。
例如有个进程,它定期的产生一个子进程,这个子进程需要做的事情很少,做完它该做的事情之后就退出了,因此这个子进程的生命周期很短,但是,父进程只管生成新的子进程,至于子进程退出之后的事情,则一概不闻不问,这样,系统运行上一段时间之后,系统中就会存在很多的僵死进程,倘若用ps命令查看的话,就会看到很多状态为Z的进程。 严格地来说,
僵尸进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程
。因此,当我们寻求如何消灭系统中大量的僵死进程时,其中一种解决方案就是把产生大量僵死进程的那个元凶枪(产生僵尸进程的父进程)毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程就能处理了。
严格的说,僵尸进程并不是问题的根源,罪魁祸首是产生大量僵死进程的父进程。因此,我们可以直接除掉元凶,通过kill发送
SIGTERM
或者
SIGKILL
信号。元凶死后,僵尸进程进程变成孤儿进程,由init充当父进程,并回收资源。命令为:
kill -9 父进程的pid值
强制杀死父进程。
父进程通过wait或waitpid等函数去等待子进程结束,但是不好,会导致父进程一直等待被挂起,相当于一个进程在干活,没有起到多进程的作用。
通过信号机制,子进程退出时向父进程发送SIGCHLD信号,父进程调用signal(SIGCHLD,sig_child)去处理SIGCHLD信号,在信号处理函数sig_child()中调用wait进行处理僵尸进程。什么时候得到子进程信号,什么时候进行信号处理,父进程可以继续干其他活,不用去阻塞等待。
孤儿进程则是指当一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
孤儿进程由于有init进程循环的wait()回收资源,因此并没有什么危害。我们已经知道,孤儿进程是没有父进程的进程,孤儿进程的回收任务最终会落到了init进程身上,init进程就是所有进程的根进程,有点类似于二叉树的根节点。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程结束了其生命周期的时候,init处理孤儿进程。因此孤儿进程并不会有什么危害。
1、
进程3.0——进程状态与僵尸进程、孤儿进程
2、
僵尸进程和孤儿进程区别
文章目录前言一、僵尸进程1.1 僵尸进程的危害1.2 僵尸进程解决方案1.2.1 kill杀死元凶父进程1.2.2 父进程用wait或waitpid去回收资源1.2.3 通过信号机制,在处理函数中调用wait,回收资源二、孤儿进程参考前言 在Unix/Linux系统中,正常情况下,子进程是通过父进程创建的,子进程可以再继续创建新的进程。在Linux中,除了进程0(即PID=0的进程,init进程)以外的所有进程都是由其他进程使用系统调用fork创建的,这里调用fork创建新进程的进程即为父进程,而相对
核心
服务器
上跑了一堆的脚本、程序,难免有时候
会
出现
僵尸进程
,死不死活不活的在那里占用资源,最初只是写了个根据关键字查杀
进程
的
linux
shell脚本,后来发现很多时候
进程
死在那里的时候其实是内部调用
子进程
的时候出现了问题,这时候光杀
父
进程
根本没解决根本问题。比如说rsync的时候通过ssh来连接,rsync本身没问题,但可能ssh死掉了。因此重新写了脚本,递归查找
子进程
。
代码如下:
#!/bin/sh
# 递归找到导致
进程
僵死的最底层
子进程
并杀除.
ParentProcessID=$1;
if [ “x${ParentProcessID}” = “x” ] ; then
[root@qht2 ~]# ps -ef | grep httpd
root 3799 1 0 10:41 pts/0 00:00:00 /usr/sbin/nss_pcache off /etc/httpd/alias
root 3803 1 3 10:41 ? 00:00:00 /usr/sbin/httpd
apache 3807 3803 0 10:41 ? 0
主要功能: 1.读取配置文件程序 2.启动
进程
3.监控
进程
,查看
进程
是否
退出
或者崩溃 4.若
进程
退出
或者崩溃,重启程序。 5.支持sleep功能 6.
进程
若连续崩溃NUM_MAX次就进行相应的睡眠周期struct proc_struct proc:
struct proc_ struct [mp:
if(array)
return 0
∥切换到目录rse
chdirldiri
ifdp= opendir(dir}=NuLL}开日录/proc,矢败返回0,成功把描述指针返回给d
return o
〃将φpro文件夹的描述符指针传递给reεddir,读取文件夹内容,循环赋值给结构体di
while ((dirp= readdir(dp))= NULLY
char data 301
∥取文件名称赋值给数组daa(其中包含有
进程
的名称(pid
sprintf(data, "s", dirp->d_name);
∥是否是由字符09组成的字符串,即得到所有
进程
的pid
f((IsDigit(data))
prac =(struct proc_struct )4 malloc(sizeof(struct proc_struct)
tmp proc:
prac->pid =a: oi(dirp->d_name):
It(proc tind( proc. array))
free( tmp);
closedir(dp
cturn
proc_find
两个参数分别是两个
进程
描述的结构体指针
李比较两个
进程
pd是否相等
李*相等返回1,不相等返回0
幸率球事容球家草事家事球峰率享事球摩率球享享溶事*事卷寒球套事塞容寒/
int proc find( struct prcc_struct* src, struct proc- struct* dest)
char buffer[40%6]. ps cmd[20]
It fd. I
sprintf(buffer, "ed/star", sre->pid);
fd = open(butter, O_RDONLY)
if(fd==-1)
rerurn 0
memset(buffer, wO, sizeof(buffer))
len= read(fd, bufter, sizeof(bufter )-1)
close(ld)
return 0:
p= butter:
p= strrchr(p, C)
narq=strrchr(p, ))
n=q-p-1
if (len >= sizeof, srt->name))
len= sizeof(src->name)-1
p+ l, len
src->namelen]=0;
turn(strcmp( src->name, dest
dest->name)==0)? 1: 0-
条善参数aay:让程结构体指针;参数sie
进程
列表数组aray的大小ie:配置文件路径
从配置文件得到指定的程序列表,将对应
进程
的信息填充到aray数组中
羋执行成功返回
进程
个数,执行失败返回0
int get_ proc( struct proc_struct array, int size, char file
intnRet=o
if(! array I‖(si
0)l‖fhle
myprinttf"invalid parameterin
retun o
char line[4096];
FILE fp= fopen(file, T");
if(fp)
printf("open file cs fail\n", file)
return U
memset(line, 0, 4095);
while(fgets(lire, 4095, tp)&& nRet size)
memcpy(void s)[(&arraylnRet )->cmdline), (void")line, strlen(line)-2 )
tmp= strrchr(line, / )
Lmp += I:
memcpy((&array inRet))->name, tmp, strlen(tmp)- 2)
nRet++
return(nReL);
康棒串串浓凉率旅浓串底率卖毒志着旅浓浓准溶房表
装 startProc
*卷参数proc:要启动的进的结构体描述指针
执行成功返回1,
子进程
退出
宗塞家康家家家家家家家家宋家家聚家苯家球察塞家塞家家容家塞家家家家室家家察家家家聚家聚寒撑家装家掌建察家家室事
int startProc (struct proc_ struct* proc, struct proc _struct*must_run_ proc int mIst_run_size
static inti=d
if( proc)return 0
if(strlen(proc->cmdline I<=0) return 0;
int pid= forko:
〃
进程
内部返回值为0,返回给
父
进程
的为自己的pid
inta〓
if(pid
pid= fork(
ifpd≡0
execl(char")proc->cmdline,(char")prDc->name,NULL);
∥exit:
It(o):
sleep42片
waiL(NULL)
sleep( I:
if(i== must run size -1)
if(check proc(&must run proc[i])==0)
startProc( &mtust_run_proeli], must_run_prce, must_run_size);
if(i== must run size-11
start Proc( &must_run_proclil, must_run_ proc, mustrun_ _size);
!**幸幸串率幸米幸*家*幸毕零*幸幸半字幸字华米*幸半孝率非幸零幸学幸幸车
3a*8*daemon init
幸*启动配置文件当中的需要守护的程序
执行成功返回1,中途出错,返回-1
长界零家墨军零家零率家三哮零座零率零零容岸军零罕型率零零零零牢察座察零零零零季球军零容零
int moniter_ run(struct proc_struct"must_run_proc, int proc_ size)
nti=0:
for(i=0; i< must_run_size: i ++)
∥监控程序是否正在运行
if(check_ proc(&(must un_ proc[il))<=o)
∥厘新片动程序
startProc(&' must run procli]), must run proc, proc size
return I:
幸*事率事率率**率**字幸学摩*率*幸幸学幸半*率幸字****幸中*幸学幸
春*着*信号处理函数 exit_proc
翥安全结束监控的程序
4来没有返回值
告参毒萨响幸帝称昨嗜幸古称索点响卷南都南请南幸难布际本啪昨青市南动南香请非市赤南本
void exit_ proc(int ar
InL I
struct proc struct proc
for(i=0; i< must run_ Size: i++)
proc=&(must_run_proc[i]):
kill(proc->pid, SIGTERM);
exit flag=I
exit(o):
void exit_proc(int pid
要main
L.获取程序列表2启动
进程
3.监控
进程
,若程序岀或崩溃,重新启动程序4.收到退
出信号,安全结束监控程序
成功返回1,失败返回0
零牢容容家容字家容容察*禁容容字哮零常字容容容家察容牢容零容容容容容牢字家客字容牢容零容*字容客字容容字容家容容字岩
static void run moniter( void data)
读取程序列表
must_ run _size get proc(must_run_proc, sIZE, data
if(rmust run Sizc <=1)
return o
struct sigaction act, oldact
act,sa handler= exit_proc
act. sa flags =SA ONESHOT
sigaction(SIGTERM, &act, NULL)
sigaction(SIGINT, &act, NULL)
sigaction(sIGHUP, &act, NULL);
检测并启动未启动的程序
moniter_ run(must run proc, must run slze)
eturn null
int creat and run moniter(char * file)
pthread_t moniter_ thread
if(pthread_create(&moniter_thread, NULL, run_moniter, file)==0)
printf("thread create Ok, check thread start \n")
return
printf( thread check create Errin"):
return -I
零零零零享享事职增零半非寥零享半容零摩率率零享剩率容半半享零半率零半率零率辱寒零享
要 IsDigit
参茶爹数a字符串的地址
*判断字符串是否有纯数字字符组成
春客是纯数字返回1,不是纯数字返回0
喜非要串思率串串串串家串润串串串串串串毒毒喜串串最率毒串串踪串率串串非球毒串妆串串毒串串影零串串毒事串
static int IsDigit[char aD)
Int size
∥得到当前字符串的长度
size= strlen(a:
∥若字符串长度为0,则直接返回0:即宇符串为空则返回0:
if(size ==0) return 0;
∥循环遍历整个字符串
forli=0; i< Size; i++)
∥如果字符小于字符0,或者大于字符9,则返回0
if(ai]<ol ai>9)
retum
∥走到这一步说明字符串由字符09组成,返回1
return
主进程
源文件:man,c
main.c
#include"process, h
Include <stdio. h>
include <stdlib h>
甲 include< unistd. I>
甲 nclude< signal>
却 nclude <sys/ ypes,h
include <sys/stat. h>
甲 include< tenth>
int main(void)
creat_and_run_moniter("proclistini")
while(l)
sleep(D)
以上内容是程序全部源文件
linux
下我们可以调用fork函数创建
子进程
,创建的
子进程
将
会
得到
父
进程
的数据空间、堆、栈……副本(采用写时复制机制),
子进程
将
会
继承
父
进程
的信号掩码、信号处理方式、当前工作目录、
会
话id、组id……。当
子进程
退出
时
父
进程
应当及时获取
子进程
退出
状态,否则,如果
父
进程
是一直在运行,那么
子进程
的
退出
状态将一直保存在内存中,直到
父
进程
退出
才释放。
我们可以使用如下几种方法避免
僵尸进程
的产生:
1.在fork后调用wait/waitpid函数取得
子进程
退出
状态。
2.调用fork两次(第一次调用产生一个
子进程
,第二次调用fork是在第一个
子进程
中调用,同时将
父
进程
退出
(第一个
子进程
退出
),此时的第
Linux
允许
进程
查询内核以获得其
父
进程
的 PID,或者其任何
子进程
的执行状态。例如,
进程
可以创建一个
子进程
来执行特定的任务,然后调用诸如 wait() 这样的一些库函数检查
子进程
是否终止。如果
子进程
已经终止,那么,它的终止代号将告诉
父
进程
这个任务是否已成功地完成。
为了遵循这些设计原则,不允许
Linux
内核在
进程
一终止后就丢弃包含在
进程
描述符字段中的数据。只有
父
进程
发出了与被终止的
进程
相关的 wait() 类系统调用之后,才允许这样做。这就是引入僵死状态的原因:尽管从技术上来说
进程
已死,但必须保存它的描述符,直到
父
进程
得到通知。
如果一个
进程
已经终止,但是它的
父
进程
尚未调用 wai
今天在看一些关于线程方面的文章时,觉得这篇文章讲得很不错,对于初学者对于线程的理解很有帮助,一方面想自己保存起来,另一方面希望更多人能看到。原文出处:简单了解C语言中主线程
退出
对子线程的影响
对于程序来说,如果
主进程
在
子进程
还未结束时就已经
退出
,那么
Linux
内核
会
将
子进程
的
父
进程
ID改为1(也就是init
进程
),当
子进程
结束后
会
由init
进程
来回收该
子进程
。
那如果是把
进程
换成线程的话,
会
怎么样呢?假设主线程在子线程结束前就已经
退出
,子线程
会
发生什么?
在一些论坛上看到许多人说子线程也
会
跟着
退出
,其实这是
一般来说如果我们启动了一个A
进程
,然后通过A
进程
再启动B
进程
,那么A
进程
就是B
进程
的
父
进程
,或者说B
进程
是A
进程
的
子进程
。
那么如果这个时候我们强杀了A
进程
之后,B
进程
会
处于什么状态呢?是继续运行还是也
退出
了?
实际情况是这两种情况都有可能发生,取决A
进程
的状态。如果A
进程
是
会
话首
进程
,那么A
退出
后,B
进程
也
会
退出
;反之如果A
进程
不是
会
话首
进程
,那么A
退出
后,B
进程
不会
退出
。
说到这,你可能懵逼了,什么是
会
话首
进程
,怎么看一个
进程
是不是
会
话首
进程
呢。
首先,每个
进程
都
会
属于一个
进程
组,每个
进程
组有个
我们知道在
unix
/
linux
中,正常情况下,
子进程
是通过
父
进程
创建的,
子进程
在创建新的
进程
。
子进程
的结束和
父
进程
的运行是一个异步过程,即
父
进程
永远无法预测
子进程
到底什么时候结束。 当一个
进程
完成它的工作终止之后,它的
父
进程
需要调用wait()或者waitpid()系统调用取得
子进程
的终止状态。
一个
父
进程
退出
,而它的一个或多个
子进程
还在运行,那么那些
子进程
将成为
孤儿进程
。
孤儿进程
将被init
进程
(
进程
号为1)所收养,并由init
进程
对它们完成状态收集工作。
一个
进程
使用fork创建
子进程
,如果
子进程
退出
,而
父
进程
并没有调用wait或waitpid获取
子进程
主进程
创建守护
进程
其一:守护
进程
会
在
主进程
代码执行结束后就终止
其二:守护
进程
内无法再开启
子进程
,否则抛出异常:AssertionError:daemonic proesses are not allowed to have children
注意:
进程
之间是互相独立的,
主进程
代码运行结束,守护...
Linux
环境下,
僵尸进程
和
孤儿进程
是两种不同的
进程
状态。
僵尸进程
是已经终止但其
父
进程
没有调用 `wait` 函数获取它的终止状态,导致它的
进程
描述符仍然存在于系统中。
僵尸进程
不占用系统资源,但是如果过多地产生
僵尸进程
,可能
会
对系统造成影响。
孤儿进程
是指其
父
进程
已经终止,但其本身并未终止的
进程
。这样的
进程
将被系统的 init
进程
(
进程
号为 1)收养,并由 init
进程
继续等待它们的终止状态。
因此,
僵尸进程
是由
父
进程
造成的,
孤儿进程
则是由
父
进程
终止造成的。