进程间通信(IPC)之————信号量

本文介绍了信号量的概念,作为进程间通信的一种方式,用于控制临界资源的互斥访问。信号量本质是数据操作锁,通过P操作(减操作)和V操作(加操作)实现资源的申请和释放。文中还详细讲解了信号量的创建、销毁、初始化以及P、V操作的实现,并通过实例展示了如何使用信号量解决进程间的同步问题,确保资源的有序访问。

一. 信号量

    在谈论信号量之前,先要提到临界资源临界区的概念,临界资源是指多个进程访问但一个时间段内只允许一个进程独占的资源,而临界区是指多个进程访问临界资源的这一段公共的代码。

    信号量的本质是一种数据操作锁,也可以说就是一个计数器,它本身并不能提供对进程间的通信,而是通过控制某一资源来完成进程间的互斥和同步,比如当一个进程请求某一用信号量来表示的临界资源时,先要进行检查该资源的信号量,若信号量大于零,表示有资源可用,若信号量等于零,则表示有其他进程正在使用该资源,则申请的进程需要挂起等待。

    信号量的存在其实就是给进程间的信号,表示该资源可不可以被访问,因此也是作为进程间通信的一种方式。


二. 信号量的创建与销毁

    信号量和前面提到的管道、消息队列是一样的,都是需要函数创建来获取的:

wKiom1cPO7jAAimFAAAnd4ht7cQ852.png

函数参数中,key在消息队列中就提到过,它可以被认为是一个端口号,用ftok函数来创建:

wKioL1cPPPLDpGDmAAAJYnVVfDs415.png

pathanme是文件路径名,而proj_id是一个整数,二者结合可转换成一个整数标识。

nsems表示要创建多少个信号量,因为semget创建的是一个信号量集,可能有多个资源需要被标识;

semflg也和消息队列中的msgflg一样,有两个选项IPC_CREATIPC_EXCL,当两个一起使用时可以保证信号量集是新创建的,创建失败返回-1;当IPC_CREAT单独使用时若信号量集已存在则返回已存在的,若不存在则新建一个;


当semget创建信号量集成功就会返回一个信号量集的sem_id的整数,若失败返回-1;


信号量集创建出来了当然使用完成之后也是需要销毁的:

wKiom1cPQJ2AS4UtAAAIst0izPY700.png

函数参数中第一个当然是要删除的信号量集的sem_id;

semnum表示要对信号量集中的第几个信号量进行cmd操作;

cmd操作为IPC_RMID删除

下面要提到cmd的一个操作SETVAL,可以用来初始化信号量,可以看到semctl函数中最后一项是可变参数列表,也就是说semctl的参数可以为三个或四个,当为四个参数时,最后一个必须是一个联合体,要按如下定义:

wKiom1cPQaPxrlgNAAAzXf-sYYs504.png

这个联合体不一定定义在某个系统头文件中,若没有,则需要用户自己定义;联合中第一个整形val就是需要初始化的值;至于后面的buf是IPC_STST、IPC_SET使用的缓存区;array是GETALL、SETALL所使用的数组及_buf表示IPC_INFO(Linux特有)使用的缓存区;以上这些都是需要用户自定的。


三. 信号量的P操作与V操作

    当一个进程要申请某个资源时,系统会先检查该资源的信号量是否大于零,若大于零表示有资源可用,这时该进程就要使用该资源,因此应该将该资源的信号量减为零,也就是P操作;当进程使用完资源之后要将其放回供其他进程使用,此时就应该将信号量的值加回为原值,也就是V操作了:

wKioL1cPREnhmswhAAApAvici1s486.png

函数参数中,

semid就不说了是表示要进行哪一个信号量操作的sem_id;

sops是一个结构体指针,该结构体应如下定义:

wKiom1cPRVmgtwuDAAAHBwYbc0w776.png

sem_num表示是信号量集中的第几个信号量;

sem_op表示要进行什么样的操作,小于零表示减,大于零表示加,等于0是;

sem_flg有两个选项,IPC_NOWAITSEM_UNDO,IPC_NOWAIT表示如果没有资源可用则不阻塞直接返回       EAGAIN,如果一个操作指定为SEM_UNDO,当进程终止的时候它就会自动撤消该操作恢复原来值;

nsops表示有几个要操作的信号量数;



栗子时间:

