Linux进程间通信之信号量

来源:互联网 发布:软件图标设计 编辑:程序博客网 时间:2024/05/21 17:45

众所周知,每个进程使用的数据是是它独有的,即使之前没有,它也会在自己的内存区域拷贝一个(这里不考虑共享内存机制)。

如果进程间需要通信,有共享资源时,信号,管道,消息队列,信号量,共享内存,这几种技术都可以帮我们。这里先介绍信号量

信号量定义:

为了防止出现因多个程序同时访问一个共享资源而引发的一些问题,我们需要任一时刻只有一个执行线程访问代码的临界区域。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它。

信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。

这里二进制信号量是最常见的信号量,本文也重点讨论二进制信号量

工作原理:

信号量只能进行两种操作(等待和发送信号),即PV操作

P:如果信号量变量>=0,就将其减-,如果值为0,就挂起该进程的执行

V:如果有其他进程因等待信号量变量而被挂起,就将其恢复,如果没有,就将变量加-;

      通俗一点的说就是:如果一个进程执行P操作进入了临界区,变量变为0,而另一个进程执行P的时候发现变量为0,将被挂起。而第一个进程执行完了后执行V操作时发现第二个进程在等待该变量,就将其恢复,这时变量没变值。

函数:

1:semget函数:创建一个新信号量或者取消一个已有信号量

int semget(key_t key, int num_sems, int sem_flags);  

第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。

第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。


semget函数成功返回一个相应信号标识符(非零),失败返回-1.

2、semop函数:改变信号量的值

int semop(int sem_id, struct sembuf *sem_opa, size_t num_sem_ops);

sem_id是由semget返回的信号量标识符,sembuf结构的定义如下:

struct sembuf{    short sem_num;//除非使用一组信号量,否则它为0    short sem_op;//信号量在一次操作中需要改变的数据,通常是两个数,一个是-1,即P(等待)操作,                    //一个是+1,即V(发送信号)操作。    short sem_flg;//通常为SEM_UNDO,使操作系统跟踪信号,                    //并在进程没有释放该信号量而终止时,操作系统释放信号量};

 

3、semctl函数: 直接控制信号量信息

int semctl(int sem_id, int sem_num, int command, ...); 

如果有第四个参数,它通常是一个union semum结构,定义如下:

union semun{    int val;    struct semid_ds *buf;    unsigned short *arry;};


 

这里举个进程使用信号量来通信的例子:  例子是两个相同的程序同时向屏幕输出字符,这个例子要同一时间只有一个进程输出字符

#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>union semun{int val;struct semid_ds *buf;unsigned short *arry;};static int sem_id = 0;static int set_semvalue();static void del_semvalue();static int semaphore_p();static int semaphore_v();int main(int argc, char *argv[]){char message = 'X';int i = 0;//创建信号量sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);if(argc > 1){//程序第一次被调用,初始化信号量if(!set_semvalue()){fprintf(stderr, "Failed to initialize semaphore\n");exit(EXIT_FAILURE);}//设置要输出到屏幕中的信息,即其参数的第一个字符message = argv[1][0];sleep(2);}for(i = 0; i < 10; ++i){//进入临界区if(!semaphore_p())exit(EXIT_FAILURE);//向屏幕中输出数据printf("%c", message);//清理缓冲区,然后休眠随机时间fflush(stdout);sleep(rand() % 3);//离开临界区前再一次向屏幕输出数据printf("%c", message);fflush(stdout);//离开临界区,休眠随机时间后继续循环if(!semaphore_v())exit(EXIT_FAILURE);sleep(rand() % 2);}sleep(10);printf("\n%d - finished\n", getpid());if(argc > 1){//如果程序是第一次被调用,则在退出前删除信号量sleep(3);del_semvalue();}exit(EXIT_SUCCESS);}static int set_semvalue(){//用于初始化信号量,在使用信号量前必须这样做union semun sem_union;sem_union.val = 1;if(semctl(sem_id, 0, SETVAL, sem_union) == -1)return 0;return 1;}static void del_semvalue(){//删除信号量union semun sem_union;if(semctl(sem_id, 0, IPC_RMID, sem_union) == -1)fprintf(stderr, "Failed to delete semaphore\n");}static int semaphore_p(){//对信号量做减1操作,即等待P(sv)struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = -1;//P()sem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){fprintf(stderr, "semaphore_p failed\n");return 0;}return 1;}static int semaphore_v(){//这是一个释放操作,它使信号量变为可用,即发送信号V(sv)struct sembuf sem_b;sem_b.sem_num = 0;sem_b.sem_op = 1;//V()sem_b.sem_flg = SEM_UNDO;if(semop(sem_id, &sem_b, 1) == -1){fprintf(stderr, "semaphore_v failed\n");return 0;}return 1;}


结果如下:

 

可以发现X和 我输入的第一个参数0都是成对的打印的,这是因为p操作进入临界区后,另一个进程无法进入,只有当前进程再次打印一个字符并执行V操作后,另一个进程才能够进去。

为了验证信号量的作用,这里举个不用信号量的例子:

#include <stdio.h>#include <stdlib.h>int main(int argc, char *argv[]){char message = 'X';int i = 0;if(argc > 1)message = argv[1][0];for(i = 0; i < 10; ++i){printf("%c", message);fflush(stdout);sleep(rand() % 3);printf("%c", message);fflush(stdout);sleep(rand() % 2);}sleep(10);printf("\n%d - finished\n", getpid());exit(EXIT_SUCCESS);}


结果:

结果很明显了,0X并不是成对打印的。

0 0