UnixC( 六)之信号

1.1信号
1.1.1
中断: CPU执行的时候被打断。
信号:软中断
软中断:软件模拟的中断机制。
中断处理程序:CPU被打断以后,CPU并不处理这个中断,而是让中断处理程序去专门的进行处理。
中断和中断处理程序是异步的。所以信号是异步通信机制。

  • 举例:CPU 每次都是取一条指令执行,执行的时候突然内存告诉CPU,内存满了,这时候CPU怎么办?这时候CPU就停下来看看怎么回事,这就是中断。

使用信号实现异步通信。
-查看信号命令 : kill -l
64 个信号,中间 32,33 信号不存在
SIGCHLD信号,子进程一死,给父进程发送这个信号。
SIGALRM 信号,闹钟
1.1.2 信号产生过程

  1. 信号从产生到处理中间的过程分为: 信号产生 ,信号未决,信号抵达,信号处理。
    如果进程设置了信号阻塞(信号阻塞是在信号产生直接设置的,设置了信号阻塞以后,当信号产生的时候,信号会一直在未决队列中,直到解除信号的阻塞,信号才会抵达,最终被处理)。
  2. 每个进程都有默认的进程处理函数,默认处理方式是终止进程,
  3. 信号处理函数可以被子进程继承。

1.1.3 改变进程的处理函数
#include <signal.h>

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
功能:
参数:
signum:信号编号 1- 64
handler:信号处理函数地址。SIG_IGN, SIG_DFL,还有自定义的函数。
返回值:
注意:The signals SIGKILL and SIGSTOP cannot be caught or ignored.

sighandler_t类型的名字
typedef void (*sighandler_t)(int);

1.1.4 实例代码

void print(int n)
{
	printf("the function print is called ... %d\n",n);
	return;
}
int main()
{
	signal(2,SIG_IGN);
	signal(3,print);
	signal(9,SIG_IGN);
	while(1);
	return 0;
}

1.2 信号产生

1.2.1 信号产生
1、硬件 ctrl+c ctrl+
2、使用linux命令产生信号 kill -信号(1-64) pid
3、使用函数产生信号
kill(2)
raise(3)
alarm(2)
sigqueue 和 sigaction 一般联合使用

sigqueue函数
	功能:新的发送信号系统调用,主要是针对实时信号提出的支持信号带有参数,与函数sigaction()配合使用。
	注意:和kill函数相比Int kill(pid_t pid, int siq)多了参数
	原型:
	int sigqueue(pid_t pid, int sig, const union sigval value);
	参数
	 sigqueue的第1个参数是指定接收信号的进程id,第2个参数确定即将发送的信号,第3个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。
	返回值成功返回0,失败返回-1 
	sigqueue()比kill()传递了更多的附加信息,但sigqueue()只能向一个进程发送信号,而不能发送信号给一个进程组。
sigaction函数
	包含头文件<signal.h>
	功能:sigaction函数用于改变进程接收到特定信号后的行为。
	原型:
int  sigaction(int signum,const struct sigaction *act,const struct sigaction *old);
	参数
	该函数的第一个参数为信号的值,可以为除SIGKILL及SIGSTOP外的任何一 个特定有效的信号(为这两个信号定义自己的处理函数,将导致信号安装错误)
	第二个参数是指向结构sigaction的一个实例的指针,在结构 sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理
	第三个参数oldact指向的对象用来保存原来对相应信号的处理,可指定oldact为NULL。
	返回值:函数成功返回0,失败返回-1
    signal(num., handle)

sigaction结构体
	第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉哪些函数等等
struct sigaction {
	void (*sa_handler)(int);   //信号处理程序 不接受额外数据
	void (*sa_sigaction)(int, siginfo_t *, void *); //信号处理程序 能接受额外数据,和sigqueue配合使用 
	sigset_t sa_mask; //
	int sa_flags; //影响信号的行为 SA_SIGINFO表示能接受数据
	void (*sa_restorer)(void); //废弃
};
注意1:回调函数句柄sa_handler、sa_sigaction只能任选其一。
注意2:The sigaction structure is defined as something like 思考如何测试?

