信号量/灯(system V IPC)

 

信  号  量

信号量的原理是一种数据操作锁的概念,它本身不具备数据交换的功能,而是通过控制其他的通信资源(文件,外部设备等)来实现进程间通信。信号量本身不具备数据传输的功能,其只是一种外部资源的标识。本小节将深入介绍信号量的操作。

一、信号量的概念

信号量本身不具备数据传输的功能,它只是一种外部资源的标识,通过该标识可以判断外部资源是否可用,信号量在此过程中负责数据操作的互斥、同步等功能。

当请求一个使用信号量来表示的资源时,进程需要先读取信号量的值,以判断相应的资源是否可用。当信号量的值大于0时,表明有资源可以请求。等于0时,说明现在无可用资源,所以进程会进入睡眠状态直至有可用资源时。

当进程不再使用一个信号量控制的共享资源时,此信号量的值增1,对信号量的值进行增减操作均为原子操作,这是由于信号量主要的作用是维护资源的互斥或多进程的同步访问。而在信号量的创建以及初始化时,不能保证操作均为原子。

同其他的IPC对象一样,内核对每一个信号量集都会设置一个shmid_ds结构(详细介绍见14.3.4),同时用一个无名结构来标识一个信号量。简要定义如下:

struct {

    unsigned short  semval;    

    pid_t           sempid;    

    unsigned short  semncent;  

    unsigned short  semzcnt;   

    ...

    ...

}

二、信号量的创建

同共享内存一样,系统中同样需要为信号量集定制一系列专有的操作函数(semget,semctl等)。系统命令ipcs可查看当前的系统IPC的状态,在命令后使用-s参数。使用函数semget可以创建或者获得一个信号量集ID,函数原型如下:

#include <sys/shm.h>

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

函数中参数key用来变换成一个标识符,每一个IPC对象与一个key相对应。当新建一个共享内存段时,使用参数flag的相应权限位对ipc_perm结构中的mode域赋值,对相应信号量集的shmid_ds初始化的值如表14-6所示。

表14-6  shmid_ds结构初始化值表

ipc_perm结构数据

初    值

ipc_perm结构数据

初    值

Sem_otime

0

Sem_nsems

Nsems

Sem_ctime

系统当前值

参数nsems是一个大于等于0的值,用于指明该信号量集中可用资源数(在创建一个信号量时)。当打开一个已存在的信号量集时该参数值为0。函数执行成功,则返回信号量集的标识符(一个大于等于0的整数),失败,则返回–1。函数semop用以操作一个信号量集,函数原型如下:

#include <sys/sem.h>

int semop( int semid, struct sembuf semoparray[], size_t nops );

函数中参数semid是一个通过semget函数返回的一个信号量标识符,参数nops标明了参数semoparray所指向数组中的元素个数。参数semoparray是一个struct sembuf结构类型的数组指针,结构sembuf来说明所要执行的操作,其定义如下:

struct sembuf{

    unsigned short  sem_num;   

    short       sem_op;    

    short       sem_flg;       

}

在sembuf结构中,sem_num是相对应的信号量集中的某一个资源,所以其值是一个从0到相应的信号量集的资源总数(ipc_perm.sem_nsems)之间的整数。sem_op指明所要执行的操作,sem_flg说明函数semop的行为。sem_op的值是一个整数,如表14-7所示,列出了详细sem_op的值及所对应的操作。

表14-7  sem_op值详解

Sem_op

操    作

正数

释放相应的资源数,将sem_op的值加到信号量的值上

0

进程阻塞直到信号量的相应值为0,当信号量已经为0,函数立即返回。如果信号量的值不为0,则依据sem_flg的IPC_NOWAIT位决定函数动作。sem_flg指定IPC_NOWAIT,则semop函数出错返回EAGAIN。sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生。信号量值为0,将信号量的semzcnt的值减1,函数semop成功返回;此信号量被删除(只有超级用户或创建用户进程拥有此权限),函数smeop出错返回EIDRM;进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR

负数

请求sem_op的绝对值的资源。如果相应的资源数可以满足请求,则将该信号量的值减去sem_op的绝对值,函数成功返回。当相应的资源数不能满足请求时,这个操作与sem_flg有关。sem_flg指定IPC_NOWAIT,则semop函数出错返回EAGAIN。sem_flg没有指定IPC_NOWAIT,则将该信号量的semncnt值加1,然后进程挂起直到下述情况发生:当相应的资源数可以满足请求,该信号的值减去sem_op的绝对值。成功返回;此信号量被删除(只有超级用户或创建用户进程拥有此权限),函数smeop出错返回EIDRM:进程捕捉到信号,并从信号处理函数返回,在此情况将此信号量的semncnt值减1,函数semop出错返回EINTR

