系统调用
系统调用中发送信号常用的函数有kill()、raise()、abort()等,其中kill是最常用的函数,该函数的作用是给指定进程发送信号,但是否杀死进程,取决于所发送信号的默认动作。kill()存在于函数库signal.h中,其函数声明如下:
int kill(pid_t pid, int sig);
若函数调用成功,则返回0;否则返回-1,并设置errno。kill()函数有两个参数:pid表示接收信号的进程的pid,sig表示要发送的信号的编号。参数pid的不同取值会影响kill()函数作用的进程,其取值可分为4种情况,每种取值代表的含义如下:
● 若pid > 0,则发送信号sig给进程号为pid的进程;
● 若pid = 0,则发送信号sig给当前进程所属组中的所有进程;
● 若pid = -1,则发送信号sig给除1号进程与当前进程的所有进程;
● 若pid < -1,则发送信号sig给属于进程组-pid的所有进程。
kill()函数发送信号的对象范围取决于调用kill()函数进程的权限,只有root用户有权发送信号给任一进程,普通用户进程只能向属于同一进程组或同一用户的进程发送信号。参数sig的取值一般为常规信号的编号,当其设置为特殊值0时,kill()函数不发送信号,但会进行错误检查,此时可以根据kill()函数的返回值来判断用户进程是否有权限向另外一个进程发送信号:
● 若返回值为0,表示kill()函数成功调用,当前进程有权限;
● 若返回值为-1,且errno为ESCRCH,表明指定接收信号的进程不存在;否则表示当前进程没有权限。
不同操作系统中信号编号对应的信号名不一定相同,为了提高代码可读性和可移植性,用户在使用kill()函数时应尽量使用系统中定义的宏进行传参。
案例1:使用fork()函数创建一个子进程,在子进程中使用kill()发送信号,杀死父进程。
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <signal.h>
5 int main()
6 {
7 pid_t pid;
8 pid=fork();
9 if(pid==0){ //子进程
10 sleep(1);
11 printf("child pid=%d,ppid=%d\n",getpid(),getppid());
12 kill(getppid(),SIGKILL);//发送信号SIGKILL给父进程
13 }
14 else if(pid>0){ //父进程
15 while(1){
16 printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
17 }
18 }
19 return 0;
20 }
为了保证父进程能接收到子进程发送的信号,在父进程执行的代码段中添加循环,保持父进程的运行;子进程的代码段中调用了kill()函数发送SIGKILL信号给父进程,在此之前使子进程先沉睡1秒。编译案例,执行程序,执行结果如下:
……
parent pid=2873,ppid=2672
child pid=2874,ppid=2873
Killed
当终端输出“Killed”时,表明子进程发送的信号SIGKILL成功杀死了父进程。
多学一招:raise()/abort()
除kill()外,raise()、abort()和pause也是常用的系统调用。raise()函数的功能是发送指定信号给当前进程自身,该函数存在于函数库signal.h中,其函数声明如下:
int raise(int sig);
若raise()函数调用成功,则返回0;否则返回非0。其参数sig为要发送信号的编号,使用kill()函数可以实现与该函数相同的功能,该函数与kill()之间的关系如下:
raise(sig==kill(getpid(),sig)
abort()函数的功能是给当前进程发送异常终止信号SIGABRT,终止当前进程,并生成core文件,该函数存在于函数库stdlib.h中,其函数声明如下:
void abort(void);
该函数在调用之时会先解除阻塞信号SIGABRT,然后才发送信号给自己。它不会返回任何值,可以视为百分百调用成功。
pause()函数的作用是造成进程主动挂起,等待信号唤醒。调用该函数后进程将主动放弃cpu,进入阻塞状态,直到有信号递达将其唤醒,才继续工作。pasue()存在于函数库unistd.h中,其函数声明如下:
int pause(void);
pasue()函数的参数列表为空,不一定有返回值。根据唤醒进程信号不同的默认动作,pause()函数可能有以下几种情况:
(1) 若信号的默认处理动作是终止进程,则进程终止,pause()函数没有机会返回;
(2) 若信号的默认处理动作是忽略,进程继续处于挂起状态,pause()函数不返回;
(3) 若信号的处理动作是捕捉,则调用玩信号处理函数后,pause返回-1,并将errno设置为EINTR,表示“被信号中断”;
由以上情况可知,pause()只有错误返回值。另外,需要注意的是,若信号被屏蔽,被pasue()函数挂起的进程无法被其唤醒。