个人知识库 个人知识库
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub

Agnes001

坚持是一件很伟大的事业
首页
关于
  • C语言
  • CPlusPlus
  • Linux
  • PHP
  • Nginx
  • MySQL
  • Redis
  • Docker
  • Kubernetes
  • SRS
阅读
常用工具
  • 分类
  • 标签
  • 归档
GitHub
  • C语言

  • CPlusPlus

  • Lua技术栈

  • edoyun

  • 内存管理

  • 数据结构

  • 网络编程

  • Linux

    • Linux基础
    • 系统编程

      • 概述
      • 文件io
      • 文件系统
      • 进程控制
      • 进程间通信
      • 信号
        • 信号相关概念
          • 信号的状态
          • 信号的处理方式:
          • 信号的特质
        • 产生信号函数
          • kill函数/命令产生信号
          • raise和abort系统函数
          • 软件条件产生信号
          • 终端按键产生信号
          • 硬件异常产生信号
        • 信号集操作函数
          • 自定义信号集设定
          • sigprocmask函数
          • sigpending函数
        • 信号捕捉
          • signal函数
          • sigaction函数
      • 线程
    • 基础命令
    • itcast
    • 文件io
    • gdb
    • Ubuntu安装eclipse
    • gcc安装
    • 系统编程
    • linux内核多线程
  • 池化技术

  • 操作系统

  • python

  • 编程技术
  • Linux
Agnes001
2022-07-05

信号

中断键(interrupt key, Delete 或 Ctrl+C) 退出键(quit key, Ctrl+)

# 信号相关概念

#查看信号1~31为非实时信号,34~64为实时信号
kill -l 
#查看信号说明
man 7 signal

# 信号的状态

  • 产生 按键、系统调用、命令、软件条件、硬件条件
  • 未决状态(没有被处理的状态,等待被进程处理)
  • 递达(递送并且到达进程)

# 信号的处理方式:

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

# 信号的特质

信号的实现手段导致信号有很强的延时性,但对于用户来说,时间非常短,不易察觉。 Linux内核的进程控制块PCB是一个结构体,task_struct, 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要指阻塞信号集和未决信号集。

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

警告

  1. SIGKILL 和19) SIGSTOP信号,不允许屏蔽、忽略和捕捉,只能执行默认动作。

信号是由内核产生发送的,信号的优先级比较高,进程收到信号之后,暂停正在处理的工作,优先处理信号,处理完成之后再继续暂停的工作。

# 产生信号函数

# kill函数/命令产生信号

kill命令产生信号:kill -SIGKILL 进程ID kill函数:给指定进程发送指定信号(不一定杀死)

//成功:0;失败:-1 (ID非法,信号非法,普通用户杀init进程等权级问题),设置errno
int kill(pid_t pid, int sig);	 
//sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致。
//pid > 0:  发送信号给指定的进程。
//pid = 0:  发送信号给与调用kill函数进程属于同一进程组的所有进程。
//pid < -1:  取 |pid| 发给对应进程组。
//pid = -1:发送给进程有权限发送的系统中所有进程。
//进程组:每个进程都属于一个进程组,进程组是一个或多个进程集合,他们相互关联,共同完成一个实体任务,每个进程组都有一个进程组长,默认进程组ID与进程组长ID相同。
//权限保护:super用户(root)可以发送信号给任意用户,普通用户是不能向系统用户发送信号的。 kill -9 (root用户的pid)  是不可以的。同样,普通用户也不能向其他普通用户发送信号,终止其进程。 只能向自己创建的进程发送信号。普通用户基本规则是:发送者实际或有效用户ID == 接收者实际或有效用户ID

//kill(getppid(), SIGKILL);

# raise和abort系统函数

  • raise 函数:给当前进程发送指定信号(自己给自己发) raise(signo) == kill(getpid(), signo); int raise(int sig); 成功:0,失败非0值
  • abort 函数:给自己发送异常终止信号 6) SIGABRT 信号,终止并产生core文件 void abort(void); 该函数无返回

