[linux] 进程间信号

Linux操作系统中的信号处理与应用
本文详细介绍了Linux操作系统中的信号机制,包括信号的定义、产生、处理方式,以及如何通过命令行和编程接口(如`kill`、`alarm`、`raise`等)来发送和处理信号。特别讨论了信号的默认行为、屏蔽和捕捉,以及`SIGCHILD`信号在处理子进程退出时的作用。同时,文章通过实例展示了如何自定义信号处理函数以实现特定的行为。

信号在生活中无处不在,上下课铃,红绿灯,闹钟等,这一切都是信号,可是我们是如何识别信号的呢?无非就是有人定义将信号与意义绑定,并灌输。在操作系统中也存在信号,那么我们一起来看看吧
在这里插入图片描述

1 理解信号

信号不管是在生活中还是在操作系统中其实都可以简单看为三部分

  1. 是什么
  2. 为什么
  3. 怎么处理

而在操作系统层面来看并不需要知道为什么(毕竟信号也是工程师定义的),他只需要知道是啥信号和收到信号后要如何处理,其实生活中收到信号也是如此,过马路红灯停,绿灯行,并不会有人问为啥。


1.1 见识Linux中的信号

在linux中查看信号需要用到命令 kill -l,一共62个信号,1~31为默认信号,24~64为实时信号
在这里插入图片描述

浅谈: 默认信号 VS 实时信号

普通信号会丢失,实时信号不会丢失,也就是是否要存储信号,后文会提

在Linux下的全部信号都是宏,也就是一个标志位,这样做的目的节省空间,用一个bit位标识一种状态,默认信号存放在该路径下/usr/include/bits/signum.h

在这里插入图片描述

1.2 如何产生信号

一切事情都是有前因后果的,当然信号也是,先要如何产生才可以知道如何处理

  1. 键盘产生
  2. 命令行向进程发送信号
  3. 调用函数向os发送请求
  4. 代码出错(访问硬件出错)

键盘产生

ctrl+c 终止进程 ctrl+\ 终止进程并保存core文件 ,ctrl+z挂起一个文件,他们其实和信信号列表一一对应,后文信号捕捉验证
在这里插入图片描述


命令行向进程发送信号

使用命令 kill -信号(数字or宏名) 进程pid
在这里插入图片描述

调用函数向os发送请求

其实就是和os说快给我发信号,C语言接口alarm(延迟xx秒让os发送14号信号给该进程)abort(让os给当前发6号信号)raise(指定让os发送x号信号)kill(给别的进程发送信号)

alarm
代码:

int main()
{
  alarm(5);
  int count=0;
  while(1)
  {

    cout<<count++<<" "<<getpid()<<endl;
    sleep(1);
  }
  return 0;
}

结果:
在这里插入图片描述

kill
在这里插入图片描述

代码:

#include<iostream>
#include<csignal>
#include<unistd.h>
#include<sys/types.h>
using namespace std;


int main()
{
  kill(1213,1);
}

结果:
在这里插入图片描述

代码出错(访问硬件出错)

子进程退出的时候可以用wait函数等待回收他的资源,且也可以知道他的退出码,core dump ,信号 。
在这里插入图片描述

代码:

#include<iostream>
#include<csignal>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>

using namespace std;


int main()
{
  pid_t id=fork();
  while(id==0)
  {
    //child
    int count=0;
    while(count!=5)
    {
      count++;
      cout<<"I am child proc"<<endl;
      sleep(1);
    }
    //除0错误
	//int a =1;
    //int b=0;
    //a/=b;

    //野指针
    int *c=0x000000;
    *c=1;
  }
  int status=0;
  waitpid(id,&status,0);
  cout<<(status&0x7f)<<endl;
  return 0;
}

结果:
在这里插入图片描述

os是如何知道代码出错而发对应的信号呢?

除0对应的硬件 == cup中的状态寄存器
野指针 == 内存/页表MMU
os是软硬件的管理者,自然就知道哪里出错,在根据硬件在对进程发送信号