下面实例演示了关于信号量操作的基本流程。程序中使用semget函数创建一个信号量集,并使用semop函数在这个信号集上执行了一次资源释放操作。并在shell中使用命令查看系统IPC的状态。

(1)在vi编辑器中编辑该程序。

程序清单14-10  create_sem.c 使用semget函数创建一个信号量

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/sem.h>

#include <stdio.h>

#include <stdlib.h>

int main( void )

{

    int sem_id;

    int nsems = 1;

    int flags = 0666;

    struct sembuf buf;

   

    sem_id = semget(IPC_PRIVATE, nsems, flags); /*创建一个新的信号量集*/

   

    if ( sem_id < 0 ){

        perror( "semget ") ;

        exit (1 );

    }

    /*输出相应的信号量集标识符*/

    printf ( "successfully created a semaphore : %d/n", sem_id );  

    buf.sem_num = 0;                                 /*定义一个信号量操作*/

    buf.sem_op = 1;                                 /*执行释放资源操作*/

    buf.sem_flg = IPC_NOWAIT;                       /*定义semop函数的行为*/

   

    if ( (semop( sem_id, &buf, nsems) ) < 0) {      /*执行操作*/

        perror ( "semop");

        exit (1 );

    }

   

    system ( "ipcs -s " );                          /*查看系统IPC状态*/

    exit ( 0 );

}

(2)在shell中编译该程序如下:

$gcc create_sem.c–o create_sem

(3)在shell中运行该程序如下:

$./ create_sem

------ Semaphore Arrays --------

key        semid      owner      perms      nsems    

0x00000000 294911     root      666        1        

successfully created a semaphore : 294911

在上面程序中,用semget函数创建了一个信号量集,定义信号量集的资源数为1,接下来使用semop函数进行资源释放操作。在程序的最后使用shell命令ipcs来查看系统IPC的状态。

%注意:命令ipcs参数-s标识查看系统IPC的信号量集状态。

三、信号量集的操作

三个IPC对象类型中,信号量集的操作函数相对于其他两个类型的操作函数要复杂得多,当然信号量的应用也比其他两个更广泛些。像共享内存的操作一样,信号量也有自己的专属操作函数semctl,函数原型如下:

#include <sys/sem.h>

int semctl( int sem_id, int semnu, int cmd [, union semun arg]);

函数中参数sem_id是一个信号量标识符,semnum指定sem_id的信号集中的某一个信号灯,其类似于在信号量集资源数组中的下标,用来对指定资源进行操作。参数cmd定义函数所要进行的操作。其取值以及表达的意义如表14-8所示。

表14-8  cmd值详解

cmd的取值

操    作

GETVAL

返回成员semnum的semval值

SETVAL

使用arg.val对该信号量的semnum.sempid赋值(需要参数arg)

GETPID

返回成员semnum的sempid值

GETNCNT

返回成员semnum的semncnt值

GETZCNT

返回成员semnum的semzcnt值

GETALL

将该信号量集的值赋值到arg.array(需要参数arg)

SETALL

使用arg、array数组中的值对信号量集赋值(需要参数arg)

IPC_RMID

删除信号量集。此操作只能由具有超级用户的进程或信号量集拥有者的进程执行,这个操作会影响到正在使用该信号量集的进程

IPC_SET

设置此信号量集的sem_perm.uid、sem_perm.gid以及sem_perm.mode的值。此操作只能由具有超级用户的进程或信号量集拥有者的进程执行

SPC_STAT

(需要参数arg)

函数中参数arg为可选参数,根据参数cmd的相关操作来选择使用,其定义如下:

union semun{

    int                 val;       

    struct semid_ds         *buf ; 

    unsigned short      *array;

};

函数成功返回值大于等于0(当semctl的操作为GET操作时返回相应的值,其余返回0),失败返回–1并设置错误变量errno。

下面实例演示了如何使用semctl函数。程序中先使用semget函数创建了一个新的信号量集,然后通过shell命令查看系统IPC的状态,再调用一次semctl函数做删除操作,并查看系统IPC的状态

(1)在vi编辑器中编辑该程序如下:

