Linux进程间通信 - (二) 信号

本文详细介绍了Linux中的信号机制,包括信号的异步特性、处理方式(忽略、捕获和默认操作)、信号编程中的kill(), raise(), alarm(), pause()函数用法,以及signal()和sigaction()的信号设置。通过实例演示了如何自定义信号处理并理解信号在进程间通信中的重要作用。

Linux进程间通信 - (二) 信号

1. 概述

  • 信号是在软件层面上对中断机制的一种模拟。在原理上,一个进程收到一个信号与处理器收到一个中断请求可以说在某种程度上是一样的。

  • 信号是异步的:一个进程不必通过任何操作来等待信号的到达。事实上,进程也不知道信号什么时候会到达。

  • 信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户进程发生了那些系统事件。

  • 信号可以在任何时候发给某一进程,而无需知道该进程的状态。如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它为止。

  • 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。

  • 信号是进程间通信机制中唯一的异步通信机制。我们可以把这种机制看作异步通知,通知接收信号的进程有哪些事情发生了。

  • 信号机制经过POSIX实时扩展后,功能更加强大,除了基本的通知功能外,还可以传递附加信息。

  • 信号事件的产生有硬件来源(比如按下键盘或其他引荐故障)和软件来源,常用的信号相关函数有kill()、raise()、alarm()、settitimer()和sigqueue()等。软件来源还包括一些非法运算等。

2. 信号处理方式

进程可以通过以下三种方式来响应一个信号。

1. 忽略信号

  • 即对信号不做任何处理,其中有两个信号不能忽略。SIGKILLSIGSTOP

2. 捕获信号

  • 定义信号处理函数,当信号发生时,执行相应的处理函数。

3. 执行默认操作

Linux对每种信号都规定了默认操作。如下表:
在这里插入图片描述

3. 信号编程

信号的相关函数

信号的先关函数包括信号的发送和设置,具体如下:

  1. 发送信号的函数:kill()raise()
  2. 设置信号的函数:signal()sigaction()
  3. 其他函数:alarm()pause()
信号的发送与设置
1. 信号的发送:kill()和raise()

kill()函数同我们熟悉的kill系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令就是用kill()函数实现的)。

需要注意的是,它不仅可以终止进程(发送SIGTERM信号),也可以向进程发送其他信号。

kill()函数所不同的是,raise()函数只允许进程向自身发送信号。
kill()函数语法

所需头文件
	#include <signal.h>
	#include <sys/types.h>
函数原型
	int kill(pid_t pid, int sig);
函数传入值
	pid
		整数:发送信号给进程号为pid的进程
		0:信号被发送到所有和当前进程在同一个进程组的进程
		-1:信号发送给所有进程表中的进程(除了进程号最大的进程外)
		<-1:信号发送给进程组号为-pid的每一进程
	sig
		信号类型
函数返回值
	成功:0
	出错:-1

raise()函数语法

所需头文件
	#include <signal.h>
	#include <sys/types.h>
函数原型
	int raise(int sig);
函数传入值
	sig:信号类型
函数返回值
	成功:0
	出错:-1

定时器信号

  • alarm()也称为闹钟函数,它可以在进程中设置一个定时器。当定时器指定的时间到了时,它就向进程发送SIGALARM信号。
  • 需要注意的是一个进程只能有一个闹钟时间,如果在调用alarm()之前已设置过闹钟时间,则任何以前的闹钟时间都被新值所代替。
  • pause()函数是用于将调用进程挂起直至接收到信号为止

alarm()函数语法

所需头文件
	#include <unistd.h>
函数原型
	unsigned int alarm(unsigned int seconds);
函数传入值
	seconds:指定秒数,系统经过seconds秒之后向该进程发送SIGALRM信号。
函数返回值
	成功:如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间剩余的时间,否则返回0。
	出错:-1

pause()函数语法

所需头文件
	#include <unistd.h>
函数原型
	int pause(void);