# 软件条件产生信号

# alarm函数

设置定时器(闹钟)。在指定seconds后,内核会给当前进程发送14)SIGALARM信号。进程收到该信号,默认动作终止。 每个进程都有且只有唯一的一个定时器。 unsigned int alarm(unsigned int seconds); 返回0或剩余的秒数,无失败。 常用:取消定时器alarm(0),返回旧闹钟余下秒数。 例:alarm(5) → 3sec → alarm(4) → 5sec → alarm(5) → alarm(0) 定时,与进程状态无关(自然定时法)!就绪、运行、挂起(阻塞、暂停)、终止、僵尸...无论进程处于何种状态,alarm都计时。

//练习:编写程序,测试你使用的计算机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命令查看程序执行的时间。程序运行的瓶颈在于IO,优化程序,首选优化IO。 time ./a.out //实际执行时间 = 系统时间 + 用户时间 + 等待时间

# setitimer函数

设置定时器(闹钟)。 可代替alarm函数。精度微秒us,可以实现周期定时。

//成功:0;失败:-1,设置errno
int setitimer(int which, const struct itimerval *new_value, struct itimerval *old_value);	
//参数:which:指定定时方式
//	① 自然定时:ITIMER_REAL → 14)SIGLARM				 		计算自然时间
//	② 虚拟空间计时(用户空间):ITIMER_VIRTUAL → 26)SIGVTALRM  	 只计算进程占用cpu的时间
//	③ 运行时计时(用户+内核):ITIMER_PROF → 27)SIGPROF		 计算占用cpu及执行系统调用的时间

struct itimerval{
  struct timeval it_interval; //定时器循环周期
  struct timeval it_value;    //第一次触发定时器的时间
};

struct timeval{
  time_t tv_sec;        //seconds
  suseconds_t tv_usec;  //microseconds
};

# 终端按键产生信号

Ctrl + c → 2) SIGINT(终止/中断) "INT" ----Interrupt Ctrl + z → 20) SIGTSTP(暂停/停止) "T" ----Terminal 终端。 Ctrl + \ → 3) SIGQUIT(退出)

# 硬件异常产生信号

除0操作 → 8) SIGFPE (浮点数例外) "F" -----float 浮点数。 非法访问内存 → 11) SIGSEGV (段错误) 总线错误 → 7) SIGBUS

# 信号集操作函数

信号产生,信号处于未决状态,进程收到信号之后,信号被放入未决信号集;放入未决信号集中信号等待处理,再处理之前需要做意见事情,判断阻塞信号集中该信号对应的标志位是否为1,如果为1,不处理,如果为0,则处理该信号;当阻塞信号集中该信号对应的标志位为0时,该信号被处理。

# 自定义信号集设定

//将某个信号集清0		 		成功:0;失败:-1,设置errno
int sigemptyset(sigset_t *set);			
//将某个信号集置1		  		成功:0;失败:-1,设置errno
int sigfillset(sigset_t *set);		
//将某个信号加入信号集合中  	成功:0;失败:-1,设置errno		
int sigaddset(sigset_t *set, int signum);	
//将某信号从信号清出信号集   	成功:0;失败:-1,设置errno
int sigdelset(sigset_t *set, int signum);		
//判断某个信号是否在信号集中:在:1;不在:0;出错:-1,设置errno
int sigismember(const sigset_t *set, int signum);
//除sigismember外,其余操作函数中的set均为传出参数。sigset_t类型的本质是位图。但不应该直接使用位操作,而应该使用上述函数,保证跨系统操作有效。
//对比认知select 函数。

# sigprocmask函数

用来屏蔽信号、解除屏蔽也使用该函数。其本质,读取或修改进程控制块中的信号屏蔽字。 严格注意,屏蔽信号:只是将信号处理延后执行(延至解除屏蔽);而忽略表示将信号丢弃处理。 将自定义信号集设置给内核阻塞信号集

