System V IPC(2)-信号量

一.概述                                                   

System V信号量与System V消息队列不同。它不是用来在进程间传递数据。它主要是来同步进程的动作。

1.一个信号量是一个由内核维护的整数。其值被限制为大于或等于0。

2.可以在信号量上加上或减去一个数量。

3.当一个减操作把信号量减到小于0时,内核会阻塞调用进程。直到另一操作把信号恢复,阻塞才会解除。

4.常用的信号量是二进制信号量。即操作0和1来控制临界区。

二.函数接口                                           

1.创建或打开一个信号量

1 #include <sys/sem.h>
2
3 int semget(key_t key, int nsems, int semflg);

key和semflg跟消息队列一样,这里不再介绍,上面已给出消息队列的文章连接。

nsems:指定信号量数目,一般都是1。

2.控制信号量

1 #include <sys/sem.h>
2
3 int semctl(int semid, int semnum, int cmd, ...);

semid:semget()返回的信号量标识符。

semnum:信号量编号,如果是成组的信号量,就要用到它,否则是0。

cmd:控制信号量的命令。SETVAL初始化一个值,IPC_RMID删除信号量。

第四个参数是一个union semun结构。如果有的Linux版本头文件没有这个结构,需要自己定义:

1 union semun {
2                int              val;    /* Value for SETVAL */
3                struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
4                unsigned short  *array;  /* Array for GETALL, SETALL */
5                struct seminfo  *__buf;  /* Buffer for IPC_INFO
6                                            (Linux-specific) */
7  };

3.改变信号量的值

1 #include <sys/sem.h>
2
3 int semop(int semid, struct sembuf *sops, size_t nsops);

sops:指向一个sembuf结构,该结构成员如下:

unsigned short sem_num:信号量编号,如果不是一组信号,一般都为0。
short sem_op:对信号量加减操作,如:-1,+1,1。
short sem_flg:通常设置为SEM_UNDO。如果进程终止时没有释放该信号量,内核会释放它。

三.简单例子                                            

我们封装一个简单的二进制信号量,写2个小程序,一个只打印基数,一个只打印偶数,通过二进制信号量来控制它们按顺序打印1-10。

1.封装二进制信号量

 1 /**
 2  * @file binary_sem.h
 3  */
 4
 5 #ifndef _BINARY_SEM_H_
 6 #define _BINARY_SEM_H_
 7
 8 union semun {
 9     int              val;    /* Value for SETVAL */
10     struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
11     unsigned short  *array;  /* Array for GETALL, SETALL */
12     struct seminfo  *__buf;  /* Buffer for IPC_INFO
13                                 (Linux-specific) */
14 };
15
16 /* 设置信号量 */
17 int sem_set(int sem_id);
18 /* 增大信号量 */
19 int sem_up(int sem_id);
20 /* 减小信号量 */
21 int sem_down(int sem_id);
22 /* 删除信号量 */
23 int sem_delete(int sem_id);
24
25 #endif
 1 /**
 2  * @file binary_sem.c
 3  */
 4
 5 #include <stdio.h>
 6 #include <stdlib.h>
 7 #include <string.h>
 8 #include <sys/sem.h>
 9
10 #include "binary_sem.h"
11
12 static union semun sem_union;
13 static struct sembuf sem_buf;
14
15
16 /* 设置信号量 */
17 int sem_set(int sem_id)
18 {
19     sem_union.val = 1;
20     if (semctl(sem_id, 0, SETVAL, sem_union) == -1)
21         return -1;
22     return 0;
23 }
24
25 /* 改变信号量为1 */
26 int sem_up(int sem_id)
27 {
28     sem_buf.sem_num = 0;
29     sem_buf.sem_flg = SEM_UNDO;
30     sem_buf.sem_op = 1;
31     if (semop(sem_id, &sem_buf, 1) == -1)
32         return -1;
33     return 0;
34 }
35
36 /* 信号量减1 */
37 int sem_down(int sem_id)
38 {
39     sem_buf.sem_num = 0;
40     sem_buf.sem_flg = SEM_UNDO;
41     sem_buf.sem_op = -1;
42     if (semop(sem_id, &sem_buf, 1) == -1)
43         return -1;
44     return 0;
45 }
46
47 /* 删除信号量 */
48 int sem_delete(int sem_id)
49 {
50     if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
51         return -1;
52     return 0;
53 }

