信号部分知识总结

信号

信号描述

信号的共性:

  1. 简单
  2. 不能携带大量数据
  3. 满足某一特定条件才能发送

信号的本质

  • 信号软件层面上的”中断”,一旦信号产生,无论程序执行到哪里,都立即停止,处理信号,处理完成,在继续后续指令。
  • 所有的信号,产生、处理都是由内核完成。
  • 信号的实现手段导致,信号有很强的延时。对用户而言,依然感觉不到。
信号相关的概念
  • 未决

    • 产生与递达(处理)之间的状态。主要受阻塞(屏蔽)影响
  • 递达

    • 内核产生信号后提送并且送达进程。递达的信号会被内核立即处理。
  • 信号处理方式

    1. 执行默认动作
    2. 忽略(丢弃)。
    3. 捕捉(调用用户指定函数)
  • 阻塞信号集(信号屏蔽字):
    本质:位图,用来记录信号的屏蔽状态

    • 该信号集中的信号,表示成功被设置屏蔽,再次收到该信号,其处理动作将延迟后解除屏蔽。此期间该信号一直处于未决态。
  • 未决信号集

    • 本质:位图,用来记录信号的处理状态。
    • 该信号集中信号,表示信号已经产生,但尚未被处理。
信号4要素
  • 信号使用前,必须先确定四要素,在使用。
  • 四要素内容:
    1. 编号
    2. 名称
    3. 事件
    4. 默认处理动作
  • 使用命令kill -l 查看Linux系统中支持的所有信号。
  • sigkill 和 sigstop信号不允许忽略和捕捉,只能执行默认动作。甚至不能设置为阻塞。
信号的产生
  1. 按键的产生
    • Ctrl +c ->2 sigint(终止/中断)
    • Ctrl + \ -> 3sigquit(退出))
  2. 系统调用产生
    1. alarm()-> sigalarm
    2. abort()
    3. raise()
  3. 软件条件产生
    • alarm()->sigalarm
    • setitimer()->sigalarm
  4. 硬件异常产生信号
    • 段错误:内存访问异常-> sigsegv
    • 浮点数除外:除0 -> sigfpe
    • 总线错误。内存对齐出错。->sigbus.
  5. 命令产生
    • kill命令

kill函数、命令产生信号

#include 
int kill(pid_t pid,int sig); //发送信号给一个指定的进程。
	pid: >0 发送信号给指定进程
		=0 发送信号给调用kill函数的那个进程,处于同一进程组的进程
		<1: 取绝对值,当作进组id,发送信号给该进程组的所有组员。kill -SIGKILL -9527
		-1:发送信号给有权限发送的所有进程
	sig:信号编号
返回值:
	成功:0
	失败:-1,errno

案例

#include
#include
#include
#include
#include
#include
#include
#include 
#include
void sys_err(const char*str)
{
    perror(str);
    exit(1);
}

int main(int argc,char*argv[])
{
    pid_t pid = fork();
    if(pid == 0)
    {
        sleep(2);
        kill(getppid(),SIGKILL);
    }
    else if(pid > 0)
    {
        while(1)
        {
            printf("I'm a parent,pid = %d\n",getpid());
            usleep(10000);
        }
    }
    return 0;
}

alarm函数产生信号

  • 一个进程有且仅有一个唯一的闹钟
unsigned int alarm(unsigned int seconds); // 设置定时,发送SIGALARM信号
seconds:定时的秒数.
返回值:
    上次定时剩余实现
    不会出错
alarm(0):取消闹钟
  • 统计当前使用的计算机,1s最多能数多少数。
#include
#include
#include
#include
#include
#include
#include
#include 
#include
void sys_err(const char*str)
{
    perror(str);
    exit(1);
}

int main(int argc,char*argv[])
{
    // 设置1s的定时器
    alarm(1);
    int i = 0;
    for(i = 0;;i++)
    {
        printf("i = %d\n",i);
    }
    return 0;
}
  • 使用time命令查看程序执行消耗的时间
  • 实际时间 = 用户时间 + 内核时间 + 等待时间
  • time ./alarm > out ----程序优化的瓶经在IO
setitimer函数
#include int setitimer(int which,  const struct itimerval *new_value, struct itimerval *old_value);
功能:
    设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。
参数:
    which:指定定时方式
        a) 自然定时:ITIMER_REAL → 14)SIGALRM计算自然时间
        b) 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  只计算进程占用cpu的时间
        c) 运行时计时(用户 + 内核):ITIMER_PROF → 27)SIGPROF计算占用cpu及执行系统调用的时间
    new_value:struct itimerval, 负责设定timeout时间
        struct itimerval {
            struct timerval it_interval; // 闹钟触发周期
            struct timerval it_value;    // 闹钟触发时间
        };
        struct timeval {
            long tv_sec;            // 秒
            long tv_usec;           // 微秒
        }
        itimerval.it_value: 设定第一次执行function所延迟的秒数 
        itimerval.it_interval:  设定以后每几秒执行function
