《Linux C编程实战》笔记:信号的发送

信号的发送主要由函数kill、raise、sigqueue、alarm、setitimer以及abort来完成

kill函数

kill函数用来发送信号给指定的进程。

#include
#include
int kill(pid_t pid,int sig);

该函数的行为与第一个参数pid有关,第二个参数sig表示信号编号。

  • 如果pid是正数,则发送信号sig给进程号为pid的进程
  • 如果pid为0,则发送信号sig给当前进程所属的进程组里的所有进程
  • 如果pid为-1,则把信号sig广播至系统除1号进程(init进程)和自身以外的所有进程
  • 如果pid是比-1还小的负数,则发送信号sig给属于进程组-pid(也就是pid的绝对值) 的所有进程
  • 如果参数sig是0,则kill仍执行正常的错误检查,但不发送信号。可以利用这一点来确定某新城是否有权向另外一个进程发送信号。如果向一个并不存在的进程发送空信号,则kill返回-1,errno被设置为ESRCH

函数执行成功返回0,当有错误发生时返回-1,错误代码存入errno。

注意:只有具有root权限的进程才能向其他任意进程发送信号,非root权限的进程只能向属于同一个组或同一个用户的进程发送信号。

示例程序1

该示例程序实现了自己的kill命令。在shell中kill命令也是用来发送信号的,我们用代码仿照shell的kill命令,但是不支持-l选项(显示信号编号)

