25,Linux 信号

学习目标:

-了解信号的分类与产生方式

-掌握不同情况下信号的产生方法

-掌握屏蔽、捕捉信号的方法

-熟练处理程序中产生的信号

-熟练掌握利用信号机制实现进程同步的方法


1.信号及信号来源

信号全称软中断信号,其本质是软件层次上对中断机制的一种模拟,用于提醒进程某件事情已经发生。

可以使用

kill -l

命令查看系统中的信号:

 信号的编号从1开始,其中1~31号信号为常规信号,也就是早期信号模型中的不可靠信号;

34~64号信号为实时信号,是用于计算机底层开发中的信号。

怎么产生信号?

- 硬件
  - 执行非法指令
  - 访问非法内存
  - 驱动程序
 

- 软件

  - 控制台:
    - ctrl+c:中断信号
    - ctrl+|:退出信号
    - ctrl+z:停止信号

  - kill命令
  - 程序调用kill()函数

信号的处理方式:

- 忽略:进程当信号从来没有发生过
- 捕获:进程会调用相应的处理函数,进行相应的处理

- 默认:使用系统默认处理方式来处理信号

常用信号分析

| 信号名      | 信号编号 | 产生原因     | 默认处理方式        |
| SIGHUP   | 1        | 关闭终端     | 终止                |
| SIGINT     | 2        | ctrl+c       | 终止                |
| SIGQUIT  | 3        | ctrl+\       | 终止+转储           |
| SIGABRT | 6        | abort()      | 终止+转储           |
| SIGPE      | 8        | 算术错误     | 终止                |
| SIGKILL   | 9        | kill -9 pid  | 终止,不可捕获/忽略 |
| SIGUSR1 | 10       | 自定义       | 忽略                |
| SIGSEGV | 11       | 段错误       | 终止+转储           |
| SIGUSR2 | 12       | 自定义       | 忽略                |
| SIGALRM | 14       | alarm()      | 终止                |
| SIGTERM | 15       | kill pid     | 终止                |
| SIGCHLD | 17       | (子)状态变化 | 忽略                |
| SIGTOP    | 19       | ctrl+z       | 暂停,不可捕获/忽略 |

pkill命令

2.信号的产生

本节以系统调用为主讲解信号的产生的方式

2.1系统调用

系统调用中发送信号常用的函数有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为ESRCH,表明指定接收信号的进程不存在;否则表示当前进程没有权限。

不同操作系统中的信号编号对应的信号名不一定相同,为了提高代码可读性和可移植性,用户在使用kill()函数时应尽量使用系统中定义的宏进行传参。

案例7-1:使用fork()函数创建一个子进程,在子进程中使用kill()发送信号,杀死父进程。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
int main()
{
        pid_t pid;
        pid = fork();
        if(pid == 0)//子进程
        {
                sleep(1);
                printf("child pid=%d,ppid=%d\n",getpid(),getppid());
                kill(getppid(),SIGKILL);//发送信号,杀死父进程
        }
        else if(pid > 0)//父进程
        {
                while(1)
                {
                        printf("parent pid=%d,ppid=%d\n",getpid(),getppid());
                }
        }
        return 0;
}

为了保证父进程能接收到子进程发送的信号,在父进程执行的代码段中添加循环,保持父进程的运行;子进程的代码段中调用了kill()函数发送SIGKILL信号给父进程,在此之前使子进程沉睡1秒,执行结果如下:

 当终端输出Killer时,表明子进程发送的信号SIGKILL成功杀死了父进程。

多学一招:raise()、abort()、pause()

除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,进入阻塞状态,直到有信号传达将其唤醒,才继续工作。

int pause(void);

3.软件条件

当满足某种软件条件时,也可以驱使内核发送信号。Linux系统中的alarm()函数就是一个典型的生产软件条件信号的信号源。

1.alarm()

alarm()函数的功能相当于计时器,驱使内核在指定秒数后发送信号到调用该函数的进程。alarm()函数存在于函数库unistd.h中,其函数声明如下:

unsigned int alarm(unsigned int seconds);