int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);	成功:0;失败:-1,设置errno
// 参数:
// 		set:传入参数,是一个自定义信号集合。由参数how来指示如何修改当前信号屏蔽字。
// 		oldset:传出参数,保存旧的信号屏蔽字。
// 		how参数取值:假设当前的信号屏蔽字为mask
// 1.	SIG_BLOCK: 当how设置为此值,set表示需要屏蔽的信号。相当于 mask = mask|set
// 2.	SIG_UNBLOCK: 当how设置为此,set表示需要解除屏蔽的信号。相当于 mask = mask & ~set
// 3.	SIG_SETMASK: 当how设置为此,set表示用于替代原始屏蔽及的新屏蔽集。相当于mask = set若,调用sigprocmask解除了对当前若干个信号的阻塞,则在sigprocmask返回前,至少将其中一个信号递达。

# sigpending函数

读取当前进程的未决信号集 int sigpending(sigset_t *set); set传出参数。 返回值:成功:0;失败:-1,设置errno 练习:编写程序。把所有常规信号的未决状态打印至屏幕。

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

void printset(sigset_t *ped)
{
	int i;
	for(i = 1; i < 32; i++){
		if((sigismember(ped, i) == 1)){
			putchar('1');
		} else {
			putchar('0');
		}
	}
	printf("\n");
}

int main(void)
{
	sigset_t set, ped;
#if 1
	sigemptyset(&set);
	sigaddset(&set, SIGINT);
#else
	sigaddset(&set, SIGSEGV);
	sigaddset(&set, SIGKILL);
	sigaddset(&set, SIGQUIT);
	sigfillset(&set);
#endif
	sigprocmask(SIG_BLOCK, &set, NULL);	//不获取原屏蔽字

	while (1) {
		sigpending(&ped);               //获取未决信号集
		printset(&ped);
		sleep(1);
	}

	return 0;
}

# 信号捕捉

# signal函数

//注册一个信号捕捉函数:
typedef void (*sighandler_t)(int);
//成功:返回函数指针;失败:返回SIG_ERR,设置errno
sighandler_t signal(int signum, sighandler_t handler); 
//该函数由ANSI定义,由于历史原因在不同版本的Unix和不同版本的Linux中可能有不同的行为。因此应该尽量避免使用它,取而代之使用
sigaction函数。
void (*signal(int signum, void (*sighandler_t)(int))) (int);
能看出这个函数代表什么意思吗?  注意多在复杂结构中使用typedef。

# sigaction函数

//修改信号处理动作(通常在Linux用其来注册一个信号的捕捉函数)
//成功:0;失败:-1,设置errno
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);  
// 参数:
// act:传入参数,新的处理方式。
// 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_handler:指定信号捕捉后的处理函数名(即注册函数)。也可赋值为SIG_IGN表忽略 或 SIG_DFL表执行默认动作
// ② sa_mask: 调用信号处理函数时,所要屏蔽的信号集合(信号屏蔽字)。注意:仅在处理函数被调用期间屏蔽生效,是临时性设置。
// ③ sa_flags:通常设置为0,表使用默认属性。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

/*自定义的信号捕捉函数*/
void sig_int(int signo)
{
	printf("catch signal SIGINT\n");//单次打印
  sleep(10);
  printf("----slept 10 s\n");
}

int main(void)
{
	struct sigaction act;		

	act.sa_handler = sig_int;
	act.sa_flags = 0;
	sigemptyset(&act.sa_mask);		//不屏蔽任何信号
  sigaddset(&act.sa_mask, SIGQUIT);

	sigaction(SIGINT, &act, NULL);

  printf("------------main slept 10\n");
  sleep(10);

	while(1);//该循环只是为了保证有足够的时间来测试函数特性

	return 0;
}
编辑此页
进程间通信
线程

← 进程间通信 线程 →

Theme by Vdoing
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式