linux系统编程-信号-1

26 篇文章 0 订阅

linux系统编程-信号

0x00 . 基本概念

信号4要素:

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

信号特征:

  1. 简单,开销小;
  2. 不能携带大量信息;
  3. 满足某个特设条件才发送。

信号是软件层面上实现的中断,早期常被称为“软中断”。

时钟中断基于硬件

由于信号是通过软件方法实现,所以信号有延时性,cpu可以察觉。

每个进程收到的所有信号,都是由内核负责发送的,内核处理。

kill -l显示信号列表。

1-31为普通信号
34-64为实时信号,用于嵌入式开发。

常用按键信号:

Ctrl + c ,2-SIGINT(终止/中断)

Ctrl + z ,20-SIGTSTP(暂停/停止),和19-SIGSTP有区别。

Ctrl + \ ,3-SIGQUIT(退出)


man 7 signal查看信号帮助文档。

有些信号有3个值,对应不同平台,linux对应中间的值。

文档中有一句要注意:The signals SIGKILL and SIGSTOP cannot be caught, blocked, or ignored. 只能执行默认动作。甚至不能将其设置为阻塞。

产生信号的方式

  1. 按键产生,如:Ctrl+c、Ctrl+z、Ctrl+\
  2. 系统调用产生,如:kill、raise、abort
  3. 软件条件产生,如:定时器alarm
  4. 硬件异常产生,如:非法访问内存(段错误)、除0(浮点数例外)、内存对齐出错(总线错误)
  5. 命令产生,如:kill命令

信号处理

递达:递送并且到达进程。

未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。

信号的处理方式::

  1. 执行默认动作
  2. 忽略(丢弃)
  3. 捕捉(调用其它处理函数)

默认动作:

  • Term:终止进程;
  • Ign: 忽略信号 (默认即时对该种信号忽略操作),比如内核通知父进程回收子进程;
  • Core:终止进程,生成Core文件。(查验进程死亡原因, 用于gdb调试,和-g有关);
  • Stop:停止(暂停)进程;
  • Cont:继续运行进程。

0x01. 未决信号集和阻塞信号集

PCB(task_struct)还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

它们都是set

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

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

未决信号集

信号产生,未决信号集中描述该信号的位立刻翻转为1,表信号处于未决状态。当信号被处理对应位翻转回为0。这一时刻往往非常短暂。

信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合叫做未决信号集。在屏蔽解除前,信号一直处于未决状态。

0x02. kill()

int kill(pid_t pid, int sig); 成功返回0;失败返回-1。

第2个参数建议使用宏,因为不同系统编号可能不同。

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

pid = 0: 发送信号给 与调用kill函数进程属于同一进程组的所有进程。

pid < 0: 取|pid|发给对应进程组。

pid = -1:发送给进程有权限发送的系统中所有进程。

super用户(root)可以发送信号给任意用户,普通用户只能向自己创建的进程发送信号。

//杀死第3个子进程
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

#define N 5
int main(void)
{
    int i;
    pid_t pid, q;

    raise(SIGSEGV);
    abort();


    for (i = 0; i < N; i++) {
        pid = fork();
        if (pid == 0)
            break;
        if (i == 2)
            q = pid;
    }
    if (i < 5) {
        while(1) {
            printf("I'm child %d, getpid = %u\n", i, getpid());
            sleep(1);
        }

    } else {
        sleep(1);
        kill(q, SIGKILL);
        while (1);
    }

    return 0;
}

raise() & abort()

int raise(int sig); 成功返回0,失败返回非0值。给自己发送信号,相当于kill(getpid(), signo);

void abort(void); 给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件。

0x03. alarm()

每个进程都有且只有唯一个定时器。

unsigned int alarm(unsigned int seconds);返回0或剩余的秒数,无失败。

到达时间后,内核会给当前进程发送14)SIGALRM信号。

alarm(5);
sleep(3);
alarm(0);	//return 2

alarm(0)相当于终止闹钟。

//测试1秒内运算次数
#include <stdio.h>
#include <unistd.h>

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

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

	return 0;
}

time ./a.out可以显示执行时间,real = user + sys + wait,会发现io等待时间很多。

time ./a.out > out,则实际时间约等于用户运行时间+内核运行时间

setitimer()

可代替alarm函数。精度微秒us,可以实现周期定时。

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

成功返回0;失败返回-1,设置errno。

参数which指定定时方式:

  • ITIMER_REAL,自然定时 ,14)SIGLARM
  • ITIMER_VIRTUAL,虚拟空间计时(用户空间),26)SIGVTALRM,只计算进程占用cpu的时间
  • ITIMER_PROF,运行时计时(用户+内核),27)SIGPROF,计算占用cpu及执行系统调用的时间

注意两个结构体。

struct itimerval {
	struct timeval it_interval; /* Interval for periodic timer ,两次定时任务之间间隔的时间*/
    struct timeval it_value;    /* Time until next expiration */
};
 struct timeval {
	time_t      tv_sec;         /* seconds */
	suseconds_t tv_usec;        /* microseconds */
};
//测试1秒内运算次数
#include <stdio.h>
#include <stdlib.h>
#include <sys/time.h>

//手动实现alarm()
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(void)
{
	int i;
	my_alarm(1);  //alarm(sec);

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

	return 0;
}