函数返回值
	-1,并且errno值设为EINTR

注意,这里pause()函数只返回-1,错误代码EINTR 表示有信号到达中断了此函数。
下面来看个小例子,揭示alarm()函数和pause()函数的用法:

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

int main()
{
	alarm(5); //调用alarm()定时器函数,定时结束后会向当前进程发送SIGALARM信号
	pause()//将调用级进程挂起,知道收到信号为止
	printf("This is my world\n"); //词句不会被执行,包括后面的
  
	return 0}

由于 SIGALARM的系默认操作是终止该进程,所以当进程接收到该信号时就会终止该进程,所以printf内容不会被输出,因为进程提前终止了。运行该程序后,进程在等待5s后,会输出如下:
在这里插入图片描述
这个Alarm clock打印是信号SIGALARM系统默认处理函数的打印的。

2. 信号的设置

信号处理的主要的主要方法有两种:

  1. 一是使用简单的signal()函数
  2. 使用sigaction()函数。

signal()函数

所需头文件
	#include <signal.h>
函数原型
	typedef void (*sighandler_t)(int);
	sighandler_t signal(int signum, sighandler_t handler);
函数传入值
	signum :指定信号代码
	handler:
		SIG_IGN:忽略该信号
		SIG_DFL:采用系统默认方式处理信号
		自定义的信号处理函数
函数返回值
	成功:以前的信号处理函数
	出错:-1

注意,这里signal()的返回值和第二个参数都是一个无返回值带一个int型参数的函数指针。

sigaction()函数

所需头文件
	#include <signal.h>
函数原型
	int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact);
函数传入值
	signum:信号类型,除SIGKILL及SIGSTOP之外的任何一个信号
	act:指向sigaction结构体的指针,包含对特定信号的处理
	oldact:保存信号原先的处理方式
函数返回值
	成功:0
	出错:-1

sigaction()函数中的第2个和第三个参数用到了sigaction结构体,我们来看一下:

struct sigaction
{
	void (*sa_handler)(int signo);  // 对应的信号处理函数
	sigset_t sa_mask;  // 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置
	int sa_flags;  // 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置
	void (*sa_restore)(void); // 一般未使用到
}