​
    old_value: 存放旧的timeout值,一般指定为NULL
返回值:
    成功:0
    失败:-1
void myfunc(int sig)
{
    printf("hello\n");
}int main()
{
    struct itimerval new_value;//定时周期
    new_value.it_interval.tv_sec = 1;
    new_value.it_interval.tv_usec = 0;//第一次触发的时间
    new_value.it_value.tv_sec = 2;
    new_value.it_value.tv_usec = 0;signal(SIGALRM, myfunc); //信号处理
    setitimer(ITIMER_REAL, &new_value, NULL); //定时器设置while (1);return 0;
}
信号机操作函数
操作自定义的操作函数
#include   sigset_t set ; 自定义信号集 //typedef  unsigned long sigset_t 
int sigemptyset(sigset_t *set);    //将set集合置空
int sigfillset(sigset_t *set)//将所有信号加入set集合
int sigaddset(sigset_t *set, int signo);  //将signo信号加入到set集合
int sigdelset(sigset_t *set, int signo);   //从set集合中移除signo信号0 -1
int sigismember(const sigset_t *set, int signo); //判断信号是否存在1 0
操作信号屏蔽字mask的信号集操作函数
  • 设置屏蔽信号、接触屏蔽。
#include int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
功能:
    检查或修改信号阻塞集,根据 how 指定的方法对进程的阻塞集合进行修改,新的信号阻塞集由 set 指定,而原先的信号阻塞集合由 oldset 保存。
​
参数:
    how : 信号阻塞集合的修改方法,有 3 种情况:
        SIG_BLOCK:向信号阻塞集合中添加 set 信号集,新的信号掩码是set和旧信号掩码的并集。相当于 mask = mask|set。
        SIG_UNBLOCK:从信号阻塞集合中删除 set 信号集,从当前信号掩码中去除 set 中的信号。相当于 mask = mask & ~ set。
        SIG_SETMASK:将信号阻塞集合设为 set 信号集,相当于原来信号阻塞集的内容清空,然后按照 set 中的信号重新设置信号阻塞集。相当于mask = set。
    set : 要操作的信号集地址。
        若 set 为 NULL,则不改变信号阻塞集合,函数只把当前信号阻塞集合保存到 oldset 中。
    oldset : 保存原先信号阻塞集地址
​
返回值:
    成功:0,
    失败:-1,失败时错误代码只可能是 EINVAL,表示参数 how 不合法。
 
  • 查看 未决集合函数sigpending
#include
#include
#include
#include
#include
#include
#include
#include
#include

void sys_err(const char*str)
{
    perror(str);
    exit(1);
}

// 创建函数,打印未决的每一个二进制位
void print_pedset(sigset_t*set)
{
    int i = 0;
    for (i=1;i<32;i++)
    {
        if(sigismember(set,i))
        {
            putchar('1');
        }
        else
        {
            putchar('0');
        }
    }
    printf("\n");

}
int main(int argc,char*argv[])
{
    //自定义信号集
    sigset_t set, oldset,pedset;
    // 清空自定义信号集合
    sigemptyset(&set);
    //将2号信号添加到自定义信号集
    sigaddset(&set, SIGINT);

    // 借助自定义信号,设置pcb中的信号屏蔽字中的2号信号为屏蔽、
    int ret = sigprocmask(SIG_BLOCK,&set, &oldset);
    if(ret == -1)
        sys_err("sigprocmask error");

    while(1)
    {
       
        // 获取当前的未决信号集
        ret = sigpending(&pedset);
        if(ret == -1)
            sys_err("sigpending error");

        // 打印未决信号集
        print_pedset(&pedset);
         sleep(1);
    }
    return 0;
}

信号捕捉
signal函数
#include 

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

参1:待捕捉的信号编号
参2; 一旦捕捉到该信号,执行的回调函数
#include
#include
#include
#include
#include
#include
#include
#include
#include

void sys_err(const char*str)
{
    perror(str);
    exit(1);
}

void sig_catch(int signum)
{
    if(signum == SIGINT)
    printf("catch you!! %d\n",signum);
    else if(signum == SIGQUIT)
    {
        printf("哈哈,%d,你被我抓住了\n",signum);
    }
    return ;
}