0x04. 信号集操作函数

内核通过读取未决信号集来判断信号是否应被处理。

信号屏蔽字mask可以影响未决信号集,所以可以在应用程序中自定义set来改变mask,达到屏蔽指定信号的目的。

sigset_t  set;		// typedef unsigned long sigset_t; 

int sigemptyset(sigset_t *set); 将某个信号集清0

int sigfillset(sigset_t *set); 将某个信号集置1

int sigaddset(sigset_t *set, int signum);将某个信号加入信号集

int sigdelset(sigset_t *set, int signum);将某个信号清出信号集

以上返回值,成功0,失败-1

int sigismember(const sigset_t *set, int signum);判断某个信号是否在信号集中 返回值:在集合1,不在0,出错-1

sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。

下面这个函数用了屏蔽信号和接触屏蔽,本质是读取或修改进程PCB中的信号屏蔽字。

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

set:传入参数,是一个位图,set中哪位置1,就表示当前进程屏蔽哪个信号。

oldset:传出参数,保存旧的信号屏蔽集。

how,假设当前的信号屏蔽字为mask:

  • SIG_BLOCKset表示需要屏蔽的信号,mask= mask|set
  • SIG_UNBLOCKset表示需要解除屏蔽的信号,mask= mask& ~set
  • SIG_SETMASKset表示用于替代原始屏蔽集的新屏蔽集,mask= set
//将3号信号置为1:
sigemptyset(&set);
sigaddset(&set, 3);
sigprocmask(SIG_BLOCK,&set,&oldset);

int sigpending(sigset_t *set);读取当前进程的未决信号集并传给传出参数。返回值:成功0,失败-1并设置errno。

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

void printped(sigset_t *ped)
{
    int i;
    for (i = 1; i < 32; i++) {
        if (sigismember(ped, i)) {
            putchar('1');
        } else {
            putchar('0');
        }
    }
    printf("\n");
}
int main(void)
{
    sigset_t myset, oldset, ped;
    sigemptyset(&myset);
    sigaddset(&myset, SIGQUIT);
    sigaddset(&myset, SIGINT);  // not working when kill -9
    sigprocmask(SIG_BLOCK, &myset, &oldset);
    while (1) {
        sigpending(&ped);
        printped(&ped);
        sleep(1);
    }

    return 0;
}
/*
0000000000000000000000000000000
^\0010000000000000000000000000000
0010000000000000000000000000000

*/

0x05. 信号捕捉

signal()

该函数注册一个信号捕捉函数.

该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用sigaction函数。

#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
#include <stdio.h>
#include <sys/time.h>
#include <signal.h>

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

int main(void)
{
	struct itimerval it, oldit;
    
	signal(SIGALRM, myfunc);   

	it.it_value.tv_sec = 5;
	it.it_value.tv_usec = 0;

	it.it_interval.tv_sec = 3;
	it.it_interval.tv_usec = 0;

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

	while(1);

	return 0;
}
//

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

typedef void (*sighandler_t) (int);

void catchsigint(int signo)
{
    printf("-----------------catch\n");
}

int main(void)
{
    sighandler_t handler;

    handler = signal(SIGINT, catchsigint);
    if (handler == SIG_ERR) {
        perror("signal error");
        exit(1);
    }

    while (1);
    
    return 0;
}

sigaction()

int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);

struct sigaction {
        void     (*sa_handler)(int);
        void     (*sa_sigaction)(int, siginfo_t *, void *);
        sigset_t   sa_mask; 
        int       sa_flags; 
        void     (*sa_restorer)(void);
};

sa_restorer:该元素是过时的,不应该使用,POSIX.1标准将不指定该元素。(弃用)

sa_sigaction:当sa_flags被指定为SA_SIGINFO标志时,使用该信号处理程序。(很少使用)

sa_flags:通常设置为0,表使用默认属性。

sa_handler注册函数也可赋值为SIG_IGN忽略 或 SIG_DFL执行默认动作

sa_mask:调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意,仅在处理函数被调用期间屏蔽生效,是临时性设置。

关于信号捕捉特性

进程正常运行时,默认PCB中有一个信号屏蔽字。

捕捉到信号执行注册函数时,有可能执行很长时间,在这期间所屏蔽的信号不由PCB信号屏蔽字来指定。而是由sigaction.sa_mask来指定。执行完后再恢复。

阻塞的常规信号不支持排队,产生多次只记录一次。

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

void docatch(int signo)
{
    printf("%d signal is catched\n", signo);
    sleep(10);	//此时ctrl+c没有反应。
    printf("-------finish------\n");
}
int main(void)
{
    int ret;
    struct sigaction act;

    act.sa_handler = docatch;
    sigemptyset(&act.sa_mask);
    sigaddset(&act.sa_mask, SIGQUIT);
    act.sa_flags = 0;  //默认属性   信号捕捉函数执行期间,自动屏蔽本信号
    
    
    ret = sigaction(SIGINT, &act, NULL);
    if (ret < 0) {
        perror("sigaction error");
        exit(1);
    }

    while (1);
 
    return 0;
}

内核信号捕捉过程

user mode kernel mode 中断 do_signal() back signal_handler(int) sigreturn() sys_sigreturn() back to the interrupted place user mode kernel mode
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值