sa_handler是一个函数指针,指向信号处理函数。它既可以是用户自定义的处理函数,也可以为SIG_DEL(采用默认的处理方式或SIG_IGN(忽略信号)。信号处理函数只有一个参数,即信号类型

sa_mask是一个信号集合,用来指定在信号处理函数执行过程中哪些信号被屏蔽。
sig_flags中包含了许多标志位,都是和信号处理相关的选项。常见的选项值如下:
在这里插入图片描述

通过下面这个小例子,我们来看一下sigaction()函数的用法:

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

void my_func(int sign_no)
{
	if (sign_no == SIGINT)
	{
		printf("I have got SIGINT\n");
	} else if (sign_no == SIGQUIT){
		printf("I have got SIGQUIT\n");
	}
}

int main()
{
	struct sigaction action;

	sigaction(SIGINT,0,&action);   // 信号SIGINT,前面讲到过键盘输入ctrl+c
	action.sa_handler = my_func;  // 注册我们自己的信号处理函数
	sigaction(SIGINT,&action,0);

	sigaction(SIGQUIT,0,&action); // 信号SIGQUIT,前面讲到过键盘输入ctrl+\
	action.sa_handler = my_func;
	sigaction(SIGQUIT,&action,0);
	printf("Waiting for signal SIGINT or SIGQUIT...\n");

	pause();  // 阻塞进程直到收到信号
	Sexit(0);
}

运行上面的进程后,进程会阻塞,等待信号的到来。信号到来后会调用我们自己的信号处理函数。此时当我们在键盘输入ctrl+c时,会打印如下:
在这里插入图片描述
当我们键入ctrl+\时,会打印如下:
在这里插入图片描述

<think> 我们被要求详细讲解Linux进程间通信信号量。根据用户提供的引用,我们需要整合这些信息,并构建一个详细的回答。 引用[1]提到有名信号量(Named Semaphore)是由操作系统内核维护的,具有全局唯一名字,可以在不同进程之间共享。 引用[2]介绍了信号是软中断,用于进程间通信和处理异常,是异步通知机制。 引用[3]解释了信号量是一种计数器,用于协调多个进程对共享资源的访问。它描述了信号量的基本操作过程:当进程想要使用共享资源时,先测试信号量,如果值大于0则使用并减1,否则挂起等待;当资源被释放时,信号量加1。 引用[4]提到了同步的概念,即进程之间按照依赖关系运行,并提到实现信号量的操作。 注意:这里用户询问的是“信号量”(semaphore),而引用[2]中提到了“信号”(signal),这是两个不同的概念。我们需要区分清楚,避免混淆。 根据以上引用,我们组织回答如下: 1. 信号量的定义和作用:信号量是一个计数器,用于控制多个进程对共享资源的访问。它主要用于进程间的同步和互斥。 2. 信号量的类型:分为有名信号量和无名信号量(也叫基于内存的信号量)。有名信号量通过名字在系统中唯一标识,可用于不同进程之间;无名信号量通常存在于内存中,一般用于同一进程内的线程间同步,但也可以通过共享内存用于进程间。 3. 信号量的操作:P操作(等待,信号量减1)和V操作(释放,信号量加1)。当信号量值为0时,试图进行P操作的进程将被阻塞,直到有其他进程执行V操作。 4. 在Linux中,信号量的实现:System V信号量和POSIX信号量。POSIX信号量又分为有名信号量和无名信号量。 5. 使用示例:我们将给出一个简单的使用POSIX有名信号量进行进程间同步的代码示例。 注意:由于引用中提到了有名信号量,我们将重点放在有名信号量的介绍上,但也会简要提及无名信号量。 我们将按照以下结构组织回答: 1. 信号量的基本概念 2. 信号量的类型(有名信号量和无名信号量) 3. 信号量的工作原理(P/V操作) 4. 信号量的操作函数(POSIX有名信号量) 5. 使用示例 6. 注意事项 同时,根据要求,我们需要在最后生成相关问题。 注意:引用标识要自然地添加在引用内容的末尾。我们参考了用户提供的引用[1][3][4](引用[2]是关于信号的,与信号量不同,所以我们不引用它)。 下面开始回答: </think> ### Linux 进程间通信信号量详解 信号量(Semaphore)是 Linux 进程间通信(IPC)的核心机制之一,本质是一个**受保护的计数器**,用于协调多个进程对共享资源的访问控制。其核心功能是通过原子操作确保资源的互斥访问和同步协调。 --- #### **1. 信号量基础原理** - **计数器机制**: 信号量维护一个整数值,表示可用资源的数量: - 值 $ > 0 $:当前有可用资源 - 值 $ = 0 $:资源耗尽,请求进程需等待 - 值 $ < 0 $:绝对值表示等待队列中的进程数量 - **P/V 操作**(原语操作): - **P 操作**(`sem_wait()`): $$ P(S): \text{ if } S > 0 \text{ then } S \leftarrow S-1 \text{ else block} $$ 进程尝试获取资源,若信号量值 $ S \leq 0 $ 则阻塞 - **V 操作**(`sem_post()`): $$ V(S): S \leftarrow S+1 \text{, wake a blocked process} $$ 进程释放资源,唤醒等待队列中的进程[^3] --- #### **2. 信号量类型** | 类型 | 标识方式 | 作用范围 | 生命周期 | |------|----------|----------|----------| | **有名信号量** | 文件系统路径名 (如 `/mysem`) | 跨进程共享 | 持续到显式删除或系统重启 | | **无名信号量** | 共享内存地址 | 父子进程/线程间 | 随进程终止销毁[^1] | > 有名信号量通过全局唯一路径名访问,适合无亲缘关系进程;无名信号量需配合共享内存使用,适合相关进程。 --- #### **3. POSIX 信号量核心 API** ```c #include <semaphore.h> // 创建/打开有名信号量 sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value); // 初始化无名信号量 int sem_init(sem_t *sem, int pshared, unsigned int value); // P操作(等待资源) int sem_wait(sem_t *sem); // 阻塞等待 int sem_trywait(sem_t *sem); // 非阻塞尝试 // V操作(释放资源) int sem_post(sem_t *sem); // 关闭信号量 int sem_close(sem_t *sem); // 删除有名信号量 int sem_unlink(const char *name); ``` **参数说明**: - `value`:信号量初始值(资源数量) - `pshared`:`0`为线程共享,`非0`为进程共享 - `oflag`:`O_CREAT`(不存在则创建)或 `O_EXCL`(配合`O_CREAT`确保新建) --- #### **4. 使用场景与同步模型** 1. **互斥锁(Mutex)**: 初始值设为1,实现临界区保护: ```c sem_t mutex; sem_init(&mutex, 1, 1); // 进程间共享,初始值1 // 进程A sem_wait(&mutex); // 进入临界区 /* 访问共享资源 */ sem_post(&mutex); // 离开临界区 ``` 2. **生产者-消费者问题**: 使用两个信号量控制缓冲区的空/满状态: ```c sem_t empty, full; sem_init(&empty, 1, BUFFER_SIZE); // 初始空槽数量 sem_init(&full, 1, 0); // 初始满槽数量 // 生产者 sem_wait(&empty); // 等待空槽 /* 生产数据放入缓冲区 */ sem_post(&full); // 增加满槽计数 // 消费者 sem_wait(&full); // 等待满槽 /* 取出缓冲区数据 */ sem_post(&empty); // 增加空槽计数 ``` --- #### **5. 实战示例:有名信号量同步进程** ```c #include <fcntl.h> #include <semaphore.h> #include <stdio.h> #include <unistd.h> int main() { sem_t *sem = sem_open("/demo_sem", O_CREAT, 0644, 1); // 初始值=1 if (fork() == 0) { // 子进程 sem_wait(sem); printf("Child enters critical section\n"); sleep(2); // 模拟操作共享资源 printf("Child exits critical section\n"); sem_post(sem); } else { // 父进程 sem_wait(sem); printf("Parent enters critical section\n"); sleep(1); // 模拟操作共享资源 printf("Parent exits critical section\n"); sem_post(sem); wait(NULL); sem_unlink("/demo_sem"); // 清理信号量 } return 0; } ``` **输出**: ```plaintext Parent enters critical section Parent exits critical section Child enters critical section # 子进程等待父进程释放信号量 Child exits critical section ``` --- #### **6. 信号量与信号的区别** | 特性 | 信号(Semaphore) | 信号 (Signal) | |--------------|----------------------------------|-----------------------------| | **本质** | 资源计数器 | 软件中断通知 | | **用途** | 控制共享资源访问 | 事件通知/异常处理 | | **通信方向** | 无明确发送方/接收方 | 有明确发送方(如`kill()`) | | **同步机制** | 内置阻塞/唤醒队列 | 依赖信号处理函数 | | **数据携带** | 仅计数器数值 | 可附带`siginfo_t`信息[^2] | --- #### **7. 注意事项** 1. **死锁预防**:避免循环等待(如进程A持有信号量S1等待S2,进程B持有S2等待S1) 2. **信号量泄漏**:确保`sem_close()`/`sem_unlink()`配对调用 3. **原子性保障**:P/V 操作是原子指令,确保计数器更新不可分割 4. **优先级反转**:高优先级进程可能因等待低优先级进程持有的信号量而阻塞 > 信号量是实现进程同步的底层原语,在数据库连接池、线程池等资源受限场景中广泛应用[^4]。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值