core dump


1.3 信号的处理

在现实中我们处理信号一般有三种方式

  1. 默认(识别信号马上处理 , 红绿灯)
  2. 忽略 (识别信号不处理,打游戏妈妈叫吃饭)
  3. 捕捉自定义 (识别后用自己的方法,听到闹钟声跳个舞🐶🐶)

在操作系统中也是如此处理信号也是以上三种方式,且也知道一个进程收到信号不一定马上处理那么不处理就要保存起来(该进程的PCB中)。按照老的套路先描述在组织,这里的组织方式就是一个位图

在这里插入图片描述

当os发送信号给进程时,看信号是xx号并在对应的pending位图中,把xx号位置设为1 ,再看看block位图xx号位置是否设为1,如果为1屏蔽信号,不然直接执行方法。其实信号被屏蔽还有一个专业的名词(未决),信号没有被屏蔽执行的方法(递达)

递达信号流程图
在这里插入图片描述


1.3.1 屏蔽信号与获取pending

在C语言中有一个类型sigset_t 可以表示一个每个信号的有效与无效,也就是一个位图,我们可以用它和信号集操作函数等来告诉os屏蔽那些信号

信号集操作函数

 #include <signal.h>
int sigemptyset(sigset_t *set);//初始化 ”位图“ 为0
int sigfillset(sigset_t *set);//初始化 ”位图“ 为1
int sigaddset (sigset_t *set, int signo);//给 “位图”指定位置为1
int sigdelset(sigset_t *set, int signo);//给 “位图”指定位置为0
int sigismember(const sigset_t *set, int signo);//判断 ”位图“指定位置是否为1

上述的接口都是语言接口,也就说明,我们还没有告诉os要屏蔽那个信号,那么就要用到系统接口,sigprocmask

sigprocmask使用介绍
在这里插入图片描述

使用这个接口就可以让os吧pcb中block变成自己所设置位图的样子,屏蔽信号

sigpending使用介绍

在这里插入图片描述
输入形参数,获取pending当前状态

代码
实现屏蔽信号获取pending,删除屏蔽,抵达信号

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;


void showpending(sigset_t * set)
{
	for(int i =1; i<=31;i++)
	{
		if(sigismember(set,i))
		{
			cout<<'1';
		}
		else
		{
			cout<<'0';
		}
	}
	cout<<endl;
}
int main()
{
	sigset_t set,oset;
	sigemptyset(&set);
	sigemptyset(&oset);


	sigaddset(&set,2);
	sigprocmask(SIG_SETMASK,&set,&oset);

	int count=0;
	while(1)
	{
		sigpending(&set);
		showpending(&set);
		sleep(1);
    count++;
		if(count==7)
		{
			break;
		}
	}


	return 0;
}

结果:


在这里插入图片描述


1.3.2 信号捕捉(自定义处理)

上述说过信号的处理方式, 默认,忽略,捕捉,其实捕捉就是自定义信号(递达),也就是修改pcb中的hander函数数组中的方法,系统接口signal ,sigaction

接口介绍:
,

接口使用

signal

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;


void hander(int signo)
{
  cout<<"signo: "<<signo<<endl;
}
int main()
{
  signal(2,hander);
  int count=0;
  while(1)
  {
    cout<<count++<<endl;
    sleep(1);
  }



  return 0;
}

结果:
在这里插入图片描述


sigaction

#include<iostream>
#include<unistd.h>
#include<signal.h>
#include<unistd.h>
using namespace std;


void hander(int signo)
{
  cout<<"signo: "<<signo<<endl;
}
int main()
{

  //signal(2,hander);
   struct sigaction act;
   struct sigaction oact;
  act.sa_handler=hander;
  sigaction(2,&act,&oact);
  int count=0;
  while(1)
  {
    cout<<count++<<endl;
    sleep(1);
  }



  return 0;
}

结果:
在这里插入图片描述

