Linux系统编程---信号(一)

一、信号的概念

1.信号的概念和机制

信号共性:

        简单、不能携带大量信息、满足条件才发送。

信号的特质:

        信号是软件层面上的“中断”。一旦信号产生,无论程序执行到什么位置,必须立即停止运行,处理信号,处理结束,再继续执行后续指令。

所有信号的产生及处理全部都是由【内核】完成的。

2.与信号相关的事件和状态

产生信号:

  1. 按键产生:如 Ctrl+c、Ctrl+z、Ctrl+\
  2. 系统调用产生:如 kill、raise、abort
  3. 软件条件产生:如 定时器alarm
  4. 硬件异常产生:如 非法访问内存、内存对齐出错
  5. 命令产生:如 kill命令

递达:递送并且到达进程

未决:产生和递达之间的状态,主要由于阻塞导致该状态

信号的处理方式

  1. 执行默认动作
  2. 忽略(丢弃)
  3. 捕捉/自定义(调用户处理函数)

执行默认动作的五种形式:

Term:终止进程

Ign: 忽略信号 (默认即时对该种信号忽略操作)

Core:终止进程,生成Core文件。(查验进程死亡原因, 用于gdb调试)

Stop:停止(暂停)进程

Cont:继续运行进程

阻塞信号集(信号屏蔽字)

将某些信号加入集合,对他们设置屏蔽,当屏蔽x信号后,再收到该信号,该信号的处理将推后(解除屏蔽后)。

本质:位图,用来记录信号的屏蔽状态。一旦被屏蔽的信号,再解除屏蔽前 一直处于未决态 

未决信号集

1.信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为o。这一时刻往往非常短暂。
2.信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集。在屏蔽解除前,信号一直处于未决状态。

本质:位图,用来记录信号的处理状态。该信号集中的信号,表示已经产生,但尚未被处理

3.信号四要素:

1. 编号         2. 名称         3. 对应事件         4. 默认处理动作

信号使用之前,应先确定其4要素,而后再用!!!

kill -l        查看当前系统中常规信号 

常用信号一览表:

1) SIGHUP: 当用户退出shell时,由该shell启动的所有进程将收到这个信号,默认动作为终止进程
2) SIGINT:当用户按下了<Ctrl+C>组合键时,用户终端向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程。
3) SIGQUIT:当用户按下<ctrl+\>组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出些信号。默认动作为终止进程。
4) SIGILL:CPU检测到某进程执行了非法指令。默认动作为终止进程并产生core文件
5) SIGTRAP:该信号由断点指令或其他 trap指令产生。默认动作为终止里程 并产生core文件。
6) SIGABRT: 调用abort函数时产生该信号。默认动作为终止进程并产生core文件。
7) SIGBUS:非法访问内存地址,包括内存对齐出错,默认动作为终止进程并产生core件。
8) SIGFPE:在发生致命的运算错误时发出。不仅包括浮点运算错误,还包括溢出及除数为0等所有的算法错误。默认动作为终止进程并产生core文件。
9) SIGKILL:无条件终止进程。本信号不能被忽略,处理和阻塞。默认动作为终止进程。它向系统管理员提供了可以杀死任何进程的方法。
10) SIGUSR1:用户定义 的信号。即程序员可以在程序中定义并使用该信号。默认动作为终止进程。
11) SIGSEGV:指示进程进行了无效内存访问。默认动作为终止进程并产生core文件。
12) SIGUSR2:另外一个用户自定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进程。
13) SIGPIPE:Broken pipe向一个没有读端的管道写数据。默认动作为终止进程。

14) SIGALRM: 定时器超时,超时的时间 由系统调用alarm设置。默认动作为终止进程。

15) SIGTERM:程序结束信号,与SIGKILL不同的是,该信号可以被阻塞和终止。通常用来要示程序正常退出。执行shell命令Kill时,缺省产生这个信号。默认动作为终止进程。

16) SIGSTKFLT:Linux早期版本出现的信号,现仍保留向后兼容。默认动作为终止进程。

17) SIGCHLD:子进程状态发生变化时,父进程会收到这个信号。默认动作为忽略这个信号。

18) SIGCONT:如果进程已停止,则使其继续运行。默认动作为继续/忽略。

19) SIGSTOP:停止进程的执行。信号不能被忽略,处理和阻塞。默认动作为暂停进程。

20) SIGTSTP:停止终端交互进程的运行。按下<ctrl+z>组合键时发出这个信号。默认动作为暂停进程。

二、kill函数

int kill(pid_t pid, int sig)

给指定进程发送指定信号(不一定杀死)

参数:

        pid:> 0:发送信号给指定进程

                = 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。

                < -1:取绝对值,发送信号给该绝对值所对应的进程组的所有组员。

                = -1:发送信号给,有权限发送的所有进程。

        signum:待发送的信号

返回值:

        成功: 0

        失败: -1 errno

子进程发送信号kill父进程: 

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<errno.h>
#include<pthread.h>
#include<signal.h>

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

int main(int argc,char *argv[])
{
        pid_t pid = fork();
        if (pid > 0)
        {
                printf("patent,pid = %d\n",getpid());
                while(1);
        }
        else if(pid == 0)
        {
                printf("child pid = %d,ppid = %d\n",getpid(),getppid());
                sleep(2);
                kill(getppid(),SIGKILL);
        }

        return 0;
}

让父进程进入死循环,子进程睡2秒(确保父进程先执行),然后在子进程中使用kill杀死父进程

make之后执行得到以下输出:

patent,pid = 1728043
child pid = 1728044,ppid = 1728043
Killed

kill -9 10698:杀死PID为10698的这个进程

kill -9 -10698:杀死进程组为10698的所有进程

三、alarm函数:使用自然计时法

unsigned int alarm(unsigned int seconds)

