Fork me on GitHub

Linux信号处理——发送信号

Linux提供了应用编程接口,通过这些接口,进程可以向其他进程或进程组发送信号。root权限的进程可以向任何进程发送信号,非root权限的进程智能向属于同一个回话或同一个用户的进程发送信号。

发送信号

常用的函数原型如下

/*
向进程发送信号
    pid>0 进程ID为pid的进程
    pid=0 同一进程组的进程
    pid<0 && pid!=-1 进程组ID为-pid的所有进程
    pid=-1 除发送给进程自身外,还发送给所有进程ID>1的进程
成功返回0,否则-1
*/
int kill(pid_t pid,int signo);
/*向进程本身发送信号,等价于kill(getpid(),sig),成功返回0,否则-1*/
int raise(int signo);
/*向进程发送SIGABORT信号,默认情况下进程会退出*/
void abort(void);
/*
向进程发送实时信号
    pid 接收信号的进程ID,只能向一个进程发送信号
    sig 指定即将发送的信号
    val指定信号传递的参数
成功返回0,否则-1
*/
int sigqueue(pid_t pid,int sig,const union sigval val);

union sigval
{
    int sival_int;      //传送一个整形数
    void *sival_ptr;    //传送任何数据结构的指针
};

typedef struct {
    int si_signo;
    int si_code;
    union sigval si_value;
    int si_errno;
    pid_t si_pid;
    uid_t si_uid;
    void *si_addr;
    int si_status;
    int si_band;
} siginfo_t;

案例:使用sigqueue发送带参数的信号

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void SigHandler(int signo,siginfo_t *info,void *context)
{
    printf("%s\n",(char *)info->si_value.sival_ptr);
}

int main()
{
    struct sigaction sigAct;
    sigval_t val;
    char *pMsg = "i still believe";

    sigAct.sa_flags = SA_SIGINFO;
    sigAct.sa_sigaction=SigHandler;
    if(sigaction(SIGUSR1,&sigAct,NULL)==-1)
    {   
        printf("fail set sig_handler");
        return 1;
    }
    val.sival_ptr=pMsg;
    if(sigqueue(getpid(),SIGUSR1,val)==-1)
    {
        printf("fail send sigqueue");
        return 2;
    }
    sleep(3);
}

sleep睡眠延时

可以使用sleep函数将程序延迟一段时间后继续执行,其实现机制是:

  1. 调用alarm函数设置延迟时间
  2. 调用pause函数挂起进程,等待系统发送SIGALARM信号,当SIGALARM信号到达进程时,进程被唤醒。
/*
设置时间闹钟
    seconds表示闹钟间隔时间,原有闹钟无效
若调用alarm函数前,进程已经设置了闹钟,则返回上一个闹钟剩余时间,否则返回0
*/
unsigned int alarm(unsigned int seconds);
//等待信号,进程收到信号后,执行信号处理函数,pause函数返回,原进程继续执行
void pause();

案例:实现sleep函数

#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>

void alarmhandler(int signum)
{   
    printf("Alarm received from kernel\n");
}

int mysleep(unsigned int time)
{
    printf("about to sleep for %d seconds\n",time);
    signal(SIGALRM,alarmhandler);
    alarm(time);
    pause();
    printf("continue from alarm \n");
    return 0;
}

int main(int argc,char *argv[])
{
    printf("start run the program.\n");
    unsigned int time = atoi(argv[1]);
    mysleep(time);
    printf("i am awake,haha\n");
    return 0;
}

间隔计时器

alarm函数计时单位是秒,当延迟时间到来,只能触发一次。不能满足需要高精度时间、有周期性定时需求的需求。为此,引入间隔计时器,其原理是:

当等待时间来到,内核向处于等待状态的进程发送信号,同时,再次设置时间间隔。间隔计时器属于面向进程的计时器。

进程运行时间

通常,LInux系统最小时钟间隔是10ms,意味着每秒产生100个时钟中断。进程以时间片的形式分享CPU,进程的执行有两种模式:用户态和内核态。当进程执行的是用户地址空间的代码,称进程运行在用户态;当进程进入系统调用或硬件中断,称进程运行在内核态。此外,进程还有休眠态,即将CPU交给其他进程。所以进程并非时刻都在运行,而是在用户态、内核态、休眠态之间切换。

由此内核提供三种计时器:

  • 真实时间 用户态+内核态+休眠态时间
  • 虚拟时间 用户态时间
  • 实用时间 用户态+内核态时间
/*
获得当前进程中指定类型间隔计时器的值
which 计时器类型
    ITIMER_REAL     真实时间,经过指定时间,内核发送SIGALRM限号
    ITIMER_VIRTUAL  用户态时间,经过指定时间,内核发送SIGVTALRM信号
    ITIMER_PROF     实用时间,经过指定时间,内核发送SIGPRT信号
value 存储获得的间隔计时器的值
*/
int getitimer(int which,struct itmerval *value);

struct itimerval
{
    struct timeval it_interval; //下一个值
    struct timeval it_value     //当前值
};

struct timeval
{
    long tv_sec;    //秒
    long tv_usec;   //微秒
};
/*
设置间隔计时器
which 指定定时器类型
newval指向被设置值
oldval指向被替换设置值
成功返回0,否则-1
若oldval不为NULL,之前计时器的值将被复制到oldval
*/
int setitimer(int which,const struct itimerval *newval,struct itimerval *oldval);

Comments