2.打印基数的程序

 1 /**
 2  * @file sem1.c
 3  */
 4
 5 #include <stdio.h>
 6 #include <string.h>
 7 #include <stdlib.h>
 8 #include <sys/sem.h>
 9 #include <unistd.h>
10
11 #include "binary_sem.h"
12
13 #define SEM_KEY 7788
14
15 void err_exit(const char *err_msg)
16 {
17     printf("error: %s\n", err_msg);
18     exit(1);
19 }
20
21 int main(int argc, const char *argv[])
22 {
23     int sem_id, i;
24
25     /* 创建信号 */
26     if ((sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT)) == -1)
27         err_exit("semget()");
28
29     /* 初始化信号为1 */
30     if (sem_set(sem_id) == -1)
31         err_exit("sem_set()");
32
33     for (i = 1; i < 10; i += 2)
34     {
35         /* 信号减一 */
36         if (sem_down(sem_id) == -1)
37             err_exit("sem_down()");
38
39         sleep(3);
40         printf("%s:%d\n", argv[0], i);
41
42         /* 信号加一 */
43         if (sem_up(sem_id) == -1)
44             err_exit("sem_up()");
45
46         sleep(1);
47     }
48     return 0;
49 }

对信号量加减操作之间的代码就是临界区。程序第39行的sleep()是为了有足够的时间来允许第二个程序,46行的sleep()是尽量让其他程序来获取信号量。

3.只打印偶数的程序

 1 /**
 2  * @file sem2.c
 3  */
 4
 5 #include <stdio.h>
 6 #include <string.h>
 7 #include <stdlib.h>
 8 #include <unistd.h>
 9 #include <sys/sem.h>
10
11 #include "binary_sem.h"
12
13 #define SEM_KEY 7788
14
15 void err_exit(const char *err_msg)
16 {
17     printf("error: %s\n", err_msg);
18     exit(1);
19 }
20
21 int main(int argc, const char *argv[])
22 {
23     int sem_id, i;
24
25     /* 创建信号 */
26     if ((sem_id = semget(SEM_KEY, 1, 0666 | IPC_CREAT)) == -1)
27         err_exit("semget()");
28
29     for (i = 2; i <= 10; i += 2)
30     {
31         /* 信号减一 */
32         if (sem_down(sem_id) == -1)
33             err_exit("sem_down()");
34
35         printf("%s:%d\n", argv[0], i);
36
37         /* 信号加一 */
38         if (sem_up(sem_id) == -1)
39             err_exit("sem_up()");
40
41         sleep(1);
42     }
43     return 0;
44 }

四.实验                                                  

1.为了测试方便,上面2个程序的key统一用一个值为7788的宏来指定。

2.两个程序执行的流程:sem1初始化一个为1的信号量,进入循环后对信号执行减一,并进入临界区。休眠3秒的时间我执行第二个程序sem2,sem2进入循环后也执行减一操作,但此时信号已被sem1减为0,此时内核会阻塞sem1的操作,因为再减就小于0了。回到sem1,3秒过后,打印基数,并把信号重置为1,离开临界区,休眠1秒。此时内核发现信号是1,可以执行减操作,内核会对sem2放行,sem2进入临界区打印偶数。刚刚休眠1秒的sem1再次进入临界区,发现信号量被sem2减到了0,此时被内核阻塞,直到sem2重置信号量。如此反复!!!

最后:其实System V信号量的语义和API都比较复杂,有机会再探讨比它简单的POSIX信号量。

时间: 2024-10-08 05:08:57

System V IPC(2)-信号量的相关文章