1.2.2 alarm 函数
#include <unistd.h>
unsigned int alarm(unsigned int seconds);

DESCRIPTION
     alarm() arranges for a SIGALRM signal to be delivered to the calling process in seconds seconds.
     If seconds is zero, any pending alarm is canceled.  
     In any event any previously set alarm() is canceled.
    翻译:
    	alarm 函数在 senconds秒后给 当前进程发送一个SIGALRM信号。如果参数seconds是0 ,所有悬而未决的alarm函数就会被取消。如果second 是0,之前所有设置的闹钟都被取消

RETURN VALUE
     alarm()  returns the number of seconds remaining until any previously scheduled alarm was due to be delivered,or zero if there was no previously scheduled alarm.
  	翻译: alarm  函数 :在之前没有调度的alarm函数就会返回0,在之前有调度的alarn函数就会返回之前的alarm函数剩余的秒数。
pause(2)函数的使用
#include <unistd.h>
int pause(void);
功能:等待一个信号。让进程睡觉,直到一个信号到达
返回值:
只有信号抵达,信号处理函数返回以后才返回-1。errno被设置

1.2.3 alarm实例代码

  • 实例程序1
int main()
{
	int  num = alarm(1);
	for(int i =1; i > 0 ;i++)
	{
		printf("zhangsan .. %d\n",i);
		printf("num = %d \n",num);
	}
	return 0;
}

结果证明了 ,alarm 函数之前没有alarm 函数的时候就会返回0;

在这里插入图片描述

  • 实例程序2
int main()
{
	int  num = alarm(5);
	for(int i =1; i < 150000 ;i++)
	{
		printf("zhangsan .. %d\n",i);
		printf("num = %d \n",num);
	}
	
	 num = alarm(0);
	printf("num2 =%d\n", num);
	return 0;
}

在这里插入图片描述
1.2.4 pause

1.3 信号集

1.3.1 图解PCB和信号集
程序员可以设置进程对某些信号的阻塞。信号抵达以后不对信号进行处理,解除对信号的阻塞以后,在对信号进行相应的处理。
在这里插入图片描述

blocking 中1 号信号设置为阻塞,pending 中1 号信号为0 ,说明没有产生1号信号。
blocking 中2 号信号设置为非阻塞,pending 中2号信号为0 ,说明没有产生,也没有阻塞。
blocking 中3 号信号设置为阻塞,pending 中3号信号为1,说明产生3号信号,并且被阻塞
blocking 中4 号信号设置为非阻塞,pending 中4 号信号为1 ,说明产生4号信号,但是没有阻塞。

1.3.2 信号集操作相关函数

 #include <signal.h>

       int sigemptyset(sigset_t *set);   
        - 初始化 信号集为空
	
       int sigfillset(sigset_t *set);
       - 初始化信号集全为1

       int sigaddset(sigset_t *set, int signum);
       - 向信号集中添加元素

       int sigdelset(sigset_t *set, int signum);
	 - 从信号集中删除信号
	 
       int sigismember(const sigset_t *set, int signum);
	 -  判断某个信号是否在信号集里面。

	 详细介绍看 man  3  sigaddset 官方文档。

实例代码:

在这里插入代码片

1.3.3 信号掩码

信号掩码 在POSIX下,每个进程有一个信号掩码(signal mask)。简单地说,信号掩码是一个"位图",其中每一位都对应着一种信号。如果位图中的某一位为1,就表示在执行当前信号的处理程序期间相应的信号暂时被"屏蔽",使得在执行的过程中不会嵌套地响应那种信号。

  • 信号掩码其实就是一个集合,这个集合是在内核中是用数组进行表示的,数组中包含64个位置,如果数组元素的值是1,就是被阻塞(屏蔽)。