程序清单14-11  ctl_sem.c 使用semctl删除信号量

#include <sys/sem.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <stdio.h>

#include <stdlib.h>

int main( void )

{

    int sem_id ;

    int nsems = 1;

    int flags = 0666;

   

    semid = semget ( IPC_PRIVATE, nsems, flags );   /*创建一个信号量集*/

   

    if ( sem_id < 0 ){                              /* 创建信号两失败 */

        perror ( "semget" );

        exit ( 1 );

    }

    printf ( "successfully created a semaphore: %d /n", sem_id );
                                                    /*输出创建的信号量的ID */

   

    system ( "ipcs -s" );                           /*查看系统IPC状态*/

   

    if ( (semctl (semid, 0, IPC_RMID)) < 0 ) {      /* 删除指定信号量集*/

        perror ( "semctl" );

        exit (1 );

    }

    else {

        printf ( "semaphore removed /n");

        system ( "ipcs -s ");                       /*查看系统IPC状态*/

    }

   

    exit ( 0 );

}

(2)在shell中编译该程序如下:

$gcc ctl_sem.c–o ctl_sem

(3)在shell中运行该程序如下:

$./ ctl_sem

------ Semaphore Arrays --------

key        semid      owner      perms      nsems    

0x00000000 294911     root      666        1        

0x0056a4d5 327681     root      600        1        

0x00000000 360450     root      666        1        

successfully created a semaphore: 360450

------ Semaphore Arrays --------

key        semid      owner      perms      nsems    

0x00000000 294911     root      666        1        

0x0056a4d5 327681     root      600        1        

semaphore removed  

在shell中可以调用如下命令来删除已存在的信号量:

$./ ipcrm –s  <semaphore semid>

%说明:上述程序中,使用semget函数创建一个信号量时,有可能系统中已经有了一个跟IPC_PRIVATE键关联的信号量,此时应在shell中先删除该IPC,然后再运行程序。

