一. 信号量

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

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

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


二. 信号量的创建与销毁

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

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

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



《完》