既然可以屏蔽捕捉信号,那我全部屏蔽我这个进程不就杀不死了吗?

既然你可以想到,没错工程师也想到了,在linux下有SIGKILL与SIGTOP信号是无法捕捉的和屏蔽,专门防止你小心思


问信号时如何捕捉的呢?

抽象理解,捕捉函数是一个地雷,而触发的条件就是信号。

回顾进程虚拟地中空间:
在这里插入图片描述

之前说每个进程都有自己的一张独有的页表,其实还不太准确,其实有俩张一张是用户级页表,一张是系统级页表(每个进程共享),在执行代码时,没有遇到系统接口前用的都是用户级页表,当执行到系统接口时转换为系统页表

图解捕捉捕捉的流程图:
捕捉的过程其实很像数学中的一个符号

在这里插入图片描述


1.4 SIGCHILD

之前说过子进程退出的时候父进程会回收子进程,一方面是防止内存泄漏,一方面是获取子进程的退出结果。但等待,父进程就在阻塞等待里(or非阻塞轮询),着都让父进程无法一心一意的执行自己的代码?那么如何解决呢?

解决方案

子进退出的时候会给父进程发送一个信号SIGCHILD,🙉🙉🙉,那配合signal不就…………子进程退出发送信号,捕捉,改变SIGCHILD的函数实现方法

代码:

using namespace std;

void hander(int signo)
{
	cout<<signo<<endl;
  pid_t id;
	while((id=wait(NULL))>0)
	{
		printf("wait pid %d success",id);
	}


}
int main()
{

	signal(17,hander);
	pid_t id =fork();
	if(id==0)
	{
		//child
		int count=4;
		while(count>0)
		{
			count--;
			cout<<count<<" "<<getpid()<<endl;
			sleep(1);
		}
		exit(1);
	}

	while(true)
	{
		cout<<"I am father proce"<<endl;
		sleep(1);
	}

	return 0;
}

结果:
在这里插入图片描述