参数:

        seconds:定时秒数

返回值:

        上次定时剩余时间。

        无错误现象。

alarm(0):取消闹钟。

定时发送SIGALRM给当前进程,每个进程都有唯一的闹钟 

使用alarm函数计时,打印变量i的值:

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

int main(void)
{
        int i;
        alarm(1);

        for (i = 0;;i++)
        {
                printf("%d\n",i);
        }

        return 0;
}

time 命令 : 查看程序执行时间。 实际时间 = 用户时间 + 内核时间 + 等待时间。

time ./alarm

程序运行的瓶颈在于IO,优化程序,首选优化lO。

 time ./alarm > out

四、setitimer函数:设置闹钟,可以替代alarm函数,精度微秒us,可以实现周期定时

int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);

参数:

        which:ITIMER_REAL: 采用自然计时。 ——> SIGALRM

        ITIMER_VIRTUAL: 采用用户空间计时 ——> SIGVTALRM

        ITIMER_PROF: 采用内核+用户空间计时 ——> SIGPROF

        new_value:定时秒数

        old_value:传出参数,上次定时剩余时间。

返回值:

        成功: 0

        失败: -1 errno

类型:

struct itimerval{

        struct timeval{

                time_t  tv_sec;         /* seconds */

                suseconds_t  tv_usec;         /* microseconds */

        }it_interval;---> 用于设定两个定时任务之间的间隔时间

        struct timeval{

                time_t  tv_sec;

                suseconds_t  tv_usec;

        }it_value; ---> 第一次定时秒数

};

可以理解为有2个定时器

  • 一个用于第一个闹钟什么时候触发打印
  • 一个用于之后间隔多少时间再次触发闹钟。

使用setitimer定时,向屏幕打印信息:

#include<stdio.h>
#include<sys/time.h>
#include<signal.h>

void myfunc(int signo)
{
        printf("hello world\n");
}

int main()
{
        struct itimerval it,oldit;
        
        signal(SIGALRM,myfunc);//注册SIGALRM信号的捕捉处理函数
        it.it_value.tv_sec = 2;
        it.it_value.tv_usec = 0;
        
        it.it_interval.tv_sec = 5;
        it.it_interval.tv_usec = 0;

        if (setitimer(ITIMER_REAL,&it,&oldit) ==-1)
        {
                perror("setitimer error");
                return -1;
        }

        while(1);

        return 0;
}

输出:第一次信息打印是两秒间隔,之后都是5秒间隔打印一次

、信号集操作函数

1. 信号集set函数

sigset_t set;自定义信号集。

sigemptyset(sigset_t *set);清空信号集

sigfillset(sigset_t *set);全部置1

sigaddset(sigset_t *set, int signum);将一个信号添加到集合中

sigdelset(sigset_t *set, int signum);将一个信号从集合中移除

sigismember(const sigset_t *set,int signum);

判断一个信号是否在集合中。 在-->1, 不在-->0

2. sigprocmask函数

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

设置信号屏蔽字和解除屏蔽

参数

        how:SIG_BLOCK: 设置阻塞(与)

        SIG_UNBLOCK:取消阻塞(取反位与)

        SIG_SETMASK:用自定义set替换mask。(不推荐)

set:自定义set

oldset:旧有的 mask。        

3. sigpending函数

int sigpending(sigset_t *set);

读取未决信号集

参数:

        set:传出的未决信号集。

 信号操作函数使用原理分析:

 利用自定义集合,来设置信号阻塞,输入被设置阻塞的信号,可以看到未决信号集发生变化 

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

void print_set(sigset_t *set)
{
    int i;

    for(i=1; i<32; i++) {
        if(sigismember(set,i))
            putchar('1');
        else
            putchar('0');
    }

    printf("\n");
}


int main(void)
{
//1. 定义自己的信号集
    sigset_t set, oldset, pedset;

    sigemptyset(&set); //清空信号集
    sigaddset(&set,SIGINT);//将2号信号(Ctrl C)加入信号集内

//2. 将系统的信号集替换为自己的
    int ret = sigprocmask(SIG_BLOCK,&set,&oldset); //SIG_BLOCK设为阻塞
    if(ret == -1)
    {
        perror("sigprocmask error");
        exit(1);
    }

//3. 查看未决信号集的状态
    while(1) {
        ret = sigpending(&pedset); //这个函数会传出状态,用pedset接收
        if(ret == -1)
        {
            perror("sigpending error");
            exit(1);
        }
        print_set(&pedset); //调用上面的函数 查看是否在信号集中
        sleep(1);
    }

    return 0;
}

代码解释:

  1. 定义自己的信号集set;
  2. 清空信号集,使用sigaddset()加指定的信号加入信号集内
  3. 使用sigprocmask()将系统的信号集替换为自己的,它的第一个参数是设置条件(阻塞,取消阻塞)
  4. 使用sigpending()查看未决信号集
  5. 封装一个sigismember函数,来打印sigpending传出的信号集

相关推荐

最近更新

  1. TCP协议是安全的吗?

    2024-04-22 18:22:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-22 18:22:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-22 18:22:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-22 18:22:01       18 阅读

热门阅读

  1. 海洋叶绿素的长期变换分布

    2024-04-22 18:22:01       15 阅读
  2. 【LeetCode热题100】【矩阵】旋转图像

    2024-04-22 18:22:01       12 阅读
  3. 【LeetCode热题100】【矩阵】搜索二维矩阵 II

    2024-04-22 18:22:01       16 阅读
  4. Spring事务

    2024-04-22 18:22:01       14 阅读
  5. QT c++ 读写锁简单举例

    2024-04-22 18:22:01       16 阅读
  6. C# 中的策略模式:从基础到高级

    2024-04-22 18:22:01       18 阅读