int main(int argc,char*argv[])
{
    // 注册信号捕捉函数
    signal(SIGINT,sig_catch);
    signal(SIGQUIT,sig_catch);

    while(1); //模拟当前进程还有很多代码药执行
    return 0;
}
sigaction函数
#include int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
功能:
    检查或修改指定信号的设置(或同时执行这两种操作)。
​
参数:
    signum:要操作的信号。
    act:   要设置的对信号的新处理方式(传入参数)。
    oldact:原来对信号的处理方式(传出参数)。
​
    如果 act 指针非空,则要改变指定信号的处理方式(设置),如果 oldact 指针非空,则系统将此前指定信号的处理方式存入 oldact。
​
返回值:
    成功:0
    失败:-1
struct sigaction结构体:
struct sigaction {
    void(*sa_handler)(int); //旧的信号处理函数指针
    void(*sa_sigaction)(int, siginfo_t *, void *); //新的信号处理函数指针
    sigset_t   sa_mask;      //信号阻塞集
    int        sa_flags;     //信号处理的方式
    void(*sa_restorer)(void); //已弃用
};
  1. sa_handler、sa_sigaction:信号处理函数指针,和 signal() 里的函数指针用法一样,应根据情况给sa_sigaction、sa_handler 两者之一赋值,其取值如下:

a) SIG_IGN:忽略该信号

b) SIG_DFL:执行系统默认动作

c) 处理函数名:自定义信号处理函数

  1. sa_mask:信号阻塞集,在信号处理函数执行过程中,临时屏蔽指定的信号。
  2. sa_flags:用于指定信号处理的行为,通常设置为0,表使用默认属性。它可以是一下值的“按位或”组合:

Ø SA_RESTART:使被信号打断的系统调用自动重新发起(已经废弃)

Ø SA_NOCLDSTOP:使父进程在它的子进程暂停或继续运行时不会收到 SIGCHLD 信号。

Ø SA_NOCLDWAIT:使父进程在它的子进程退出时不会收到 SIGCHLD 信号,这时子进程如果退出也不会成为僵尸进程。

Ø SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

Ø SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

Ø SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数。

  • 捕捉信号测试用例
#include
#include
#include
#include
#include
#include
#include
#include
#include

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

void sig_catch(int signum)
{
    if(signum == SIGINT)
        printf("catch you!! %d\n", signum);
    else if(signum == SIGQUIT)
    {
        printf("哈哈, %d, 你被我抓住了\n", signum);
    }
    return;
}

int main(int argc, char* argv[])
{
    struct sigaction act, oldact;
    act.sa_handler = sig_catch;
    sigemptyset(&(act.sa_mask));
    sigaddset(&act.sa_mask,SIGQUIT);
    act.sa_flags = 0; // 本信号自动屏蔽
    // 注册信号捕捉函数
    int ret = sigaction(SIGINT, &act, &oldact);
    if(ret == -1)
    {
        sys_err("sigaction error");
    }
    
    while(1) {
        // Add some code here if needed
    }

    return 0;
}

  1. 捕捉函数执行期间,信号屏蔽字,由原来的pcb中的mask该换为sa_mask,捕捉函数执行结束,恢复回mask。
  2. 期间,本信号自动被屏蔽(sa_flags = 0)
  3. 期间,被屏蔽的信号,多次发送,解除后只处理一次。
借助信号捕捉,完成子进程回收
sigchld 产生条件
  • 子进程运行状态发送变化,就会给父进程发送SIGCHLD
回收子进程代码实现
#include
#include
#include
#include
#include
#include
#include
#include
#include

void sys_err(const char* str)
{
    perror(str);
    exit(1);
}

void catch_child(int signum)
{
    pid_t wpid;
    int status;
    // if((wpid = wait(NULL))!=-1)//会产生僵尸进程
    // {
    //     printf("catch child pid = %d\n",wpid);
    // }
    while((wpid = waitpid(-1,&status,0))!=-1)
    {
        
        if(WIFEXITED(status))
        {
            printf("catch child wpid = %d\n",wpid);
        }
    }
}

int main(int argc,char*argv[])
{
    int i;
    pid_t pid ;
    for(i = 0;i<5;i++)
    {
        pid=fork();
        if(pid==0)
        {
            break;
        }
    }
    if( 5== i)
    {
        struct sigaction act;
        act.sa_handler = catch_child;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        sigaction(SIGCHLD,&act,NULL);
        printf("I'm a parent,pid = %d\n",getpid());
        while(1);
    }
    else
    {
        printf("I'm a child,pid = %d\n",getpid());
    }
    return 0;
}

你可能感兴趣的:(c++,c语言,visual,studio,code,visual,studio)