若进程中不是第一次调用alarm(),且上一个的alarm()尚有剩余的秒数,则该函数成功调用后会返回旧计数器的剩余秒数,否则返回0。

例如在第一个定时器alarm(5)启动3秒后,第二个新定时器alarm(4)启动,那么alarm(4)的返回值为2;若3秒后第三个定时器alarm(2)的返回值为0;若额外设置alarm(0),将会取消计时器。计时器采用自然定时法,无论当前进程是否处于运行状态,计时器都会计时。

计时结束后,内核会发送14号信号SIGALRM到当前进程,进程收到信号后默认终止运行。

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
        alarm(1);              //设置计时器
        while(1)               //循环保证进程不退出
                printf("process will finish.\n");
        return 0;
}

先设置案例7-2中设置了一个1秒的计时器;为了保证进程在信号到达之前保持运行,又在进程中添加while循环,使进程不断打印信息。1秒后计时器会驱使内核发送SIGALRM信号到进程,因此进程会在1秒之后结束。

 

编译案例7-2,执行程序,观察到屏幕不断打印“process will finish.”。一秒后停止打印并输出Alarm clock,表示计时器生效,使进程终止

2.setitimer()

setitimer()函数也可以设置定时器。与alarm()相比,它精确度更高,并且可实现周期定时。该函数存在于函数库sys/time.h中,函数声明如下:

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

若setitimer()函数成功调用则返回0;否则返回-1并设置errno。该函数有三个参数, 其中参数which用来设置以何种方式计时which有3个取值,不同的值对应不同的计时方法,产生不同的信号。which取值及对应含义如下:

①若参数为ITIMER_REAL,使用自然定时法计时,计算自然流逝的时间,计时结束递送14号信号SIGALRM。

②若参数为ITIMER_VIRTUAL,只计算进程占用CPU的时间,计时结束后递送26号信号SIGVTALRM。

③若参数为ITIMER_PROF,计算进程占用CPU以及执行系统调用的时间,即进程在用户空间和内核空间运行时间的总和,计时结束后递送27号SIGPROF。

setitimer()的第二个参数是一个传入参数,表示计时器定时时长,其本质是一个ititimerval类型数据结构的指针。itimerval中有两个timerval类型的成员,这两个成员也是结构体类型。itimerval与timeval的定义如下:

struct itimerval{

        struct timeval it_interval;

        struct timeval it_value;

} ;

struct timeval{

        long tv_sec;         //秒

        long tv_usec;       //  微秒

};

timerval结构体的两个成员分别提供秒级精度和微秒级精度;Itimerval结构体的两个成员it_interval和it_value分别指定间隔时间和初始定时时间。若只指定it_value,则只实现一次定时若同时指定it_interval,则用来实现重复定时。

setitimer()的工作机制是,先对it_value倒计时,it_value计时结束时,触发信号发送条件。然后重置it_value为it_interval,继续对it_value倒计时,如此一直循环。

setitimer()函数的第三个参数用来保存先前设置的new_value值,通常设置为NULL

案例7-3:使用setitimer()函数实现alarm()函数。

#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>
#include <error.h>
unsigned int my_alarm(unsigned int sec)
{
        struct itimerval it,oldit;   
        int ret;
        it.it_value.tv_sec = sec;      //指定时间
        it.it_value.tv_usec=0;
        it.it_interval.tv_sec=0;       //指定重复次数
        it.it_interval.tv_usec=0;
        ret = setitimer(ITIMER_REAL,&it,&oldit);
        if(ret==-1)
        {
                perror("setitimer");
                exit(1);
        }
        return oldit.it_value.tv_sec;
}
int main()
{
        my_alarm(1);
        while(1)
            printf("process will finsih!\n");
        return 0;
}

alarm ()只实现一次计时,因此my_alarm()中调用的setitimer()的参数it的成员it_interval的值都为0因为alarm()只精确到秒,所有setitimer()中参数it表示微秒的成员变量it_value.tv_usec设置为0即可。

编译后执行程序,1秒后停止输出process will finish并输出Alarm clock,说明my_alarm()实现成功,输出信息如下:

 

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值