linux进程间通讯-System V IPC 信号量

进程间通信的机制--信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信--使用信号.下面就进入信号量的讲解. 一.什么是信号量 为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域.临界区域是指执行数据更新的代码需要独占式地执行.而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在

3. System V IPC

System V IPC包含三种类型的IPC:System V消息队列.System V信号量.System V共享内存区 1. key_t键和ftok函数 三种类型的System V IPC使用key_t值作为名字.头文件<sys/types.h>定义key_t为一个至少32位的整数.函数ftok把一个已存在的路径名和一个整数标识符转换成一个key_t值,称为IPC键 #include <sys/ipc.h> key_t ftok(const char *pathname, in

从并发处理谈PHP进程间通信(二)System V IPC

.container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .container::before,.container::after { content: " "; display: table } .container::after { clear: both } .container::before,.container::after { content:

UNIX 进程间通讯(IPC)概念(Posix,System V IPC)

 IPC(Inter-Process Communication,进程间通讯)可以有三种信息共享方式(随文件系统,随内核,随共享内存).(当然这里虽然说是进程间通讯,其实也是可以和线程相通的). 相对的IPC的持续性(Persistence of IPC Object)也有三种: 随进程持续的(Process-Persistent IPC) IPC对象一直存在,直到最后拥有他的进程被关闭为止,典型的IPC有pipes(管道)和FIFOs(先进先出对象) 随内核持续的(Kernel-persist

《Unix网络编程》卷2 读书笔记 第3章- System V IPC

1. 概述 三种类型的System V IPC:System V 消息队列.System V 信号量.System V 共享内存区 System V IPC在访问它们的函数和内核为它们维护的信息上共享许多类似点.本章讲述所有这些共同属性. 下图汇总了所有System V IPC 函数 2. key_t键.ftok函数 头文件sys/types.h把数据类型key_t定义为一个整数,通常是一个至少32位的整数 #include <sys/ipc.h> key_t ftok (const char

System V IPC 之共享内存

IPC 是进程间通信(Interprocess Communication)的缩写,通常指允许用户态进程执行系列操作的一组机制: 通过信号量与其他进程进行同步 向其他进程发送消息或者从其他进程接收消息 和其他进程共享一段内存区 System V IPC 最初是在一个名为 "Columbus Unix" 的开发版 Unix 变种中引入的,之后在 AT&T 的 System III 中采用.现在在大部分 Unix 系统 (包括 Linux) 中都可以找到. IPC 资源包含信号量.

System V IPC 之消息队列

消息队列和共享内存.信号量一样,同属 System V IPC 通信机制.消息队列是一系列连续排列的消息,保存在内核中,通过消息队列的引用标识符来访问.使用消息队列的好处是对每个消息指定了特定消息类型,接收消息的进程可以请求接收下一条消息,也可以请求接收下一条特定类型的消息. 相关数据结构 与其他两个 System V IPC 通信机制一样,消息队列也有一个与之对应的结构,该结构的定义如下: struct msqid_ds { struct ipc_perm msq_perm; struct m

System V IPC相关函数

System V IPC 将一个已保存的路径名和一个整数标识符转换成一个key_t值,称为IPC键key_t:System V IPC(System V消息队列.System V信号量.System V共享内存区)将key_t作为它们的名字 #include <sys/ipc.h> key_t ftok(const char *pathname, int id); ipc_perm结构: 内核给每个IPC对象维护一个信息结构 #include <sys/ipc.h> struct

第3章 System V IPC

3.1 概述 System V IPC函数: 3.2 key_t和ftok函数 key_t是System V IPC的外部标识符,又称为IPC键,通过键,多个进程在同一个IPC对象上会合 ftok函数将路径名和整数标识符转换为key_t值 #include <sys/ipc.h> key_t ftok(char *pathname,int id) 客户与服务器在pathname和id上达成一致,则双方通过调用ftok函数获取同一个IPC键 三个get函数中(msgget.semget.shmg