进程同步:信号量sem

xiaoxiao2025-10-14  6

目录

一、信号量什么是信号量?信号量S值含义 二、相关API`semget``shmctl` 信号量的初始化和删除`semop` P/V操作,使value资源数±N 三、示例代码四、信号量代码的封装


一、信号量

什么是信号量?

信号量是一种特殊的变量,访问具有原子性。只允许对它进行两个操作: (1)P(S) 将信号量value值减1,若结果小于0,则执行P操作的进程被阻塞,若结果大于等于0,则执行P操作的进程将继续执行。 (2)V(S) 将信号量的值加1,若结果不大于0,则执行V操作的进程从信号量s有关的list所知队列中释放一个进程,使其转化为就绪态,自己则继续执行;另外,若结果大于0,则执行V操作的进程继续执行。

信号量S值含义

S>0:S表示可用资源的个数S=0:表示无可用资源,无等待进程S<0:|S|表示等待队列中进程个数

二、相关API

semget

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

参数: nsems:信号集中信号量的个数 semflg:9个权限位,与创建文件是使用的mode模式标志一样,IPC_CREATE和IPC_EXEL返回值:成功返回sem_id;失败返回-1

shmctl 信号量的初始化和删除

int semctl( int semid, int semnum, int cmd, [union semun] );

返回值:成功返回0;失败返回-1参数: semnum:表示即将要进行操作的信号量的编号,即信号量集合的索引值[0,semnum-1] [union semun]:可变参数 cmd: cmd参数含义IPC_STAT获取某个信号量集合的semid_ds结构,并将其储存在semun联合体的buf参数所指的地址之中IPC_SET设置某个集合的semid_ds结构的ipc_perm成员的值IPC_RMID从内核删除该信号量集合GETVAL返回集合中某个信号量的值SETVAL初始化:将集合中单个信号量的值设置为联合体的val成员的值

[可变参数union semun详解]:对于semctl函数,只有当cmd取特定值的时候,才会用到第4个可变参数union semun (1)第4个参数union semun定义

