Linux进程通信--信号量

本文深入解析Linux中的信号量机制,包括其基本概念、结构定义、关键API如semget、semctl及semop的使用方法,并提供简单示例帮助理解进程间如何通过信号量实现资源共享。

Semaphores 信号量

主要用于进程对共享资源的互斥访问。为了取得对一个共享资源,进程测试信号量的值,如果为正,表示还有资源可以访问,该进程对信号量加减一。如果为0表示没有资源可以用,该进程应当休眠等待信号量的值再次为正。

 

Linux中的信号量使用起来是比较麻烦的。在这里,信号量是以集合形式存在的,创建时要给出信号量的数目,并且信号量集合即使没有任何进程访问时,也是存在于系统中的。要注意对其的销毁工作。

每个信号量由以下结构体定义:

struct {

unsigned short  semval; //信号量的值,总是>=0

pid_t  sempid;  //最后一次操作信号量的进程id

unsigned short semncnt; // semval > curval时进程等待

unsigned short semzcnt; //semval == 0时,进程等待

};

每个信号量集合由semid_ds定义:

struct semid_ds {

struct ipc_perm sem_perm;

unsigned short sem_nsems; /* # of semaphores in set */

time_t sem_otime; /* last-semop() time */

time_t sem_ctime; /* last-change time */

};

struct ipc_perm {

uid_t uid; /* owner's effective user id */

gid_t gid; /* owner's effective group id */

uid_t cuid; /* creator's effective user id */

gid_t cgid; /* creator's effective group id */

mode_t mode; /* access modes */

};



获取信号量集合的标识符:

#inlcude <sys/sem.h>

int semget(key_t key,int nsems,int flag);

该函数返回给定键的信号量对应的标识符。键与标识符实际上都指向了该信号量集合,但是标识符只是在该进程内部可见,而键是所有进程都可以使用获取的。如果其他进程也是通过该标识符对信号量集合进行访问,key可以指定为IPC_PRIVATE或者IPC_NEW,比如父子进程的情况。多数情况下,其他进程不方便使用另一进程的标识符,可以调用semget函数指定同一的key,以访问信号量集合。flag在创建的时候低9bit表示权限,和creat创建时需要的权限位定义相同。当flag为0时只表示获取该信号量集合,不进行创建。

 

#include <sys/ipc.h>

key_t ftok(const char *path, int id);

该函数用以创建key。path应当是已经存在的文件,id理解为项目id。注意如果使用同一项目id,对于不同的路径可能会产生相同的key。

 

#include <sysy/sem.h>

int semctl(int semid, int semnum,int cmd,/*union semun arg */);

用以操作信号量集合,semget之后,应该使用semctl进行初始化。

注意下面这个结构体需要由用户自行定义。

union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };
cmd主要有SETVAL IPC_STAT IPC_SET GETALL SETALL GETPID

IPC_RMID 删除该信号量集合。此时正在使用该信号量集合的其他进程对齐操作时会返回EIRM.



#include <sys/sem.h>

int semop(int semid
, struct sembuf semoparray
[],size_t nops
); //原子操作

struct sembuf {

unsigned short sem_num; /* member # in set (0, 1, ..., nsems-1) */

short sem_op; /* operation (negative, 0, or positive) */

short sem_flg; /* IPC_NOWAIT, SEM_UNDO */

};



sem_op 为正:进程释放占用资源,sem_op值加到信号量上去.

为负:如果信号量值大于等于sem_op绝对值,则减去。如果小于,如果指定了IPC_NOWAIT,semop出错返回。如果未指定,则调用进程被挂起,直到符合给定的条件或者信号量被删除/进程从其他信号退出。

为0:表示等待到信号量变为0.



如果在sem_op小于零时指定了SEM_UNDO,即使程终止时未主动释放资源,内核会释放加入SEM_UNDO时占用的资源。



简单的实例代码:

进程a,创建,占用,进程b等待。

#include <sys/ipc.h>

#include <sys/sem.h>

#include <errno.h>

#include <stdio.h>

union semun {

int val; /* Value for SETVAL */

struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */

unsigned short *array; /* Array for GETALL, SETALL */

struct seminfo *__buf; /* Buffer for IPC_INFO

(Linux-specific) */

};



int main()

{

key_t sem_key = -1;

if((sem_key = ftok("sem2.tmp",1)) < 0)

{

perror("ftok error");

return -1;

}

int sid = -1;

if((sid = semget(sem_key,1,660)) < 0 )

{

perror("semget error");

return -1;

}



union semun arg = {0};

arg.val = 1;

if(semctl(sid,0,SETVAL,arg.val) < 0)

{

perror("error to set sem");

return -1;

}



struct sembuf arr[1];

arr[0].sem_num = 0;

arr[0].sem_op = -1;

arr[0].sem_flg = 0;



if(semop(sid,arr,1) < 0)

{

perror("semop error");

return -1;

}



sleep(5);



arr[0].sem_op = 1;



if(semop(sid,arr,1) < 0)

{

perror("semop error");

return -1;

}



printf("progam1 over/n");



return 0;

}

//进程b

#include <sys/ipc.h>

#include <sys/sem.h>

#include <errno.h>

#include <stdio.h>



int main()

{



key_t sem_key = -1;

if((sem_key = ftok("sem.tmp",1)) < 0)

{

perror("ftok error");

return -1;

}

int sid = -1;

if((sid = semget(sem_key,1,0)) < 0 )

{

perror("semget error");

return -1;

}



struct sembuf arr[1];

arr[0].sem_num = 0;

arr[0].sem_op = -1;

arr[0].sem_flg = 0;



if(semop(sid,arr,1) < 0)

{

perror("semop error");

return -1;

}



arr[0].sem_op = 1;



if(semop(sid,arr,1) < 0)

{

perror("semop error");

return -1;

}



printf("progam2 over/n");



return 0;

}



<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、付费专栏及课程。

余额充值