linux 进程通信之 信号量

时间:2022-02-19 15:14:12

信号量又名信号灯,与其他进程间通信方式大不相同,主要用途是用来保护临界资源。进程可以根据它判断是否能访问某些共享资源。除了用于访问控制外,还可以用于进程同步。


分类:

二值信号灯:信号灯的值只能取0或1,类似与互斥锁。但两者有不同:信号灯强调共享资源,只要共享资源可用,其他进程同样可以修改信号灯的值;互斥锁更强调进程,占用资源的进程使用完资源后,必须有进程本身来解锁。(我们常说的PV操作)

计数信号灯:信号灯的值可以取任意非负数。(多用于生产者消费者模型)

不管哪种信号灯,当信号灯的值为0时,会产生阻塞。


信号量使用相关API

int semget(key_t key, int nsems, int semflg);
函数作用:创建一个新信号量或取得一个已有信号量。
key:值为 IPC_PRIVATE 意味这即将创建新的信号量。,官方的说法是用ftok产生一个key(个人比较认同的做法是自己定义一个)使用。
nsems:指定打开或者新创建的信号量集中将包含信号量的数目。
nsems > 0时,创建一个新的信号量集,指定给集合中信号量的数量。
nsems == 0时,访问一个已存在的集合。
msgflg:是一组标志。
IPC_CREAT:如果信号量不存在,则创建一个共享内存。
IPC_EXCL:只有在信号量不存在的时候,新的信号量才建立,否则就产生错误。
对于这个参数一般是这样操作
#define PERM S_IRUSR | S_IWUSR | IPC_CREAT
然后把 PERM 当作semflg。
成功返回信号量集的ID,不成功返回-1,并设置errno。
注:semget函数执行成功后,就产生了一个由内核维持的类型为semid_ds结构体的信号量
集,返回值就是指向该信号量集的引索。结构体原型如下:
struct semid_ds {
struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */
struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集中的每个信号量对应其中一个数组元素 */
ushort sem_nsems; /* sem_base 数组的个数 */
time_t sem_otime; /* 最后一次成功修改信号量数组的时间 */
time_t sem_ctime; /* 成功创建时间 */
};
struct ipc_perm
{
    __kernel_key_t  key; // IPC 的键值
    __kernel_uid_t  uid; // 所有者的用户 id
    __kernel_gid_t  gid; // 所有者的组 id
    __kernel_uid_t  cuid; // 创建者的用户 id
    __kernel_gid_t  cgid; // 创建者的组 id
    __kernel_mode_t mode; // 访问模式
    unsigned short  seq; // 序列号 
};
struct sem {
ushort semval; /* 信号量的当前值 */
short sempid; /* 最后一次返回该信号量的进程ID 号 */
ushort semncnt; /* 等待semval大于当前值的进程个数 */
ushort semzcnt; /* 等待semval变成0的进程个数 */
};

int semop(int semid, struct sembuf *sops, unsigned nsops);
函数作用:设置信号量的值。
semid:信号量的标识符(semget 函数的返回值)
sops:指向信号量操作结构数组。结构体原型如下:
struct sembuf {
unsigned short sem_num; // 要操作的信号量在信号量集里的编号,
short sem_op; // 信号量操作
short sem_flg; // 操作表示符
};
若sem_op 是正数,其值就加到semval上,即释放信号量控制的资源
若sem_op 是0,那么调用者希望等到semval变为0,如果semval是0就返回;
若sem_op 是负数,那么调用者希望等待semval变为大于或等于sem_op的绝对值
注:semval是指semid_ds中的信号量集中的某个信号量的值
sem_flg:可以为以下值:
SEM_UNDO 由进程自动释放信号量
IPC_NOWAIT 不阻塞
nsops:信号量操作结构数组的个数。