和前面谈消息队列时一样,将信号量所需要的函数封装起来:

先是信号量的创建,为了区别是新创建的信号量还是获得已存在的信号量,可以根据传参的不同封装两个函数creat_sem和get_sem,同时还要有初始化:

wKioL1cPkazT3gP6AACqoydO4uY025.jpg

接下来是函数P、V操作和销毁信号量:

wKioL1cPkc-w5QsNAACqFTMibfY237.png



下面就可以先写一个栗子,我们知道显示器在一段时间内只允许一个进程访问时临界资源,当没有信号量标识时,两个进程同时向显示器输出会有什么样的结果呢:

wKiom1cPYprSCgm3AAAg-spxKvQ294.png


运行程序可以看到A和B是乱序输出的,也就是当两个进程同时访问一个资源时产生了冲突;

wKioL1cPY1LzPYiKAAALKYByUm4776.png


下面就可以将程序改为运用信号量完成进程间通信,也就是当两个进程同时访问临界资源时有一个交流的过程,你在用我就等着我在用你就等着:

wKiom1cPkULSfD2TAAAa_HRGPiY438.png

wKiom1cPkUaDnTTRAAAQTSMq12g555.png


上面的程序中一定要注意,fork子进程应该在父进程creat_sem之后,否则若子进程先运行get_sem出了信号量,之后父进程在运行去creat_sem就得不到信号量了,运行程序就会得到如下AABB交错有序的结果,如此也就完成了进程间的另一种通信方式:

wKioL1cPkf6Axbj1AAALKYByUm4570.png

最后要说的一点就是,对信号量的操作是原子性的,因为并没有中间值,但是在信号量的创建及初始化就不一定是原子的了。



《完》

本文出自 “敲完代码好睡觉zzz” 博客,请务必保留此出处http://2627lounuo.blog.51cto.com/10696599/1763921

信号量是一种用于进程间通信和同步的机制。它是一个计数器,用于保证在共享资源上的互斥访问。在Linux系统中,可以使用信号量来实现进程间的同步和互斥。以下是信号量的基本概念: - 计数器:信号量的值是一个计数器,它可以被多个进程共享。 - P操作:当一个进程需要访问共享资源时,它必须执行P操作,该操作会将信号量的值减1。如果信号量的值为0,则进程将被阻塞,直到信号量的值大于0。 - V操作:当一个进程使用完共享资源后,它必须执行V操作,该操作会将信号量的值加1。如果有进程正在等待该信号量,则唤醒其中一个进程继续执行。 在ZUCC中,可以使用信号量来实现进程的同步和互斥。首先,需要使用semget函数创建一个信号量集合,并使用semctl函数对信号量进行初始化。然后,可以使用semop函数执行P和V操作。例如,下面是一个简单的示例程序,用于演示信号量的使用: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/sem.h> #define SEM_KEY 1234 union semun { int val; struct semid_ds *buf; unsigned short *array; }; int main() { int semid, pid; union semun arg; struct sembuf sb; // 创建信号量集合 semid = semget(SEM_KEY, 1, IPC_CREAT | 0666); if (semid == -1) { perror("semget"); exit(EXIT_FAILURE); } // 初始化信号量 arg.val = 1; if (semctl(semid, 0, SETVAL, arg) == -1) { perror("semctl"); exit(EXIT_FAILURE); } // 创建子进程 pid = fork(); if (pid == -1) { perror("fork"); exit(EXIT_FAILURE); } else if (pid == 0) { // 子进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Child process\n"); // 子进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } else { // 父进程执行P操作 sb.sem_num = 0; sb.sem_op = -1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop P"); exit(EXIT_FAILURE); } printf("Parent process\n"); // 父进程执行V操作 sb.sem_num = 0; sb.sem_op = 1; sb.sem_flg = SEM_UNDO; if (semop(semid, &sb, 1) == -1) { perror("semop V"); exit(EXIT_FAILURE); } exit(EXIT_SUCCESS); } return 0; } ``` 在上述代码中,创建了一个信号量集合,并将其初始化为1。然后,创建了一个子进程和一个父进程,它们分别执行P和V操作。由于信号量的初始值为1,因此父进程和子进程都可以顺利地执行。如果将信号量的初始值改为0,那么父进程和子进程都将被阻塞,直到有一个进程执行V操作为止。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值