c/c++ linux 进程间通信系列5,使用信号量
linux 进程间通信系列5,使用信号量
信号量的工作原理:
由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),他们的行为是这样的:
P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行
V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.
举个例子,就是两个进程共享信号量sv,一旦其中一个进程执行了P(sv)操作,它将得到信号量,并可以进入临界区,使sv减1。而第二个进程将被阻止进入临界区,因为当它试图执行P(sv)时,sv为0,它会被挂起以等待第一个进程离开临界区域并执行V(sv)释放信号量,这时第二个进程就可以恢复执行。
1,semget()函数
int semget(key_t key, int num_sems, int sem_flags);
第二个参数nsems指明要创建的信号量集包含的信号量个数。如果只是打开信号量,把nsems设置为0即可。该参数只在创建信号量集时有效。
第三个参数semflg为操作标识,可取如下值:
0:取信号量集标识符,若不存在则函数会报错
IPC_CREAT:当semflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的信号量集,则新建一个信号量集;如果存在这样的信号量集,返回此信号量集的标识符
IPC_CREAT|IPC_EXCL:如果内核中不存在键值与key相等的信号量集,则新建一个消息队列;如果存在这样的信号量集则报错
上述semflg参数为模式标志参数,使用时需要与IPC对象存取权限(如0600)进行|运算来确定信号量集的存取权限
错误代码:
EACCESS:没有权限
EEXIST:信号量集已经存在,无法创建
EIDRM:信号量集已经删除
ENOENT:信号量集不存在,同时semflg没有设置IPC_CREAT标志
ENOMEM:没有足够的内存创建新的信号量集
ENOSPC:超出限制
2,semop()函数
int semop(int sem_id, struct sembuf *sops, size_t nsops);
semid:信号量集标识符
sops:指向进行操作的信号量集结构体数组的首地址,此结构的具体说明如下:
如果sembuf里的semnum超过了集合中信号量的最大个数,在执行semop时,会报出:FIle too large。
struct sembuf {
short semnum; /*信号量集合中的信号量编号,0代表第1个信号量*/
short val;/*若val>0进行V操作信号量值加val,表示进程释放控制的资源 */
/*若val<0进行P操作信号量值减val,若(semval-val)<0(semval为该信号量值),则调用进程阻塞,直到资源可 用;若设置IPC_NOWAIT不会睡眠,进程直接返回EAGAIN错误*/
/*若val==0时阻塞等待信号量为0,调用进程进入睡眠状态,直到信号值为0;若设置IPC_NOWAIT,进程不会睡眠,直接返回EAGAIN错误*/
short flag; /*0 设置信号量的默认操作*/
/*IPC_NOWAIT设置信号量操作不等待*/
/*SEM_UNDO 选项会让内核记录一个与调用进程相关的UNDO记录,如果该进程崩溃,则根据这个进程的UNDO记录自动恢复相应信号量的计数值*/
};
nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
错误代码:
E2BIG:一次对信号量个数的操作超过了系统限制
EACCESS:权限不够
EAGAIN:使用了IPC_NOWAIT,但操作不能继续进行
EFAULT:sops指向的地址无效
EIDRM:信号量集已经删除
sops为指向sembuf数组,定义所要进行的操作序列。下面是信号量操作举例。
struct sembuf sem_get={0,-1,IPC_NOWAIT}; /将信号量对象中序号为0的信号量减1/
struct sembuf sem_get={0,1,IPC_NOWAIT}; /将信号量对象中序号为0的信号量加1/
struct sembuf sem_get={0,0,0}; /进程被阻塞,直到对应的信号量值为0/
flag一般为0,若flag包含IPC_NOWAIT,则该操作为非阻塞操作。若flag包含SEM_UNDO,则当进程退出的时候会还原该进程的信号量操作,这个标志在某些情况下是很有用的,
比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。
semops函数详细说明参考:semops
3,semctl()函数
int semctl(int semid, int semnum, int cmd, ...);
作用:根据cmd的不同,操作创建成功的semid,比如设置信号量集合里的每个信号,同时可以访问的进程数,通过cmd:SETALL,来设置,并且后面需要跟一个指向数组的指针。
- semid:信号量集合的标识符。
- semnum:信号量集合中的第几个信号量。当cmd为SETALL时,这个参数感觉没有实际意义,多少都可以。
- cmd:根据您cmd的不同,做不同的操作。
下面的例子1创建了一个信号量的集合,可以用【ipcs -s】查看;例子2去访问例子1创建的信号量集合。
例子1里的关键点:
- 信号集合里信号的个数为16个
- 因为这句代码【semun_array[i] = 1;】,所以,每个信号,只能同时有且只有一个进程访问。如果【semun_array[i] = 2;】,则,每个信号,同时有2个进程可以访问这个信号。
- smectl的第一个参数,感觉设置多少都可以。
例子2里的关键点:
- sb.sem_num = 15;//指定要访问的信号量集合中的哪个信号量
sb.sem_op = -1;//semop前信号量的值都为1,这里指定的是-1,所以减一后为0,由于为0了,所以下个进程再想去访问,就需要排队,等现在访问这个信号量的进程结束后,才能访问。 - SEM_UNDO的作用为,访问信号量的进程结束后,会自动回复这个信号量被访问前的状态,也就是说,某个进程访问前,信号量的状态是1,这个进程退出后,把信号量的状态回复回1.
例子1
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#define NSEMS 16
int main(){
int semid;
unsigned short semun_array[NSEMS];
int i;
semid = semget(IPC_PRIVATE, NSEMS, 0600);
if(semid < 0){
perror("semget");
return 1;
}
for(i =0; i < NSEMS; ++i){
semun_array[i] = 1;
}
if(semctl(semid, 1000, SETALL, &semun_array) != 0){
perror("semctl");
return 1;
}
printf("semid:%d\n", semid);
return 0;
}
例子2:
#include <stdio.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <stdlib.h>
#define NSEMS 16
int main(int argc, char* argv[]){
int semid;
sembuf sb;
if(argc != 2){
printf("arg is wrong\n");
return 1;
}
semid = atoi(argv[1]);
sb.sem_num = 15;//指定要操作的信号量是信号集合中的号码为0信号量
sb.sem_op = -1;
sb.sem_flg = SEM_UNDO;
printf("before semop()\n");
if(semop(semid, &sb, 1) != 0){
perror("semop");
return 1;
}
printf("after semop()\n");
printf("press enter to exti\n");
getchar();
return 0;
}
例子3:多线程之间,使用信号量。
第一个进程:
- step1:创建有16个信号量的信号量集合的结果是成功的,所以结果semid >= 0,进入if分支。
- step2:把每个信号量可同时访问的进程数目设置为1,在代码的22-28行。
- step3:准备访问16个信号量,首先在31-35行,把访问设置为减一,并且UNDO。
- step4:按回车后,开始访问16个信号量。
第二个进程:
- step1:因为是用同一个key去创建信号量集合,所以是失败的,进入else分支。
- step2:去拿,已经被创建过了的信号量集合的id,semid = semget(MYIPCKEY, NSEMS, 0600);
- step3:进入while循环,观察sem_otime是否为0,不为0后,跳出循环,准备访问第0号信号量和第1号信号量。如果某个进程访问了信号量集合,sem_otime就从0变为非0.
- step4:准备访问第0号信号量和第1号信号量。
- step5:访问第0号信号量和第1号信号量,但是发现信号量的值为-1(因为第一个进程访问是给他减一了),说明已经有个进程(第一个进程)正在访问,所以就一直等待。
- step6:按回车,让第一个进程结束。信号量会从-1变为0。
- step7:因为访问的进程退出了(信号量会从-1变为0),在step5处的等待就结束了,才可以访问第0号信号量和第1号信号量。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MYIPCKEY 0xabcdabcd
#define NSEMS 16
int sem_init(){
int semid;
unsigned short semun_array[NSEMS];
sembuf sb[NSEMS];
int i;
semid = semget(MYIPCKEY, NSEMS, 0600 | IPC_CREAT | IPC_EXCL);
if(semid >= 0){
for(i = 0; i < NSEMS; ++i){
semun_array[i] = 1;
}
if(semctl(semid, NSEMS, SETALL, &semun_array) != 0){
perror("semctl");
return 1;
}
printf("[pid:%d] new semaphore set, semid=%d\n", getpid(), semid);
for(i = 0; i < NSEMS; ++i){
sb[i].sem_num = i;
sb[i].sem_op = -1;
sb[i].sem_flg = SEM_UNDO;
}
printf("IF[pid:%d] before semop()\n", getpid());
printf("IF[pid:%d] press enter to start semop()\n", getpid());
getchar();
if(semop(semid, sb, NSEMS)){
perror("semop");
return 1;
}
printf("IF[pid:%d] press enter to exit this process\n", getpid());
getchar();
exit(0);
}
else{
if(errno != EEXIST){
perror("semget");
return 1;
}
else{
printf("in else\n");
semid_ds sds;
semid = semget(MYIPCKEY, NSEMS, 0600);
if(semid < 0){
perror("semget 1");
return 1;
}
printf("ELSE[pid:%d] before semctl()\n", getpid());
while(true){
//IPC_STAT的时候,忽略第二个参数
if(semctl(semid, 0, IPC_STAT, &sds) != 0){
perror("semctl 1");
return 1;
}
printf("###########################\n");
printf("sem_perm.mode:%d,sem_perm.__seq:%d\n",sds.sem_perm.mode,sds.sem_perm.__seq);
printf("otime:%ld\n",sds.sem_otime);/* Last semop time */
printf("ctime:%ld\n",sds.sem_ctime);/* Last change time */
printf("sem_nsems:%ld\n",sds.sem_nsems);/* No. of semaphores in set */
printf("###########################\n");
if(sds.sem_otime != 0){
break;
}
printf("ELSE[pid:%d] waiting otime change...\n", getpid());
sleep(2);
}
sb[0].sem_num = 0;
sb[0].sem_op = -1;
sb[0].sem_flg = SEM_UNDO;
sb[1].sem_num = 0;
sb[1].sem_op = -1;
sb[1].sem_flg = SEM_UNDO;
printf("ELSE[pid:%d] before semop()\n", getpid());
if(semop(semid, sb, 2) != 0){
perror("semop 1");
return 1;
}
printf("ELSE[pid:%d] after semop()\n", getpid());
}
}
return 0;
}
int main(){
pid_t pid;
pid = fork();
if(sem_init() < 0){
printf("[pid:%d] sem_init() failed\n", getpid());
}
}
注意:执行一次后,再次执行前,必须用下面的命令删除信号量集合。
1,首先找到信号量集合的ID:
ipcs -s
2,删除信号量集合:
ipcrm -s id
c/c++ 学习互助QQ群:877684253
本人微信:xiaoshitou5854
c/c++ linux 进程间通信系列5,使用信号量的更多相关文章
- c/c++ linux 进程间通信系列4,使用共享内存
linux 进程间通信系列4,使用共享内存 1,创建共享内存,用到的函数shmget, shmat, shmdt 函数名 功能描述 shmget 创建共享内存,返回pic key shmat 第一次创 ...
- c/c++ linux 进程间通信系列7,使用pthread mutex
linux 进程间通信系列7,使用pthread mutex #include <stdio.h> #include <stdlib.h> #include <unist ...
- c/c++ linux 进程间通信系列6,使用消息队列(message queue)
linux 进程间通信系列6,使用消息队列(message queue) 概念:消息排队,先进先出(FIFO),消息一旦出队,就从队列里消失了. 1,创建消息队列(message queue) 2,写 ...
- c/c++ linux 进程间通信系列3,使用socketpair,pipe
linux 进程间通信系列3,使用socketpair,pipe 1,使用socketpair,实现进程间通信,是双向的. 2,使用pipe,实现进程间通信 使用pipe关键点:fd[0]只能用于接收 ...
- c/c++ linux 进程间通信系列2,使用UNIX_SOCKET
linux 进程间通信系列2,使用UNIX_SOCKET 1,使用stream,实现进程间通信 2,使用DGRAM,实现进程间通信 关键点:使用一个临时的文件,进行信息的互传. s_un.sun_fa ...
- c/c++ linux 进程间通信系列1,使用signal,kill
linux 进程间通信系列1,使用signal,kill 信号基本概念: 软中断信号(signal,又简称为信号)用来通知进程发生了异步事件.进程之间可以互相通过系统调用kill发送软中断信号.内核 ...
- Linux进程间通信(消息队列/信号量+共享内存)
写在前面 不得不说,Deadline果真是第一生产力.不过做出来的东西真的是不堪入目,于是又花了一早上重写代码. 实验内容 进程通信的邮箱方式由操作系统提供形如 send()和 receive()的系 ...
- Linux 进程间通信系列之 信号
信号(Signal) 信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身:Linux除了支持Unix早期信号语义函数sigal外,还支持语义符 ...
- Linux进程间通信(五):信号量 semget()、semop()、semctl()
这篇文章将讲述别一种进程间通信的机制——信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信 -- 信号.下面 ...
随机推荐
- 【Docker】(2)---仓库、镜像、容器
[Docker](2)---仓库.镜像.容器 学习Docker,我觉得首先要了解的是仓库.镜像.容器到底是什么,他们有什么区别. 一.通俗理解 1.Docker 镜像 (images) 容器运 ...
- 今天俺要说一说工厂方法模式(Factory)
前言;工厂方法模式又叫做工厂模式,它是23个设计模式中的一个,它解决的还是在软件设计中创建对象的问题,它可以更好的解决用户需求的变化. 问题;在简单工厂模式中,我们将实例化的对象全部放于Factory ...
- NPM 安装速度慢,镜像修改
今天安装gitbook的时候,竟然花了两个小时没有安装成功,大家在使用npm安装依赖的时候速度是不是经常慢的要死?最佳解决方案是手动更改镜像服务器地址, 强烈推荐阿里巴巴在国内的镜像服务器,执行下面命 ...
- Solr04 - 在Jetty和Tomcat上部署Solr单机服务
目录 1 准备安装环境 2 通过内部Jetty服务器启动 3 通过配置Tomcat服务器启动 3.1 删除不需要的应用 3.2 修改服务端口 3.3 部署solr.war 3.4 扩展: 虚拟目录发布 ...
- Mongodb~Linux环境下的部署
< mongodb服务脚本的制作> Mongodb这个文档型非关系型数据库,可以说它是最像关系型的了,之前大叔主要讲如何使用mongodb,而没有说过如何去部署和安装它,而今天大叔有必要讲 ...
- Asp.net Core 使用Jenkins + Dockor 实现持续集成、自动化部署(二):部署
前面又是废话 我之前写过: Asp.Net Core 程序部署到Linux(centos)生产环境(一):普通部署 Asp.Net Core 程序部署到Linux(centos)生产环境(二):doc ...
- JsChart组件使用
JsChart是什么? JSChart能够在网页上生成图标,常用于统计信息,十分好用的一个JS组件. 使用JsChart 一.导入jscharts.js 二.编写jscharts.jsp测试页面 下载 ...
- Oracle学习笔记二
多表查询: 笛卡尔积: 实际上是两张表的乘积,但是在实际开发中没有太大意义 格式: select * from 表1,表2 select * from emp; select * from dept; ...
- Java多线程父子线程关系 多线程中篇(六)
有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...
- -1-4 java io java流 常用流 分类 File类 文件 字节流 字符流 缓冲流 内存操作流 合并序列流
File类 •文件和目录路径名的抽象表示形式 构造方法 •public File(String pathname) •public File(String parent,Stringchild) ...