<think>好的,用户现在问的是POSIX信号量System-V IPC信号量的区别和联系。我需要先回顾一下这两种信号量的基本概念,然后比较它们的异同点。 首先,POSIX信号量是较新的标准,属于POSIX线程库的一部分,通常用于线程间的同步,而System-V信号量是早期的Unix系统提供的IPC机制,属于System-V IPC的一部分,用于进程间通信。用户可能是在学习操作系统中的同步机制,或者在开发多线程或分布式系统时遇到了选择问题。 接下来,我需要从几个方面进行比较:接口设计、使用场景、功能特性、内核实现、同步机制等。比如,POSIX信号量支持命名和无名两种类型,而System-V使用键值来标识。另外,POSIX的接口更简洁,而System-V的接口可能更复杂,需要更多的步骤来创建和管理。 还要注意它们的联系,比如两者都用于同步和互斥,都是系统提供的资源,都需要正确管理以避免资源泄漏。用户可能想知道在什么情况下选择哪种信号量,以及它们的优缺点。 另外,用户之前的问题涉及线程创建和参数传递,可能他们正在深入学习多线程编程,现在需要了解不同信号量机制的适用场景。因此,在回答时需要结合实际应用场景,给出使用建议,帮助用户做出选择。 需要确保回答结构清晰,分点列出区别和联系,并给出总结和使用建议。同时,要检查是否有遗漏的重要区别点,比如权限管理和生命周期管理,这些也是关键因素。 最后,生成相关问题时,要围绕用户可能的后续疑问,比如具体函数使用、性能差异、迁移建议等,确保问题相关且有帮助。</think>### **POSIX信号量System-V IPC信号量的区别与联系** --- #### **1. 基本概念** | **分类** | **POSIX信号量** | **System-V IPC信号量** | |--------------------|------------------------------------------|----------------------------------------| | **标准** | POSIX 1003.1b 实时扩展标准 | System V Unix IPC 标准(早期Unix) | | **命名方式** | 支持**命名信号量**(`sem_open`)和**无名信号量**(`sem_init`) | 通过**键值**(`key_t`)标识(`semget`) | | **适用场景** | 线程间同步、进程间同步 | 进程间同步 | --- #### **2. 核心区别** ##### **(1) 接口设计** | **特性** | **POSIX信号量** | **System-V IPC信号量** | |--------------------|------------------------------------------|----------------------------------------| | **创建与初始化** | `sem_init()`(无名)或 `sem_open()`(命名) | `semget()` + `semctl()` | | **操作函数** | `sem_wait()` / `sem_post()` | `semop()`(支持原子操作数组) | | **命名方式** | 通过路径名(如 `/my_semaphore`) | 通过 `key_t` 键值(如 `IPC_PRIVATE`) | | **权限管理** | 支持文件权限(如 `0666`) | 通过 `semid_ds` 结构体设置权限 | ##### **(2) 功能特性** | **特性** | **POSIX信号量** | **System-V IPC信号量** | |--------------------|------------------------------------------|----------------------------------------| | **支持原子操作** | 单操作(`sem_wait`/`sem_post`) | 支持多操作原子性(通过 `sembuf` 数组) | | **资源管理** | 无名信号量需手动销毁(`sem_destroy`) | 需显式删除(`semctl(..., IPC_RMID)`) | | **跨进程共享** | 无名信号量需共享内存中创建 | 天然支持进程间共享 | | **信号量类型** | 二值信号量(互斥锁)或计数信号量 | 支持多个信号量(集合) | ##### **(3) 内核实现** | **特性** | **POSIX信号量** | **System-V IPC信号量** | |--------------------|------------------------------------------|----------------------------------------| | **生命周期** | 依赖用户显式销毁(命名信号量需手动删除) | 持久化(需显式删除,否则存活至系统重启) | | **资源限制** | 无硬性限制(受系统资源约束) | 受 `SEMMSL`(最大信号量数)等限制 | --- #### **3. 核心联系** 1. **同步机制本质**: 两者均通过**计数值**控制资源访问,实现进程/线程的**互斥**与**同步**。 - `sem_wait()` / `semop(..., -1)`:计数值减 1,若为 0 则阻塞。 - `sem_post()` / `semop(..., +1)`:计数值加 1,唤醒等待进程。 2. **系统资源管理**: 均需通过系统调用由内核管理,资源泄露可能导致系统资源耗尽。 3. **跨进程通信能力**: 均可用于进程间同步(POSIX 通过命名信号量System-V 通过共享键值)。 --- #### **4. 典型使用场景对比** | **场景** | **推荐机制** | **原因** | |------------------------|------------------------------|--------------------------------------------------------------------------| | **线程间同步** | POSIX信号量 | 接口简洁,支持无名信号量(直接在共享内存中初始化)。 | | **进程间同步** | System-V 或 POSIX命名信号量 | System-V 适合传统 Unix 应用;POSIX 命名信号量更符合现代标准。 | | **复杂同步需求** | System-V 信号量 | 支持多信号量原子操作(如 `sembuf` 数组),适合复杂依赖关系。 | | **临时资源计数** | POSIX无名信号量 | 生命周期与进程绑定,无需手动清理(如线程池任务同步)。 | | **持久化资源同步** | System-V 信号量 | 信号量集合可持久化(如数据库事务锁)。 | --- #### **5. 代码示例对比** ##### **(1) POSIX信号量(命名)** ```c // 创建命名信号量 sem_t* sem = sem_open("/my_sem", O_CREAT, 0666, 1); sem_wait(sem); // P操作 sem_post(sem); // V操作 sem_close(sem); sem_unlink("/my_sem"); // 删除 ``` ##### **(2) System-V 信号量** ```c // 创建信号量集合(1个信号量) int semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT); // 初始化信号量(值为1) union semun { int val; } arg; arg.val = 1; semctl(semid, 0, SETVAL, arg); // P操作 struct sembuf sops = {0, -1, 0}; semop(semid, &sops, 1); // V操作 sops.sem_op = +1; semop(semid, &sops, 1); // 删除信号量 semctl(semid, 0, IPC_RMID); ``` --- #### **6. 选择建议** - **优先使用 POSIX 信号量**: - 现代应用推荐(接口简洁、符合多线程模型)。 - 需跨进程共享时,使用命名信号量(`sem_open`)。 - **System-V 信号量适用场景**: - 遗留系统兼容(如传统 Unix 应用)。 - 需多信号量原子操作或持久化资源管理。 --- #### **7. 注意事项** 1. **资源泄漏风险**: - POSIX 命名信号量需手动调用 `sem_unlink` 删除。 - System-V 信号量需显式调用 `semctl(..., IPC_RMID)` 删除。 2. **权限问题**: - 确保进程有权限访问信号量(通过 `0666` 等模式设置)。 3. **死锁预防**: - 避免循环等待,确保 `sem_wait`/`semop` 有对应的 `sem_post`/`semop(+1)`。 ---
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值