int semctl(int semid, int semnum, int cmd, ...);
函数功能:控制信号量信息。
semid:信号量的标识符(semget 函数的返回值)
semnum:是信号在集合中的序号
cmd:控制命令。可取值如下:
SETVAL:指定信号量的当前值。此时 semval = val (val 的描述看下面可能出现的第四个参数)
GETVAL:获取信号量的当前值。(semctl 函数的返回值)
SETALL:指定所有信号量的值。此时 使用 array来将信号量集的所有值都赋值(array 的描述看下面可能出现的第四个参数)
GETALL:获取所有信号量的值。将信号量集的所有值返回到 array 指定的数组中。(array 的描述看下面可能出现的第四个参数)
IPC_STAT:得到信号量的状态。
IPC_SET:改变信号量的状态。
IPC_RMID:删除信号量标识符
第四个参数是一个必须由用户自定义的联合结构体,该结构体原型如下:
union semun
{
int val; // cmd == SETVAL
struct semid_ds *buf // cmd == IPC_SET或者 cmd == IPC_STAT
ushort *array; // cmd == SETALL,或 cmd = GETALL
};
注:第四个参数只在需要的时候才会用到。



示例程序(简单的PV操作示例)


//sem_h.h

#ifndef SEM_H_H_INCLUDED
#define SEM_H_H_INCLUDED

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/sem.h>
#include <errno.h>

// 新建或打开信号的操作标志
#define PERM S_IRUSR | S_IWUSR | IPC_CREAT

// 联合类型 semnu , semctl函数的第四个参数
union semnu {
int val;
struct semid_ds *buf;
ushort *array;
};

//显示出错信息
void print_error ( int f_line );

//打开或者创建信号量
void open_sem ( void );

//初始化信号量
void init_sem ( void );

//信号量 p 操作
void semaphore_p ( void );

//信号量 v 操作
void semaphore_v ( void );

//删除信号量
void del_sem ( void );
#endif // SEM_H_H_INCLUDED

// sem_c.c

#include "sem_h.h"
// 信号量 ID
int sem_id = 0;
void print_error ( int f_line )
{
fprintf ( stderr, "%s %s %d\n", strerror ( errno ), __FILE__, f_line );
}

void open_sem ( void )
{
if ( ( sem_id = semget ( ( key_t ) 1234, 1, PERM ) ) == -1 ) {
print_error ( __LINE__ );
}
}

void init_sem ( void )
{
union semnu semnu;
semnu.val = 1;

if ( semctl ( sem_id, 0, SETVAL, semnu ) == -1 ) {
print_error ( __LINE__ );
}
}

void semaphore_p ( void )
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = -1;
sem_b.sem_flg = SEM_UNDO;

if ( semop ( sem_id, &sem_b, 1 ) == -1 ) {
print_error ( __LINE__ );
}
}

void semaphore_v ( void )
{
struct sembuf sem_b;
sem_b.sem_num = 0;
sem_b.sem_op = 1;
sem_b.sem_flg = SEM_UNDO;

if ( semop ( sem_id, &sem_b, 1 ) == -1 ) {
print_error ( __LINE__ );
}
}

void del_sem ( void )
{
if ( semctl ( sem_id, 0, IPC_RMID ) == -1 ) {
print_error ( __LINE__ );
}
}
sem_1.c#include "sem_h.h"int main ( void ){    open_sem();    init_sem();    while ( 1 ) {        // p 操作,尝试进入缓冲区        semaphore_p();        printf ( "%d: hello \n", getpid() );        sleep(1);        //  v 操作, 离开缓冲区        semaphore_v();    }}
// sem_2.c#include "sem_h.h"#include <signal.h>int runnig = 1;void Handlesignal(int signo){        printf("dasd\n");       runnig =0;      del_sem();}int main ( void ){      if(signal(SIGINT,Handlesignal)==SIG_ERR){        print_error(__LINE__);    }    open_sem();    while ( runnig ) {        semaphore_p();        printf ( "%d: hello \n", getpid() );        sleep(1);        semaphore_v();    }}

测试示例:

首先在命令行执行 sem_2  这时,程序是没有输出的,因为信号量的值是为0的,阻塞了,然后运行 sem_1 ,就会发现,程序开始输出,并且是交替输出的。

如图:

linux 进程通信之 信号量