<think> 首先,用户的问题是:“我想了解在Linux环境下进程间信号量、互斥锁、文件锁和资源锁的相关信息 请问Linux环境下进程间信号量、互斥锁、文件锁和资源锁的介绍”。 用户提到了几个锁机制:信号量、互斥锁、文件锁和资源锁。从引用中,我有一些参考: - 引用[1]:讨论了信号量和互斥锁的区别,强调互斥锁用于互斥(单一资源),信号量用于多资源同步。 - 引用[2]:引入了信号量、管程与互斥锁,定义了核心术语如进程同步、信号量、管程、互斥锁。 - 引用[3]:提到了Linux进程间通信中的共享内存同步方法,使用互斥锁和信号量。 用户指定了“进程间”,意思是进程间通信(IPC)。所以,我需要聚焦于Linux环境下用于进程间同步的机制。 关键术语: 1. **信号量(Semaphore)**:用于进程间同步,通常控制多个资源的访问。在Linux中,常见于IPC机制如System V信号量或POSIX信号量。 2. **互斥锁(Mutex)**:也称为Mutual Exclusion Lock,主要用于线程间同步,但也可以扩展到进程间。在Linux中,互斥锁通常用于线程,但可以通过共享内存实现进程间互斥锁。 3. **文件锁(File Lock)**:用于文件访问的同步,防止多个进程同时写入同一文件。常见的有记录锁(Record Locking)。 4. **资源锁(Resource Lock)**:这可能是一个泛称,表示对特定资源的锁机制。在上下文中,可能指代更一般的锁概念,如互斥锁或文件锁。 用户说“进程间”,所以我需要确保讨论的是进程级别的同步,而不是线程级别的。 回顾系统指令: - 所有行内数学表达式用$...$格式。 - 独立公式用$$...$$并单独成段。 - LaTeX语法正确。 - 使用中文回答。 - 生成相关问题。 - 回答中引用的段落末尾添加引用标识,例如[^1]。 另外,回答结构要清晰,帮助用户逐步解决问题。 结构化回答: 1. **介绍**:简要概述主题。 2. **详细解释每个机制**:针对每个锁机制(信号量、互斥锁、文件锁、资源锁),解释其在Linux环境下的进程间应用。 - 信号量:POSIX或System V信号量用于IPC。 - 互斥锁:如何在进程间实现互斥锁?可能需要共享内存。 - 文件锁:使用fcntl或flock实现文件锁。 - 资源锁:这可能不是特定机制;解释为一般概念或指代其他锁。 3. **区别和联系**:对比这些机制,类似于引用[1]的做法。 4. **实际应用和示例**:给出简单代码示例或伪代码。 5. **结束**:总结并生成相关问题。 确保引用正确:用户提供了引用[1],[2],[3],所以我应该在回答中引用它们。 生成相关问题:在末尾添加相关问题部分。 现在,大纲: - **引言**:解释Linux环境下进程间同步的重要性。 - **信号量**:定义、在IPC中的应用(如共享内存同步)、POSIX信号量实现。 - **互斥锁**:线程级vs进程级;如何在进程间实现(e.g., 使用共享内存和pthread mutex属性)。 - **文件锁**:描述文件锁机制,如建议锁(fcntl)或强制锁。 - **资源锁**:这可能是个泛称;解释为对共享资源的锁机制,以上机制都属于此类。 - **对比总结**:表格或列表对比信号量、互斥锁、文件锁。 - **相关问题**:生成3-5个相关问题。 在回答中,自然地融入引用: - 当提到信号量和互斥锁的区别时,引用[^1]。 - 当讨论共享内存同步时,引用[^3]。 - 引用[^2]提供了一般概念。 数学表达式:如果有数学公式,如信号量操作描述,使用LaTeX格式。 例如,信号量操作P()和V()可以提及,但可能不需要复杂公式。 确保语言中文。 最终回答结构: 1. 标题或开头陈述主题。 2. 逐步讲解每个机制。 3. 结尾相关问题。 </think> ### Linux环境下进程间信号量、互斥锁、文件锁和资源锁的介绍 在Linux操作系统中,进程间同步是确保多个进程访问共享资源时数据一致性和程序正确性的关键机制。共享资源如内存、文件或其他硬件设备,如果不加控制,可能导致竞争条件(Race Condition)或死锁(Deadlock)。Linux提供了多种同步机制,包括信号量、互斥锁、文件锁和资源锁(即更一般的锁概念)。这些机制各有特点,适用于不同场景。我将逐步介绍每个机制的概述、用途、Linux特定实现以及它们之间的区别,帮助您全面理解。注意,由于这些机制在进程间通信(IPC)中尤为重要,我将聚焦于进程级别(而非线程级别)的应用。 #### 1. **信号量(Semaphore)** 信号量是一种计数器型同步机制,用于控制多个进程对共享资源的访问。它允许多个线程或进程同时访问不同资源实例,特别适合于协调工作流或管理有限资源池。在Linux中,信号量主要用于IPC,支持POSIX信号量(如`sem_open`, `sem_wait`, `sem_post`)或System V信号量(如`semget`, `semop`)。 - **用途**:信号量适用于同步多个同类资源。例如,在共享内存同步中,信号量可以协调读写进程(如一个进程写入数据,另一个进程读取数据)。信号量的计数器值表示可用资源数量:当值为正时,进程可以访问资源;为零时,进程被阻塞。这与互斥锁不同,信号量允许多个进程同时访问不同资源[^1]。 - **Linux实现**: - **POSIX信号量**:更现代和轻量级,通常存储在共享内存中用于进程间同步。例如,在共享内存结构中定义信号量(如`sem_t`类型),并通过`sem_init`初始化和操作。 - **System V信号量**:传统机制,支持更复杂的操作集合(如原子增减计数器)。常用于数据库或文件系统同步。 - 示例代码(基于POSIX信号量):以下伪代码展示了如何在共享内存中使用信号量实现进程间互斥(假设共享内存已创建)。 ```c #include <semaphore.h> #include <sys/shm.h> typedef struct { sem_t sem; // 信号量计数器 char data[100]; } ShmData; int main() { // 创建共享内存... ShmData *shm = ...; // 映射共享内存 sem_init(&shm->sem, 1, 1); // 初始化信号量,计数器为1(类似互斥锁) sem_wait(&shm->sem); // P操作:获取信号量(如果计数器为0,则阻塞) // 访问共享资源... sem_post(&shm->sem); // V操作:释放信号量 return 0; } ``` - **优点与局限**:信号量灵活(计数器可任意整数),但实现较复杂,容易引发优先级反转问题。在Linux中,信号量常用于生产者-消费者模型或进程池管理[^3]。 #### 2. **互斥锁(Mutex)** 互斥锁是一种二元锁机制,确保同一时刻只有一个进程访问共享资源(即互斥)。在Linux中,互斥锁原本设计用于线程同步(如POSIX线程库的`pthread_mutex_t`),但可以通过共享内存扩展到进程间。互斥锁适用于保护单一资源(如一个共享变量或临界区),而不能用于多资源控制。 - **用途**:互斥锁的核心是提供互斥性——当进程持有锁时,其他试图获取锁的进程会被阻塞,直到锁被释放。这避免了数据竞争。在进程间场景下,互斥锁常用于保护共享内存或共享文件区域。信号量计数器只有0和1时,类似于互斥锁,但互斥锁更轻量且专为互斥设计[^1]。 - **Linux实现**: - **进程间互斥锁**:使用`pthread_mutexattr_setpshared`属性设置互斥锁为“进程共享”(PTHREAD_PROCESS_SHARED),并通过共享内存分配锁对象。这样,多个进程可以通过同一内存地址访问互斥锁。 - 示例代码(基于POSIX互斥锁):以下伪代码展示如何在共享内存中实现进程间互斥。 ```c #include <pthread.h> #include <sys/shm.h> typedef struct { pthread_mutex_t mutex; // 互斥锁 int counter; } SharedData; int main() { // 创建共享内存... SharedData *data = ...; // 映射共享内存 pthread_mutexattr_t attr; pthread_mutexattr_init(&attr); pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); // 设为进程共享 pthread_mutex_init(&data->mutex, &attr); // 初始化互斥锁 pthread_mutex_lock(&data->mutex); // 获取锁(阻塞其他进程) // 访问共享资源(如data->counter++) pthread_mutex_unlock(&data->mutex); // 释放锁 return 0; } ``` - **优点与局限**:互斥锁简单高效(开销低),但仅用于单一资源保护。在进程间使用时,需确保共享内存正确初始化,否则可能导致未定义行为。Linux内核还提供`futex`(Fast Userspace Mutex)优化性能[^2]。 #### 3. **文件锁(File Lock)** 文件锁是Linux中专门用于文件访问同步的机制,防止多个进程同时写入或读取同一文件区域。它基于文件描述符操作,支持建议锁(Advisory Lock)或强制锁(Mandatory Lock)。文件锁适用于进程间文件共享场景(如日志文件或数据库文件)。 - **用途**:文件锁分为两种类型: - **记录锁(Record Lock)**:锁定文件的特定区域(字节范围),使用`fcntl`系统调用实现。 - **文件级锁**:使用`flock`系统调用锁定整个文件,但仅限于建议锁(进程自愿遵守)。 强制锁需要文件系统支持(如mount时启用`mand`选项),并强制所有进程遵守锁规则。文件锁不适用于内存资源,只针对文件I/O。 - **Linux实现**: - `fcntl(F_SETLK/F_SETLKW)`:用于设置非阻塞或阻塞锁,锁定字节范围。 - `flock`:简单但不可移植,仅支持建议锁。 - 示例代码(使用`fcntl`实现进程间文件锁): ```c #include <fcntl.h> #include <unistd.h> int main() { int fd = open("shared_file.txt", O_RDWR); struct flock lock; lock.l_type = F_WRLCK; // 写锁 lock.l_whence = SEEK_SET; lock.l_start = 0; // 锁定文件起始位置 lock.l_len = 100; // 锁定100字节 fcntl(fd, F_SETLKW, &lock); // 阻塞获取锁(如果锁被占用则等待) // 写入文件... lock.l_type = F_UNLCK; // 解锁 fcntl(fd, F_SETLK, &lock); close(fd); return 0; } ``` - **优点与局限**:文件锁简单易用,但仅适用于文件资源。建议锁依赖于进程合作;强制锁更安全但性能开销大。在多个进程共享配置文件时常用[^3]。 #### 4. **资源锁(Resource Lock)** 资源锁是一个泛指概念,表示对任意共享资源(如内存、文件、设备等)的同步机制。在Linux语境下,它通常指代上述信号量、互斥锁或文件锁的统称,而不是一个独立实体。资源锁的核心是抽象出同步需求,允许开发者根据资源类型选择合适的机制。 - **用途**:资源锁覆盖所有共享资源保护场景: - 如果资源是内存块,使用信号量或互斥锁。 - 如果资源是文件,使用文件锁。 - 在分布式系统中,资源锁可能涉及更高级机制如分布式锁(e.g., Redis或ZooKeeper实现)。 资源锁强调通用性,但实现时需适配具体资源特性。 - **Linux实现**:资源锁本身无特定API;开发者需结合其他机制: - 例如,使用互斥锁保护共享内存变量,或使用文件锁保护文件资源。 - 在进程间通信中,资源锁常通过IPC对象(如信号量或共享内存)实现同步。 示例:假设一个资源池(如打印机设备),可用信号量管理可用打印机数量。 - **优点与局限**:资源锁概念灵活,但需开发者手动选择和实现。Linux内核提供了`futex`等底层原语支持高效资源锁,但错误使用可能导致死锁或饥饿。 #### 机制对比与总结 下表总结Linux环境下这些进程间同步机制的关键区别: | 机制 | 主要用途 | 适用资源 | Linux API示例 | 计数器/类型 | 优点 | 局限 | |------------|------------------------------|--------------------|---------------------------|---------------------------|--------------------------------|--------------------------------| | **信号量** | 控制多资源访问 | 共享内存、进程池 | `sem_wait`, `sem_post` | 任意非负整数 | 灵活,支持多进程协调 | 实现复杂,易引发死锁 | | **互斥锁** | 单一资源互斥 | 共享变量、临界区 | `pthread_mutex_lock` | 二元(0或1) | 轻量高效,低开销 | 仅适用于线程或进程间共享内存 | | **文件锁** | 文件区域同步 | 文件I/O | `fcntl`, `flock` | N/A(基于文件区域) | 文件专属,简单易用 | 仅限文件,建议锁需进程合作 | | **资源锁** | 通用资源共享保护(泛指概念) | 任意资源 | 无特定API,依赖其他机制 | N/A | 概念通用,可适配各种场景 | 需开发者选择底层机制实现 | 在Linux中,这些机制通常结合使用:例如,在共享内存同步中,信号量或互斥锁用于内存访问,文件锁用于相关文件。实际选择时,需考虑资源类型、性能需求和系统约束。信号量和互斥锁都源于经典的同步问题(如Dijkstra的$P()$和$V()$操作),其在Linux IPC中的实现优化了进程调度效率[^1][^2]。 #### 结束语 Linux提供了丰富的进程间同步机制,确保多进程环境下的数据一致性。信号量适用于多资源控制,互斥锁适合单一资源保护,文件锁专用于文件I/O,而资源锁作为泛称整合了这些概念。正确使用这些机制能避免常见问题如死锁(例如,使用超时机制或层次锁)。实践中,推荐优先使用POSIX标准API(如POSIX信号量),因为它们在性能和可移植性上更优[^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值