1.3.3.1 系统掩码函数

       #include <signal.h>
       int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);
       sigprocmask()  is  used  to fetch and/or change the signal mask of the calling thread.  The signal mask is the set of signals whose delivery is currently blocked for the caller (see also signal(7) for more details).
       -   sigprocmask()  获取和改变当前线程的掩码。 信号掩码是一个信号的集合,这个信号集的传递被阻塞了。
参数:
how:
SIG_BLOCK:当前进程的信号掩码和set集合的并集。设置成进程的信号掩码
SIG_UNBLOCK:将set集合里的信号从当前进程的信号掩码中移除
SIG_SETMASK:信号掩码设置成set集合
set:
oldset:保存原来的信号掩码集(旧的)。如果是NULL,不保存原来的

1.3.3.2 实例程序 z_block.c


int main()
{

	sigset_t set;
	//sigfillset(&set);
	sigemptyset(&set);
	//将信号添加到信号集
	sigaddset(&set,2);
	sigaddset(&set, 3);
	//设置信号掩码
	sigprocmask(SIG_SETMASK,&set ,NULL);
	while(1);
	return 0;
}

将2号和三号信号屏蔽以后,发送给进程以后,进程没有相应,而九号信号和 sigstop信号是无法被屏蔽的。

在这里插入图片描述在这里插入图片描述

1.3.3.3 sigmask.c

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

void  func(int n)
{
	printf("signal  = %d\n",n);
}
int main()
{
	sigset_t set;
	sigset_t oset;
	
	signal(2,func);
	signal(45,func);
	sigemptyset(&set);
	sigaddset(&set ,2);
	sigaddset(&set, 45);

	sigprocmask(SIG_SETMASK,&set, &oset);
	for(int i =0 ;i <10000000;i++)
	{
		printf("i = %d\n",i);
	}
	sigprocmask(SIG_SETMASK,&oset,NULL);
	return 0;
}

结果:

在这里插入图片描述
在进程运行过程中,多次向进程发送信号2 和45 ,结果在最后信号2 响应了1次,而信号45 响应 了多次,这就是可靠信号和不可靠信号。

结论:有些信号在阻塞的时候发送多次,进程在解除对信号的阻塞的时候,调用一次,这样的信号称为不可靠信号。信号丢失。1~31
在进程对信号阻塞的时候,信号发送多次,在进程解除对信号的阻塞以后,发送几次处理几次,这样的信号称为可靠信号。 34~64

1.4 未决信号
有信号阻塞检测未决信号才有意义。
1.4.1 如何检测进程的未决信号?
sigpending(2)
#include <signal.h>
int sigpending(sigset_t *set);
功能:检测未决信号
参数:
set:指定存储未决信号集的地址
返回值:
成功 0
错误 -1

1.4.2 未决信号的理解

  • 只有信号定义了阻塞(sigprocmask(SIG_SETMASK,&set,NULL),将信号集设置为阻塞,也就是发送set信号集里面的信号的时候就会发生阻塞,)才能捕获未决信号。sigpending(&set)函数是用来获取未决信号的,将未决信号放到set中,随后我们通过函数sigismember(&set,signum) 去判断signum是否是未决信号。

1.4.3 程序实例

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

int main()
{
	sigset_t set;
	sigset_t pset;
	sigemptyset(&set);
	sigaddset(&set, 2);
	sigprocmask(SIG_SETMASK,&set,NULL);
	
	while(1)
	{
		sleep(1);
		if(sigpending(&pset) == -1)
		{
			perror("sigpending");
			return -1;
		}
		int  is = sigismember(&pset,2);
		is ? printf("yes...\n") : printf("no..\n");

	}
	
	return 0;
}