//本程序只支持按信号的编号发送信号
#include
#include
#include
#include
#include
int main(int argc,char **argv){
    int i,j;
    int signum=SIGTERM;//默认发送SIGTERM
    pid_t pid;
    //首先检查参数,要么是只有一个参数,也就是pid,要么是三个参数,也就是 -s signum pid
    if(argc!=2&&argc!=4){
        printf("Usage:./my_kill <-s signum>[PID]\n");
        exit(0);
    }
    for(i=1;i

运行的方式:./my_kill -s 2(这是signum) 2568(这是pid)

这个例子可以配合之前几节写的信号接收的程序使用来测试

raise函数

raise函数是ANSI C而非POSIX标准定义的,用来给调用它的进程发送信号。

#include
int raise(int sig);

示例程序2

这里用raise函数写一个自发自收的信号

#include
#include
#include
#include
#include
void process(int signum){
    printf("recv!\n");
}
int main(int argc,char **argv){
    signal(SIGINT,process);
    raise(SIGINT);
    return 0;
}

这都很简单,就不解释了。

sigqueue函数

这个函数支持信号带有参数,从而可以与函数sigaction配合使用

#include
int sigqueue(pid_t pid,int sig,const union sigval value);

sigqueue的另一个与kill的不同点是它不能给一组进程发送信号

参数value是一个union共用体,定义如下

union sigval{
    int sival_int;
    void *sival_ptr;
};

union的特点就是只能是其中一个。也就是说信号携带的要么是一个整型值,要么是一个void型指针。当接收进程的信号处理函数是由sigaction设置的并且设置了SA_SIGINFO标准,接收进程可以从siginfo_t结构的si_value域取得信号发送时携带的参数

成功执行返回0,发生错误返回-1,错误码存到errno里。

程序3

这个函数书上竟然没有给示例,我尝试自己写一个demo来测试一下。

首先是接收方的程序,设置一下sigaction

#include
#include
#include
#include
#include
#include
void receive_process(int signum,siginfo_t *info_ptr,void *unused_ptr){
    printf("recv!\n");
    printf("data is:%d\n",info_ptr->si_value.sival_int);
}
int main(int argc,char **argv){
    printf("my pid:%d\n",getpid());
    struct sigaction act;
    act.sa_flags=SA_SIGINFO;
    act.sa_sigaction=receive_process;
    sigaction(SIGINT,&act,nullptr);
    while(1);
    return 0;
}

然后是发送方的程序

#include
#include
#include
#include
#include
int main(int argc,char **argv){
    pid_t pid=atoi(argv[1]);
    int data=atoi(argv[2]);
    union sigval value;
    value.sival_int=data;
    sigqueue(pid,SIGINT,value);
    return 0;
}

运行结果如图

《Linux C编程实战》笔记:信号的发送_第1张图片

完美的发送和接收,证明讲的确实没问题。

我在上一节里写的si_value的类型是sigval_t,我去源码里翻了下, sigval_t和union sigval是一样的,只是重新命名了。 

sigaction不会写的可以看我之前写的文章《Linux C编程实战》笔记:信号的捕捉和处理-CSDN博客

alarm函数

alarm函数可以用来设置定时器,定时器超时将产生SIGALRM信号给调用进程。

#include
unsigned int alarm(unsigned int seconds);

经过seconds秒后,内核将给调用该函数的进程发送SIGALRM信号,如果seconds为0,则不再发送SIGALRM信号。最新一次调用alarm函数将取消之前一次的设定。

注意:alarm只设定为发送一次信号,如果要多次发送,就要对alarm进行多次调用。

示例程序4

该示例程序模拟网络命令ping的功能。

其实我也不知道ping是干啥的...反正跟着敲吧。

#include
#include
#include
#include
#include
void send_ip(){
    printf("send a icmp echo request packet\n");
}
void recv_ip(){
    while(1);
}
void handler_sigalarm(int signo){
    send_ip();
    alarm(2);
}
int main(int argc,char **argv){
    signal(SIGALRM,handler_sigalarm);
    raise(SIGALRM);//触发一个SIGALRM信号给本进程
    recv_ip();
    return 0;
}

其实就是一直在发这段话罢了。整个程序也很好懂,就不多讲了。

getitimer/setitimer函数

与alarm函数一样,setitimer函数也是用来设置定时器的,且alarm和setitimer使用的是同一个定时器,因此会相互影响。setitimer要比alarm具有更多的功能。

#include
int getitimer(int which,struct itimerval *value);
int setitimer(int which,const struct itimerval *value,struct itimerval *ovalue);

第一个参数which用来指定使用哪一个定时器,根据参数which可单独设定每个定时器,定时器的种类如下:

《Linux C编程实战》笔记:信号的发送_第2张图片

 参数value用来指定定时器的时间,结构struct itimerval的定义如下:

struct itimerval{
    struct timeval it_interval;
    struct timeval it_value;
}
//解释一下setitimer里这个结构体的使用,首先定时器时间是it_value,这个计完后会发一个信号,之后
//it_value会重新变为it_interval,并且之后一直以it_interval为间隔循环计时,计到0也会发送信号
struct timeval{
    long tv_sec;//秒数
    long tv_usec;//微秒
}

对于函数getitimer,如果存在由which指定的定时器,则将剩余时间保存在it_value中,该定时器的初始值保存在it_interval中;如果不存在指定类型的定时器,则将value置为0返回。执行成功返回0,当有错误发生时则返回-1,错误代码存入errno中。

示例程序5

#include
#include
#include
#include
#include
//信号处理程序
void handler_sigtimer(int signo){
    switch (signo)
    {
    case SIGALRM:
        printf("recv SIGALRM\n");
        break;
    case SIGPROF:
        printf("recv SIGPROF\n");
        break;
    default:
        break;
    }
}
int main(int argc,char **argv){
    struct itimerval value;
    //安装信号处理函数
    signal(SIGALRM,handler_sigtimer);
    signal(SIGPROF,handler_sigtimer);
    //初始化value结构
    value.it_value.tv_sec=1;    //第一次1秒触发信号
    value.it_value.tv_usec=0;
    value.it_interval.tv_sec=5;    //第二次之后都是5秒触发信号
    value.it_interval.tv_usec=0;
    //设置定时器
    setitimer(ITIMER_REAL,&value,nullptr);
    setitimer(ITIMER_PROF,&value,nullptr);
    while(1);
    return 0;
}

程序设置了两个定时器ITIMER_REAL和ITIMER_PROF。系统经过1秒后,将触发一个SIGALRM信号,以后每5秒触发一个SIGALRM信号。按照程序执行时消耗的时间以及内核因本程序消耗的时间来计时。第一次经过一秒后将触发一个SIGPROF信号,以后每5秒触发一个SIGPROF信号。

《Linux C编程实战》笔记:信号的发送_第3张图片

abort函数

#include
void abort(void);

如果进程设置了信号处理函数以捕获SIGABRT信号,且信号处理函数不返回(如使用longjmp),则abort()不能终止进程。abort()终止进程时,所有打开的流(如i/o流、文件流)均会被刷新和关闭。如果进程设置了SIGABRT被阻塞或忽略,abort()将覆盖这种设置。

你可能感兴趣的:(linux,c语言,笔记)