union semun{ //sem_union是可选参数 int val; //一般表示要传给信号量的初始值 struct semid_ds *buf; unsigned short *arry; };

(2)使用第4个参数union semun的情况:

(最常用:信号量的初始化)当执行SETVAL命令时用到这个成员,用于指定要把信号量设置成什么值,涉及成员:val在命令IPC_STAT/IPC_SET中使用,它代表内核中所使用内部信号量数据结构的一个复制 ,涉及成员:buf在命令GETALL/SETALL命令中使用时,他代表指向整数值一个数组的指针,在设置或获取集合中所有信号量的值的过程中,将会用到该数组,涉及成员:array剩下的还有一些用法都将在系统内核中的信号量代码使用,应用程序开发中使用很少,

semop P/V操作,使value资源数±N

int semop(int semid, struct sembuf *sops, unsigned nsops);

返回值:成功返回0;失败返回-1参数: nsops:信号量的个数 struct sembuf *sops:是个指向一个结构数值的指针其中:struct sembuf结构体介绍: struct sembuf { short sem_num; //信号量的编号:除非使用一组信号量,否则它为0 short sem_op; //操作类型:信号量一次PV操作加减的数值(一般只会用到两个值±1) short sem_flg; //两个取值是IPC_NOWAIT或SEM_UNDO(一般设为SEM_UNDO) };

struct sembuf结构体中的short sem_op参数:

sem_op < 0 表示P(sem_op),进程获取|sem_op|个资源;(若没有设置 IPC_NOWAIT ,则调用进程阻塞,直到资源可用;否则进程直接返回EAGAIN)sem_op > 0 表示V(|sem_op|),进程释放|sem_op|个资源;sem_op = 0 如果sem_flg没有设置IPC_NOWAIT,则调用进程进入睡眠状态,直到信号量的值为0;否则进程不会睡眠,直接返回 EAGAIN

PV操作示例代码

int sem_p(int semid){ int ret = 0; sembuf_t buf = {0,-1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式,-1 ret = semop(semid,&buf,1 );//只操作一个信号量 return ret; } int sem_v(int semid){ int ret = 0; sembuf_t buf = {0,+1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式,+1 ret = semop(semid,&buf,1);//只操作一个信号量 return ret; }

三、示例代码

程序功能:父子进程都执行[临界区]代码

#include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <sys/time.h> #include <sys/mman.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/types.h> #define SEM_NUM 1 typedef 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) */ }semun_t; #if 0 typedef struct sembuf{ unsigned short sem_num; /* semaphore number */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ }sembuf_t; #endif typedef struct sembuf sembuf_t; //创建信号量集合的代码 int sem_create(key_t key) { int sem_id = 0; sem_id = semget(key,SEM_NUM,0666|IPC_CREAT|IPC_EXCL); if(sem_id == -1) { perror("semget"); if(errno == EEXIST) { printf("Judge by self..exist\n"); } return -1; } return sem_id; } //打开已存在的信号量集合 int sem_open(key_t key) { int sem_id = 0; sem_id = semget(key,SEM_NUM,0666); if(sem_id == -1) { //perror("semget"); return -1; } return sem_id; } void sem_del(int sem_id) { union semun sem_union; if(semctl(sem_id,0,IPC_RMID,sem_union)==-1) { fprintf(stderr,"Failed to delete sem\n"); exit(1); } else fprintf(stdout,"Have del sem sucess\n"); } //设置信号量集合里面的信号量的计数值val int sem_set(int semid,int val) { int ret = 0; if(semid < 0) { return -1; } semun_t su; su.val = val; ret = semctl(semid,0,SETVAL,su); return ret; } //获取信号量集合里面的信号量的计数值 int sem_get(int semid,int* val) { int ret = 0; if(semid < 0) { return -1; } semun_t su; ret = semctl(semid,0,GETVAL,su); *val = su.val; return ret; } //原子p操作 int sem_p(int semid) { int ret = 0; sembuf_t buf = {0,-1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式 ret = semop(semid,&buf,1 );//只操作一个信号量 return ret; } //原子v操作 int sem_v(int semid) { int ret = 0; sembuf_t buf = {0,+1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式 ret = semop(semid,&buf,1);//只操作一个信号量 return ret; } int main() { int sem_id = 0;//信号量集ID int key;//用来创建信号量集的key int tmp_val = 1024;//临时变量--用来测试设置和获取信号量的计数值 pid_t pid ; int i= 0; key = ftok("./",'c');//获取key //利用获取的key打开/创建信号量集合 sem_id = sem_open(key); if(sem_id == -1) sem_id = sem_create(key); //设置信号量集合中的信号量(元素)的计数值 sem_set(sem_id,1); //获取信号量集合中的信号量(元素)的计数值,存储到临时变量tmp_val并打印 sem_get(sem_id,&tmp_val); printf("tmp_val:%d\n",tmp_val); pid = fork(); if(pid == -1) { perror("fork"); exit(-1); } //p操作--类似上锁--保证一个进程结束另一个进程才开始执行 sem_p(sem_id); for(int j=0;j<10;j++) { usleep(10); printf("my pid:%d\t i = %d\n",getpid(),i++); } //v操作--类似解锁 sem_v(sem_id); printf("please input any to run sem_del\n"); getchar(); sem_del(sem_id); return 0; }

四、信号量代码的封装

头文件myipc_sem.h

#ifndef __MYIPC_SEM_H__ #define __MYIPC_SEM_H__ #include <stdlib.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <signal.h> #include <sys/time.h> #include <sys/mman.h> #include <sys/ipc.h> #include <sys/sem.h> #include <sys/stat.h> #include <sys/wait.h> #include <sys/types.h> #define SEM_NUM 1 //错误码 #define SEMERR_BASE 100 #define SEMERR_PARAM (SEMERR_BASE+1) #define SEMERR_EXIST (SEMERR_BASE+2) #define SEMERR_ENOXIST (SEMERR_BASE+3) typedef 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) */ }semun_t; #if 0 typedef struct sembuf{ unsigned short sem_num; /* semaphore number */ short sem_op; /* semaphore operation */ short sem_flg; /* operation flags */ }sembuf_t; #endif typedef struct sembuf sembuf_t; int sem_create(key_t key,int *semid); int sem_open(key_t key,int *semid); int sem_set(int semid,int val); int sem_get(int semid,int* val); int sem_p(int semid); int sem_v(int semid); #endif

实现文件myipc_sem.c

#include "myipc_sem.h" //封装创建信号量集合的代码 int sem_create(key_t key,int *semid) { int sem_id = 0; int ret = 0; if(semid == NULL) { ret = SEMERR_PARAM; perror("argv err\n"); return ret; } sem_id = semget(key,SEM_NUM,0666|IPC_CREAT|IPC_EXCL); if(sem_id == -1) { ret = SEMERR_EXIST; perror("semget"); if(errno == EEXIST) { printf("Judge by self..exist\n"); } return ret; } *semid = sem_id; return ret; } //封装打开已存在的信号量集合 int sem_open(key_t key,int *semid) { int sem_id = 0; int ret = 0; if(semid == NULL) { ret = SEMERR_PARAM; perror("argv err\n"); return ret; } sem_id = semget(key,SEM_NUM,0666); if(sem_id == -1) { if(errno == ENOENT) { printf("Judge by self..exist\n"); ret = SEMERR_ENOXIST; return ret; } } *semid = sem_id; return ret; } //设置信号量集合里面的信号量的计数值 int sem_set(int semid,int val) { int ret = 0; if(semid < 0) { return -1; } semun_t su; su.val = val; ret = semctl(semid,0,SETVAL,su); return ret; } //获取信号量集合里面的信号量的计数值 int sem_get(int semid,int* val) { int ret = 0; if(semid < 0) { return -1; } semun_t su; ret = semctl(semid,0,GETVAL,su);//这里需要特别注意 if(ret == -1)//返回-1表示semctl失败--直接返回 return ret; else//非零表示获取成功--但是返回值需要设置位0--上层应用收到0表示调用成功 ret = 0; *val = su.val;//将获取到的值甩出去到上层应用 return ret; } //原子p操作 int sem_p(int semid) { int ret = 0; sembuf_t buf = {0,-1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式 ret = semop(semid,&buf,1 );//只操作一个信号量 return ret; } //原子v操作 int sem_v(int semid) { int ret = 0; sembuf_t buf = {0,+1,0};//一般默认是从第0个信号量开始,第三个参数默认是0表示阻塞模式 ret = semop(semid,&buf,1);//只操作一个信号量 return ret; }
转载请注明原文地址: https://www.6miu.com/read-5037919.html

最新回复(0)