在这里插入图片描述
1.4.4 信号处理的过程
1、在bash下启动进程a.out
2、按下ctrl+c键,产生硬件中断,不管进程运行在用户态还是内核态,进程都会切换到内核态。
3、驱动程序会将中断解释为2号信号,将这个到达的信号记录到进程的PCB中。
4、当进程从内核态切换回用户的时候,检测PCB中是否有信号到达。
5,如果没有信号到达,直接切回用户态,继续执行。
6、如果有信号到达,调用信号的处理函数。信号处理函数执行完毕,调用sigreturn,结束信号处理函数,清空函数的栈帧,返回到内核态。执行第4步。
主程序 信号处理函数
异步的
1.4.4 可重入函数
只使用函数私有空间,不使用进程的全局空间,这样的函数称为可重入函数。
如果函数中使用了全局变量、静态局部变量或malloc分配的内存,这样的函数就是不可重入函数。
举例说明 不可重入函数产生的错误
在这里插入图片描述

在这里插入图片描述
1.5 前台作业和后台作业
1.5.1
ps -o pid,ppid,pgrp,sess,comm

zhangsan@zhangsan-virtual-machine:~/1c/UC/day11$ ps  -o pid,ppid,pgrp,sess,comm
   PID   PPID   PGRP   SESS COMMAND
  2588   2548   2588   2588 bash
 14591   2588  14591   2588 ps

会话id 就是bash的pid,在bash下启动的,相当于会话的组长,会话相当于一次连接,这次连接可以做很多的事情。

  • 比如儿子打电话给父母(这就是一次会话),儿子和父亲讲话,儿子和母亲讲话,孙子和爷爷讲话,孙子和奶奶讲话,这次会话就完成了四次通话。这四次通话就是4个作业。
    会话和终端相关,有的会话有终端,有的没有终端(守护进程)

比如 在shell 中下命令 ls -l |grep a.txt
这两个命令是一个进程组,我们通常不叫进程组,而是叫给计算机下了一次作业。
作业和进程组的区别。父进程子进程孙子进程属于同一个进程组,但是父进程和子进程是一个作业,父进程和孙子进程不是一个作业。
在终端上布置都是作业。
有终端的作业只有一个(必须有一个)前台作业,可以有多个后台作业。
vim hello 的时候,bash进入后台,
ctrl + z 将正在运行的作业放在后台,
jobs 查看后台作业
fg %1 fg %作业号,后台作业切回前台作业
bg %作业号,在后台运行作业
vim hello& 直接在启动的时候将作业放在后台
command& 执行作业的时候,直接放到后台

1.5.2
bash脚本
将要执行的linux命令编写到一个文件中,一起执行,这样的文件就是bash脚本文件。

#!/bin/bash
cat etc/passwd
ls

先执行前台作业和后台作业的执行顺序不定

#!/bin/bash
cat etc/passwd&
ls

使用快捷键只能给前台作业发送信号
1.5.3 分析下面的孤儿进程(通过前台作业和后台作业)S

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

//孤儿进程的特点,父进程终止了,子进程的父进程改为init进程
int main(void){
    //创建子进程
    pid_t pid=fork();
    if(pid==-1){
        perror("fork");
        return -1;
    }
    if(pid==0){//子进程执行的代码
        printf("parent of child...%d\n",getppid());
        sleep(2);//确保父进程已经终止了
        printf("parent of child...%d\n",getppid());
        
    }else{//父进程执行的代码
        sleep(1);
        exit(0);
    }
    return 0;
}

为什么打印结果是这样的?
zhangsan@zhangsan-virtual-machine:~/1c/UC/day09$ ./a.out
parent of child…15799
zhangsan@zhangsan-virtual-machine:~/1c/UC/day09$ parent of child…1566
我们需要通过前台作业和后台作业进行分析。
a.out 程序中,父进程死了,bash进程为a.out中的父进程收尸(这两个进程是一个作业),但是bash进程不管孙子进程(这两个进程不是一个作业),bash收子进程以后马上就变成前台作业,所以当前路径被打印,而一个有终端的会话只能有一前台作业,所以a.out的子进程只能放在后台作业中,但是a.out子进程的输出会在bash中显示。

参考一 : UNIX 环境高级编程
参考二 : 网上视频

参考一 :狄泰软件学院C++进阶剖析
参考二 : C++ primer中文版
如有侵权:请联系邮箱 1986005934@qq.com

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值