Linux信号处理相关内容:
1.pid_t fork( void); 创建子进程
pid_t 是一个宏定义,其实质是int 被定义在#include<sys/types.h>中
返回值: 若成功调用一次则返回两个值,子进程返回0,父进程返回子进程ID;否则,出错返回-1
一个现有进程可以调用fork函数创建一个新进程。由fork创建的新进程被称为子进程(child process)。fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
子进程是父进程的副本,它将获得父进程数据空间、堆、栈等资源的副本。注意,子进程持有的是上述存储空间的“副本”,这意味着父子进程间不共享这些存储空间(读时共享,写时复制)
fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。
当fork()完后,到底是先执行子进程还是先执行父进程,不同的系统会存在不同的先后。
代码示例:循环创建子进程
//进程id数组
pid_t pid[5]={0};
for(i=0;i<5;i++){
iRet = fork();
if(-1==iRet){
perror("fork error:");
//此处还可以将已将创建成功的子进程终止掉 kill(pid,SIGINT);
exit (-1);
}else if(0==iRet){
//fork()返回值为0时,表示子进程
//子进程直接退出循环,避免子进程再创建子进程
break;
}else if(iRet>0){
//父进程fork()后,返回子进程的ID
//保存子进程ID值,也可以再子进程中使用getpid()来获取
pid[i]=iRet;
continue;
}
if(5==i){ //当i=5时,则是父进程完成所有的子进程创建,并退出循环
sleep(5); //目的,让所有子进程退出后,父进程才退出,可以使用其他方法,如waitpid等;
printf("I'm parent,pid=%d\n", getpid());
exit (0);
}else{ //i值为[0-4],则对应的是第1-5个子进程
sleep(i+1); //对应的子进程sleep相应的i+1秒,可以使子进程顺序执行
printf("I'm %dth child,pid=%d\n", i+1, getpid() );
exit (0);
}
exit (0);
}
-
int pause(void); 让进程暂停直到信号出现
pause()会令当前的进程暂停(进入睡眠状态),直到被信号(signal)所中断
返回值: 只返回-1
程序涉及到pause处理流程:1.进程顺序执行,当遇到pause()时,进入休眠状态,不再向下执行,等到信号到来;2.当该进程收到信号(如:kill(pid,信号值)或命令行的信号等);3.该进程收到信号后,执行signal(信号值, 信号处理函数)定义的信号处理函数,进行处理,并成功返回;4.该进程的pause()才会返回,返回值为-1; -
int kill(pid_t pid,int signo);
功能:向进程或进程组发送一个信号(成功返回 0; 否则,返回 -1 )
参数:
pid>0:发送给进程号为pid的进程
pid=0:发送给当前进程所属进程组里的所有进程
pid=-1:发送给除1号进程和自身以外的所有进程
pid<-1:发送给属于进程组-pid的所有进程
signo:发送的信号值
signo = 0:不发送信号,可用于检查目标进程是否存在,以及当前进程是否具有向目标进程发送信号的权限(root权限的进程可以向任何进程发送信号,非root权限的进程只能向属于同一个session或者同一个用户的进程发送信号)。
信号值可以使用命令: kill -l 查看 (对应的是信号宏名和值) (见文末)
kill函数可以对进程发送signal();发送的信号,可以使用signal来进行捕捉。(除SIGKILL和SIGSTOP信号不能捕捉,也不能忽略)
在linux里使用的Kill命令。实际上是对Kill()函数的一个包装
如: kill( 1234, SIGKILL)函数等同于 kill -9 1234; 将1234进程杀掉
4.sig_t signal(int signum,sig_t handler); 设置某一信号的对应动作
参数:
1.signum指明了所要处理的信号类型,它可以取除了SIGKILL和SIGSTOP外的任何一种信号(原因该两种信号不能进行捕捉和忽略操作)。
2.handler描述了与信号关联的动作,它可以取以下三种值:
SIG_IGN:忽略信号,即使没有意义,代码执行仍将继续。
SIG_DFL:信号由该特定信号的默认动作处理。
无返回值的函数地址:此函数必须在signal()被调用前申明,handler中为这个函数的名字。当接收到一个类型为signum的信号时,就执行handler 所指定的函数。
这个函数应有如下形式的定义void func(int sig); 返回值为void 型 且只有一个int型的信号值参数
函数说明:
signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。当一个信号的信号处理函数执行时,如果进程又接收到了该信号,该信号会自动被储存而不会中断信号处理函数的执行,直到信号处理函数执行完毕再重新调用相应的处理函数。但是如果在信号处理函数执行时进程收到了其它类型的信号,该函数的执行就会被中断。
//示例代码,顺便说明回调函数和注册函数
//自定义信号处理函数:void func (int);
//该函数是回调函数的定义
void sig_deal( int signo ){
//进行对应信号的处理
printf( "hello! signo=%d\n", signo );
return;
}
//主程序中调用信号处理
//当该进程收到SIGINT信号时,调用sig_deal信号处理函数进行处理,且sig_deal函数自动传入SIGINT作为参数
signal( SIGINT, sig_deal ); //signal()函数叫注册函数;sig_deal叫回调函数
附一段小代码,执行理解该过程:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
//自定义信号处理函数(信号处理回调函数规定:返回void 型,参数只有一个,且是int型的信号值)
void sign_deal( int signo ){
//仅仅作为接收信号,然后信号处理函数通过,然后pause()才返回
printf( "signo = %d, sign_deal success\n", signo );
return ;
}
int main(){
int i = 0;
int j = 0;
int iRet = 0;
pid_t cpid[5];
for( i =0; i<5; i++ ){
cpid[i]=0;
}
i = 0;
printf( "test for loop \n" );
for( j = 0; j < i; j++ ){
//第一次:先执行j=0;再判断j<i;然后条件成功再执行循环体内容
printf( "i=%d;j=%d", i, j );
}
printf( "test for loop end\n" );
signal( SIGUSR1, sign_deal );
//循环创建5个子进程
for( i = 0; i < 5; i++ ){
iRet = fork();
if( iRet < 0 ){
perror( "fork error:" );
for( j = 0; j < i; j++ ){ //结束已经创建的子进程
kill( cpid[j], SIGINT );
}
exit (-1);
}else if( iRet == 0 ){ //子进程返回
break;
}
//父进程返回 > 0
cpid[i] = iRet;
continue;
}
/*先将子进程暂停pause(),然后父进程发送信号给子进程kill(),使其继续运行*/
if( i == 5 ){
//父进程创建5个子进程后,循环退出
//1.先让子进程执行,然后子进程pause()暂停,等待信号
sleep( 6 );
printf( "I am parent,pid = %d\n", getpid() );
//2.给子进程发送SIGUSR1信号,让子进程调用信号处理函数,并完成pause的返回
for( i = 0; i < 5; i++ ){
sleep( 1 );
kill( cpid[i], SIGUSR1 );
}
exit ( 0 );
}
//此处分开区分子进程,可以每个子进程执行各自的操作
if( 0 == i ){ //子进程fork()==0,则break循环
sleep(i+1);
printf( "I am %d child,pid = %d\n", i+1, getpid() );
pause();
printf( "%dth child, pause after\n", i+1 );
exit( 0 );
} else if( 1 == i ){
sleep(i+1);
printf( "I am %d child,pid = %d\n", i+1, getpid() );
pause();
printf( "%dth child, pause after\n", i+1 );
exit( 0 );
} else if( 2 == i ){
sleep(i+1);
printf( "I am %d child,pid = %d\n", i+1, getpid() );
pause();
printf( "%dth child, pause after\n", i+1 );
exit( 0 );
} else if( 3 == i ){
sleep(i+1);
printf( "I am %d child,pid = %d\n", i+1, getpid() );
pause();
printf( "%dth child, pause after\n", i+1 );
exit( 0 );
} else if( 4 == i ){
sleep(i+1);
printf( "I am %d child,pid = %d\n", i+1, getpid() );
pause();
printf( "%dth child, pause after\n", i+1 );
exit( 0 );
}
exit (0);
}
PS:
Linux系统中常用信号:
(1)SIGHUP:用户从终端注销,所有已启动进程都将收到该进程。系统缺省状态下对该信号的处理是终止进程。
(2)SIGINT:程序终止信号。程序运行过程中,按Ctrl+C键将产生该信号。
(3)SIGQUIT:程序退出信号。程序运行过程中,按Ctrl+\键将产生该信号。
(4)SIGBUS和SIGSEGV:进程访问非法地址。
(5)SIGFPE:运算中出现致命错误,如除零操作、数据溢出等。
(6)SIGKILL:用户终止进程执行信号。shell下执行kill -9发送该信号。
(7)SIGTERM:结束进程信号。shell下执行kill 进程pid发送该信号。
(8)SIGALRM:定时器信号。
(9)SIGCLD:子进程退出信号。如果其父进程没有忽略该信号也没有处理该信号,则子进程退出后将形成僵尸进程。
(10) USR1: 被用来告知应用程序重载配置文件
剩余的信号值说明:Linux signal 信号列表
=============================
后续补父进程等待子进程相关函数内容和signal和sigset的区别