Linux进程控制及守护进程
2011-10-23 16:38
176 查看
原文:/article/4716073.html
进程是程序的一次执行,是运行在自己的虚拟地址空间的一个具有独立功能的程序.进程是分配和释放资源的基本单位,当程序执行时,系统创建进程,分配内存和CPU等资源;进程结束时,系统回收这些资源。进程由PCB(进程控制块)来描述:
进程id。系统中每个进程有唯一的id,在C语言中用
进程的状态,有运行、挂起、停止、僵尸等状态。
进程切换时需要保存和恢复的一些CPU寄存器。
描述虚拟地址空间的信息。
描述控制终端的信息。
当前工作目录(CurrentWorkingDirectory)。
文件描述符表,包含很多指向
和信号相关的信息。
用户id和组id。
控制终端、Session和进程组。
进程可以使用的资源上限(ResourceLimit)。
线程又名轻负荷进程,它是在进程基础上程序的一次执行,一个进程可以拥有多个线程.
线程没有独立的资源,它共享进程的ID,共享进程的资源.
线程是UNIX中最小的调度单位,目前有系统级调度和进程级调度两种线程调度实行方式:系统级调度的操作系统以线程为单位进行调度;进程级调度的操作系统仍以进程为单位进行调度,进程再为其上运行的线程提供调度控制.
守护进程:常驻后台执行的特殊进程,如sysprocinit
读取PID号:getpidgetpgrpgetppid<unistd.h><sys/types.h>
读取用户标识号:getuidgeteuidgetgidgetegid
例子:
#include<unistd.h>
voidmain()
{
printf("pid=[%d],gid=[%d],ppid=[%d]\n",getpid(),getpgrp(),getppid());
printf("uid=[%d],euid=[%d],gid=[%d],egid=[%d]\n",getuid(),geteuid(),getgid(),getegid());
}
#./id1
pid=[3311],gid=[3311],ppid=[2925]
uid=[0],euid=[0],gid=[0],egid=[0]
UNIX中,存储了一系列的变量,在shell下执行'env'命令,就可以得到环境变量列表.
环境变量分为系统环境变量和用户环境变量两种.系统环境变量在注册时自动设置,大部分具有特定
的含义;用户环境变量在Shell中使用赋值命令和export命令设置.如下例先设置了变量XYZ,再将其转化
为用户环境变量:
[bill@billstoneUnix_study]$XYZ=/home/bill
[bill@billstoneUnix_study]$env|grepXYZ
[bill@billstoneUnix_study]$exportXYZ
[bill@billstoneUnix_study]$env|grepXYZ
XYZ=/home/bill
[bill@billstoneUnix_study]$
UNIX下C程序中有两种获取环境变量值的方法:全局变量法和函数调用法
UNIX系统中采用一个指针数组来存储全部环境值:
Externchar**environ;
该法常用于将environ作为参数传递的语句中,比如后面提到的execve函数等.
UNIX环境下操作环境变量的函数如下:
函数getenv以字符串形式返回环境变量name的取值,因此每次只能获取一个环境变量的值;而且要使用该函数,必须知道要获取环境变量的名字.
进程和人类一样,都有创建,发展,休眠和死亡等各种生命形态.
函数fork创建新进程,
函数exec执行新程序,
函数sleep休眠进程,
函数wait同步进程和函数
exit结束进程.
创建子进程的两个用途:1.复制代码2.执行新程序
调用fork创建的子进程,将共享父进程的代码空间,复制父进程数据空间,如堆栈等.调用exec族函数将使用新程序的代码覆盖进程中原来的程序代码,并使进程使用函数提供的命令行参数和环境变量去执行
新的程序.
exec函数族有六个函数如下:
#include<unistd.h>
intexecl(constchar*path,constchar*arg0,...,(char*)0);
intexecle(constchar*path,constchar*arg0,...,(char*)0,char*constenvp[]);
intexeclp(constchar*file,constchar*arg0,...,(char*)0);
intexecv(constchar*path,constchar*argv[]);
intexecve(constchar*path,constchar*argv[],constchar*envp[]);
intexecvp(constchar*file,constchar*argv[]);
externchar**environ;
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以
这些函数原型看起来很容易混,但只要掌握了规律就很好记。不带字母p(表示path)的
如果参数中包含/,则将其视为路径名。
否则视为不带路径的程序名,在
带有字母l(表示list)的
对于以e(表示environment)结尾的
vfork比起fork函数更快,二者的区别如下:
a)vfork创建的子进程并不复制父进程的数据,在随后的exec调用中系统会复制新程序的数据到内存,继而避免了一次数据复制过程
b)父进程以vfork方式创建子进程后将被阻塞,知道子进程退出或执行exec调用后才能继续运行.当子进程只用来执行新程序时,vfork-exec模型比fork-exec模型具有更高的效率,这种方法也是Shell创建新进程的方式.
在UNIX中,我们也可以使用system函数完成新程序的执行.
函数system会阻塞调用它的进程,并执行字符串string中的shell命令.
[bill@billstoneUnix_study]$catexec3.c
#include<unistd.h>
#include<stdio.h>
intmain()
{
charcmd[]={"/bin/uname-a"};
system(cmd);
return0;
}
[bill@billstoneUnix_study]$makeexec3
ccexec3.c-oexec3
[bill@billstoneUnix_study]$./exec3
Linuxbillstone2.4.20-8#1ThuMar1317:18:24EST2003i686athloni386GNU/Linux
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用
如果一个进程已经终止,但是它的父进程尚未调用
ps-ef|grep13707
bill137071441004:17pts/000:00:00./szomb1
bill1370813707004:17pts/000:00:00[szomb1<defunct>]//僵死进程
bill137101441004:17pts/000:00:00grep13707
[bill@billstoneUnix_study]$
其中,'defunct'代表僵死进程.对于僵死进程,不能奢望通过kill命令杀死之,因为它已经'死'了,不再接收任何系统信号.
当子进程终止时,它释放资源,并且发送SIGCHLD信号通知父进程.父进程接收SIGCHLD信号,调用wait返回子进程的状态,并且释放系统进程表资源.故如果子进程先于父进程终止,而父进程没有调用wait接收子进程信息,则子进程将转化为僵死进程,直到其父进程结束.
一旦知道了僵死进程的成因,我们可以采用如下方法预防僵死进程:
(1)wait法
父进程主动调用wait接收子进程的死亡报告,释放子进程占用的系统进程表资源.
(2)托管法
如果父进程先于子进程而死亡,则它的所有子进程转由进程init领养,即它所有子进程的父进程ID号变为1.当子进程结束时init为其释放进程表资源.
托管法技巧:两次fork,子进程退出,则子子进程的父进程变为init。
(3)忽略SIGC(H)LD信号
当父进程忽略SIGC(H)LD信号后,即使不执行wait,子进程结束时也不会产生僵死进程.
(4)捕获SIGC(H)LD信号
当父进程捕获SIGC(H)LD信号,并在捕获函数代码中等待(wait)子进程
所谓守护进程是一个在后台长期运行的进程,它们独立于控制终端,周期性地执行某项任务,或者阻塞直到事件发生,默默地守护着计算机系
统的正常运行.在UNIX应用中,大部分socket通信服务程序都是以守护进程方式执行.
完成一个守护进程的编写至少包括以下几项:
(1)后台执行
后台运行的最大特点是不再接收终端输入,托管法可以实现这一点
pid_tpid;
pid=fork();
if(pid>0)exit(0);//父进程退出
/*子进程继续运行*/
父进程结束,shell重新接管终端控制权,子进程移交init托管
(2)独立于控制终端
在后台进程的基础上,脱离原来shell的进程组和session组,自立门户为新进程组的会话组长进程,与原终端脱离关系
#include<unistd.h>
pid_tsetsid();
函数setsid创建一个新的session和进程组.
(3)清除文件创建掩码
进程清除文件创建掩码,代码如下:
umask(0);
(4)处理信号
为了预防父进程不等待子进程结束而导致子进程僵死,必须忽略或者处理SIGCHLD信号,其中忽略该信号的方法为:
signal(SIGCHLD,SIG_IGN);
守护进程独立于控制终端,它们一般以文件日志的方式进行信息输出.Syslog是Linux中的系统日志管理服务,通过守护进程syslogd来维护。该守护进程在启动时会读一个配置文件“/etc/syslog.conf”。该文件决定了不同种类的消息会发送向何处。例如,紧急消息可被送向系统管理员并在控制台上显示,而警告消息则可记录到一个文件中。该机制提供了3个syslog函数,分别为openlog、syslog和closelog。
下面是一个简单的守护进程实例InitServer
[bill@billstoneUnix_study]$catinitServer.c
[bill@billstoneUnix_study]$makeinitServer
ccinitServer.c-oinitServer
[bill@billstoneUnix_study]$./initServer
[bill@billstoneUnix_study]$ps-ef|grepinitServer
bill137211004:40?00:00:00./initServer//'?'代表initServer独立于终端
bill137251441004:41pts/000:00:00grepinitServer
程序在接收到SIGCLD信号后立即执行函数ClearChild,并调用非阻塞的waitpid函数结束子进程结束
信息,如果结束到子进程结束信息则释放该子进程占用的进程表资源,否则函数立刻返回.这样既保证了不增加守护进程负担,又成功地预防了僵死进程的产生.
自己编写的一个程序:
#cattest.c
程序的本意是用子进程来执行函数,而fork子进程完全复制父进程数据空间,这样子进程会获得父进程的所有变量的拷贝,尽管父子进程变量名相同,但却存在了不同的地方,因此不能通过内存变量完成父子进程之间的信息传递。
进程是程序的一次执行,是运行在自己的虚拟地址空间的一个具有独立功能的程序.进程是分配和释放资源的基本单位,当程序执行时,系统创建进程,分配内存和CPU等资源;进程结束时,系统回收这些资源。进程由PCB(进程控制块)来描述:
进程id。系统中每个进程有唯一的id,在C语言中用
pid_t类型表示,其实就是一个非负整数。
进程的状态,有运行、挂起、停止、僵尸等状态。
进程切换时需要保存和恢复的一些CPU寄存器。
描述虚拟地址空间的信息。
描述控制终端的信息。
当前工作目录(CurrentWorkingDirectory)。
umask掩码。
文件描述符表,包含很多指向
file结构体的指针。
和信号相关的信息。
用户id和组id。
控制终端、Session和进程组。
进程可以使用的资源上限(ResourceLimit)。
线程与进程
线程又名轻负荷进程,它是在进程基础上程序的一次执行,一个进程可以拥有多个线程.线程没有独立的资源,它共享进程的ID,共享进程的资源.
线程是UNIX中最小的调度单位,目前有系统级调度和进程级调度两种线程调度实行方式:系统级调度的操作系统以线程为单位进行调度;进程级调度的操作系统仍以进程为单位进行调度,进程再为其上运行的线程提供调度控制.
守护进程:常驻后台执行的特殊进程,如sysprocinit
读取PID号:getpidgetpgrpgetppid<unistd.h><sys/types.h>
读取用户标识号:getuidgeteuidgetgidgetegid
例子:
#include<unistd.h>
voidmain()
{
printf("pid=[%d],gid=[%d],ppid=[%d]\n",getpid(),getpgrp(),getppid());
printf("uid=[%d],euid=[%d],gid=[%d],egid=[%d]\n",getuid(),geteuid(),getgid(),getegid());
}
#./id1
pid=[3311],gid=[3311],ppid=[2925]
uid=[0],euid=[0],gid=[0],egid=[0]
环境变量
UNIX中,存储了一系列的变量,在shell下执行'env'命令,就可以得到环境变量列表.环境变量分为系统环境变量和用户环境变量两种.系统环境变量在注册时自动设置,大部分具有特定
的含义;用户环境变量在Shell中使用赋值命令和export命令设置.如下例先设置了变量XYZ,再将其转化
为用户环境变量:
[bill@billstoneUnix_study]$XYZ=/home/bill
[bill@billstoneUnix_study]$env|grepXYZ
[bill@billstoneUnix_study]$exportXYZ
[bill@billstoneUnix_study]$env|grepXYZ
XYZ=/home/bill
[bill@billstoneUnix_study]$
UNIX下C程序中有两种获取环境变量值的方法:全局变量法和函数调用法
(a)全局变量法
UNIX系统中采用一个指针数组来存储全部环境值:Externchar**environ;
该法常用于将environ作为参数传递的语句中,比如后面提到的execve函数等.
1:#include<stdio.h>
2:
3:externchar**environ;
4:
5:intmain()
6:
7:{
8:
9:char**p=environ;
10:
11:while(*p){
12:
13:fprintf(stderr,"%s\n",*p);
14:
15:p++;
16:
17:}
18:
19:return0;
20:
21:}
(b)函数调用法
UNIX环境下操作环境变量的函数如下:#include<stdlib.h> char*getenv(constchar*name); intsetenv(constchar*name,constchar*value,intrewrite); voidunsetenv(constchar*name);
函数getenv以字符串形式返回环境变量name的取值,因此每次只能获取一个环境变量的值;而且要使用该函数,必须知道要获取环境变量的名字.
在进程中执行新程序的三种方法
进程和人类一样,都有创建,发展,休眠和死亡等各种生命形态.函数fork创建新进程,
函数exec执行新程序,
函数sleep休眠进程,
函数wait同步进程和函数
exit结束进程.
创建子进程的两个用途:1.复制代码2.执行新程序
(1)fork-exec
调用fork创建的子进程,将共享父进程的代码空间,复制父进程数据空间,如堆栈等.调用exec族函数将使用新程序的代码覆盖进程中原来的程序代码,并使进程使用函数提供的命令行参数和环境变量去执行新的程序.
#include<sys/types.h> #include<unistd.h> pid_tfork(void);
fork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。一开始是一个控制流程,调用
fork之后发生了分叉,变成两个控制流程,这也就是“fork”(分叉)这个名字的由来了。子进程中
fork的返回值是0,而父进程中
fork的返回值则是子进程的id(从根本上说
fork是从内核返回的,内核自有办法让父进程和子进程返回不同的值),这样当
fork函数返回后,程序员可以根据返回值的不同让父进程和子进程执行不同的代码。
fork的返回值这样规定是有道理的。
fork在子进程中返回0,子进程仍可以调用
getpid函数得到自己的进程id,也可以调用
getppid函数得到父进程的id。在父进程中用
getpid可以得到自己的进程id,然而要想得到子进程的id,只有将
fork的返回值记录下来,别无它法。
fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个
file结构体,也就是说,
file结构体的引用计数要增加。
exec函数族有六个函数如下:
#include<unistd.h>
intexecl(constchar*path,constchar*arg0,...,(char*)0);
intexecle(constchar*path,constchar*arg0,...,(char*)0,char*constenvp[]);
intexeclp(constchar*file,constchar*arg0,...,(char*)0);
intexecv(constchar*path,constchar*argv[]);
intexecve(constchar*path,constchar*argv[],constchar*envp[]);
intexecvp(constchar*file,constchar*argv[]);
externchar**environ;
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以
exec函数只有出错的返回值而没有成功的返回值。
这些函数原型看起来很容易混,但只要掌握了规律就很好记。不带字母p(表示path)的
exec函数第一个参数必须是程序的相对路径或绝对路径,例如
"/bin/ls"或
"./a.out",而不能是
"ls"或
"a.out"。对于带字母p的函数:
如果参数中包含/,则将其视为路径名。
否则视为不带路径的程序名,在
PATH环境变量的目录列表中搜索这个程序。
带有字母l(表示list)的
exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有
...,
...中的最后一个可变参数应该是
NULL,起sentinel的作用。对于带有字母v(表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是
NULL,就像
main函数的
argv参数或者环境变量表一样。
对于以e(表示environment)结尾的
exec函数,可以把一份新的环境变量表传给它,其他
exec函数仍使用当前的环境变量表执行新程序。
exec调用举例如下:
char*constps_argv[]={"ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL};
char*constps_envp[]={"PATH=/bin:/usr/bin","TERM=console",NULL};
execl("/bin/ps","ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL);
execv("/bin/ps",ps_argv);
execle("/bin/ps","ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL,ps_envp);
execve("/bin/ps",ps_argv,ps_envp);
execlp("ps","ps","-o","pid,ppid,pgrp,session,tpgid,comm",NULL);
execvp("ps",ps_argv);
(2)vfork-exec
vfork比起fork函数更快,二者的区别如下:a)vfork创建的子进程并不复制父进程的数据,在随后的exec调用中系统会复制新程序的数据到内存,继而避免了一次数据复制过程
b)父进程以vfork方式创建子进程后将被阻塞,知道子进程退出或执行exec调用后才能继续运行.当子进程只用来执行新程序时,vfork-exec模型比fork-exec模型具有更高的效率,这种方法也是Shell创建新进程的方式.
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
intmain()
{
pid_tpid;
if((pid=vfork())==0){
fprintf(stderr,"----begin----\n");
sleep(3);
execl("/bin/uname","uname","-a",0);
fprintf(stderr,"----end----\n");
}
elseif(pid>0)
fprintf(stderr,"forkchildpid=[%d]\n",pid);
else
fprintf(stderr,"Forkfailed.\n");
return0;
}
[bill@billstoneUnix_study]$makeexec2
make:`exec2'isuptodate.
[bill@billstoneUnix_study]$./exec2
----begin----
forkchildpid=[13293]
[bill@billstoneUnix_study]$Linuxbillstone2.4.20-8#1ThuMar1317:18:24EST2003i686athloni386GNU/Linux
(3)system
在UNIX中,我们也可以使用system函数完成新程序的执行.函数system会阻塞调用它的进程,并执行字符串string中的shell命令.
[bill@billstoneUnix_study]$catexec3.c
#include<unistd.h>
#include<stdio.h>
intmain()
{
charcmd[]={"/bin/uname-a"};
system(cmd);
return0;
}
[bill@billstoneUnix_study]$makeexec3
ccexec3.c-oexec3
[bill@billstoneUnix_study]$./exec3
Linuxbillstone2.4.20-8#1ThuMar1317:18:24EST2003i686athloni386GNU/Linux
进程休眠:sleep
进程终止:exitabort
进程同步(等待):wait
一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或
waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量
$?查看,因为Shell是它的父进程,当它终止时Shell调用
wait或
waitpid得到它的退出状态同时彻底清除掉这个进程。
如果一个进程已经终止,但是它的父进程尚未调用
wait或
waitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程。
ps-ef|grep13707
bill137071441004:17pts/000:00:00./szomb1
bill1370813707004:17pts/000:00:00[szomb1<defunct>]//僵死进程
bill137101441004:17pts/000:00:00grep13707
[bill@billstoneUnix_study]$
其中,'defunct'代表僵死进程.对于僵死进程,不能奢望通过kill命令杀死之,因为它已经'死'了,不再接收任何系统信号.
当子进程终止时,它释放资源,并且发送SIGCHLD信号通知父进程.父进程接收SIGCHLD信号,调用wait返回子进程的状态,并且释放系统进程表资源.故如果子进程先于父进程终止,而父进程没有调用wait接收子进程信息,则子进程将转化为僵死进程,直到其父进程结束.
一旦知道了僵死进程的成因,我们可以采用如下方法预防僵死进程:
(1)wait法
父进程主动调用wait接收子进程的死亡报告,释放子进程占用的系统进程表资源.
(2)托管法
如果父进程先于子进程而死亡,则它的所有子进程转由进程init领养,即它所有子进程的父进程ID号变为1.当子进程结束时init为其释放进程表资源.
托管法技巧:两次fork,子进程退出,则子子进程的父进程变为init。
(3)忽略SIGC(H)LD信号
当父进程忽略SIGC(H)LD信号后,即使不执行wait,子进程结束时也不会产生僵死进程.
(4)捕获SIGC(H)LD信号
当父进程捕获SIGC(H)LD信号,并在捕获函数代码中等待(wait)子进程
wait和waitpid函数的原型是:
#include<sys/types.h>
#include<sys/wait.h>
pid_twait(int*status);
pid_twaitpid(pid_tpid,int*status,intoptions);
若调用成功则返回清理掉的子进程id,若调用出错则返回-1。父进程调用wait或waitpid时可能会:
阻塞(如果它的所有子进程都还在运行)。
带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信息)。
出错立即返回(如果它没有任何子进程)。
这两个函数的区别是:
如果父进程的所有子进程都还在运行,调用wait将使父进程阻塞,而调用waitpid时如果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。
wait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程。
可见,调用wait和waitpid不仅可以获得子进程的终止信息,还可以使父进程阻塞等待子进程终止,起到进程间同步的作用。如果参数status不是空指针,则子进程的终止信息通过这个参数传出,如果只是为了同步而不关心子进程的终止信息,可以将status参数指定为NULL。
例30.6.waitpid
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
intmain(void)
{
pid_tpid;
pid=fork();
if(pid<0){
perror("forkfailed");
exit(1);
}
if(pid==0){
inti;
for(i=3;i>0;i--){
printf("Thisisthechild\n");
sleep(1);
}
exit(3);
}else{
intstat_val;
waitpid(pid,&stat_val,0);
if(WIFEXITED(stat_val))
printf("Childexitedwithcode%d\n",WEXITSTATUS(stat_val));
elseif(WIFSIGNALED(stat_val))
printf("Childterminatedabnormally,signal%d\n",WTERMSIG(stat_val));
}
return0;
}
子进程的终止信息在一个int中包含了多个字段,用宏定义可以取出其中的每个字段:如果子进程是正常终止的,WIFEXITED取出的字段值非零,WEXITSTATUS取出的字段值就是子进程的退出状态;如果子进程是收到信号而异常终止的,WIFSIGNALED取出的字段值非零,WTERMSIG取出的字段值就是信号的编号。作为练习,请读者从头文件里查一下这些宏做了什么运算,是如何取出字段值的。
守护进程
所谓守护进程是一个在后台长期运行的进程,它们独立于控制终端,周期性地执行某项任务,或者阻塞直到事件发生,默默地守护着计算机系统的正常运行.在UNIX应用中,大部分socket通信服务程序都是以守护进程方式执行.
完成一个守护进程的编写至少包括以下几项:
(1)后台执行
后台运行的最大特点是不再接收终端输入,托管法可以实现这一点
pid_tpid;
pid=fork();
if(pid>0)exit(0);//父进程退出
/*子进程继续运行*/
父进程结束,shell重新接管终端控制权,子进程移交init托管
(2)独立于控制终端
在后台进程的基础上,脱离原来shell的进程组和session组,自立门户为新进程组的会话组长进程,与原终端脱离关系
#include<unistd.h>
pid_tsetsid();
函数setsid创建一个新的session和进程组.
(3)清除文件创建掩码
进程清除文件创建掩码,代码如下:
umask(0);
(4)处理信号
为了预防父进程不等待子进程结束而导致子进程僵死,必须忽略或者处理SIGCHLD信号,其中忽略该信号的方法为:
signal(SIGCHLD,SIG_IGN);
守护进程独立于控制终端,它们一般以文件日志的方式进行信息输出.Syslog是Linux中的系统日志管理服务,通过守护进程syslogd来维护。该守护进程在启动时会读一个配置文件“/etc/syslog.conf”。该文件决定了不同种类的消息会发送向何处。例如,紧急消息可被送向系统管理员并在控制台上显示,而警告消息则可记录到一个文件中。该机制提供了3个syslog函数,分别为openlog、syslog和closelog。
下面是一个简单的守护进程实例InitServer
[bill@billstoneUnix_study]$catinitServer.c
1:#include<assert.h>
2:
3:#include<signal.h>
4:
5:#include<sys/wait.h>
6:
7:#include<sys/types.h>
8:
9:voidClearChild(intnSignal){
10:
11:pid_tpid;
12:
13:intnState;
14:
15://WNOHANG非阻塞调用waitpid,防止子进程成为僵死进程
16:
17:while((pid=waitpid(-1,&nState,WNOHANG))>0);
18:
19:signal(SIGCLD,ClearChild);//重新绑定SIGCLD信号
20:
21:}
22:
23:intInitServer(){
24:
25:pid_tpid;
26:
27:assert((pid=fork())>=0);//创建子进程
28:
29:if(pid!=0){//父进程退出,子进程被init托管
30:
31:sleep(1);
32:
33:exit(0);
34:
35:}
36:
37:assert(setsid()>=0);//子进程脱离终端
38:
39:umask(0);//清除文件创建掩码
40:
41:signal(SIGINT,SIG_IGN);//忽略SIGINT信号
42:
43:signal(SIGCLD,ClearChild);//处理SIGCLD信号,预防子进程僵死
44:
45:return0;
46:
47:}
48:
49:intmain()
50:
51:{
52:
53:InitServer();
54:
55:sleep(100);
56:
57:return0;
58:
59:}
[bill@billstoneUnix_study]$makeinitServer
ccinitServer.c-oinitServer
[bill@billstoneUnix_study]$./initServer
[bill@billstoneUnix_study]$ps-ef|grepinitServer
bill137211004:40?00:00:00./initServer//'?'代表initServer独立于终端
bill137251441004:41pts/000:00:00grepinitServer
程序在接收到SIGCLD信号后立即执行函数ClearChild,并调用非阻塞的waitpid函数结束子进程结束
信息,如果结束到子进程结束信息则释放该子进程占用的进程表资源,否则函数立刻返回.这样既保证了不增加守护进程负担,又成功地预防了僵死进程的产生.
自己编写的一个程序:
#cattest.c
1:#include<unistd.h>
2:#include<stdio.h>
3:#include<sys/types.h>
4:
5:intcal()
6:{
7:
8:inti=0,sum=0;
9:
10:for(i=0;i<=100;i++)
11:
12:{
13:
14:sum+=i;
15:
16:}
17:
18:returnsum;
19:
20:}
21:
22:int
23:
24:main()
25:
26:{
27:
28:intnum=1,status;
29:
30:int*s=#
31:
32:pid_tpid;
33:
34:if((pid=fork())==0)
35:
36:{
37:
38:*s=cal();
39:
40:printf("1+..+100=%d\n",*s);
41:
42:exit(0);
43:
44:}
45:
46:elseif(pid<0)
47:
48:{
49:
50:exit(0);
51:
52:}
53:
54://pid=wait(&status);
55:
56://if(status==0)
57:
58://{
59:
60:wait();
61:
62:printf("1+2+...+100=%d\n",*s);
63:
64://}
65:
66://else
67:
68://{
69:
70://printf("error!\n");
71:
72://}
73:
74:}
75:
76:[root@localhostchapter9]#./test
77:
78:1+..+100=5050
79:
80:1+2+...+100=1
81:
程序的本意是用子进程来执行函数,而fork子进程完全复制父进程数据空间,这样子进程会获得父进程的所有变量的拷贝,尽管父子进程变量名相同,但却存在了不同的地方,因此不能通过内存变量完成父子进程之间的信息传递。
相关文章推荐
- Linux中 终端、作业控制与守护进程
- Linux--终端、作业控制、与守护进程
- linux进程控制编程与守护进程编写实例
- Linux-终端、作业控制与守护进程
- Linux 124课程 8、管理本地的Linux用户和组、控制服务和守护进程
- Linux中 终端、作业控制与守护进程
- Linux下Service守护进程开发和Shell脚本控制Daemon
- Linux进程控制及守护进程
- 【Linux程序设计】之进程控制&守护进程
- 进程控制3 守护进程 华清远见-《嵌入式 Linux 应用程序开发标准教程》
- linux系统编程之进程(五):终端、作业控制与守护进程
- Linux进程控制编程之守护进程
- 在linux或者unix操作系统中在系统的引导的时候会开启很多服务,这些服务就叫做守护进程。守护进程是在后台运行不与任何控制终端关联,是Linux中的后台服务进程。它是一个生存期较长的进程,通常独立于
- linux下tcpwraper和xinetd超级守护进程访问控制详解
- 创建守护进程步骤与setsid() -- linux
- Linux守护进程
- 【Linux系统编程】进程的控制:结束进程、等待进程结束
- 嵌入式LinuxC编程之进程控制(一)
- 如何编写linux守护进程及详细实例
- RHCE——控制服务和守护进程