一、首先:我们要知道信号量是什么?
信号量的本质是数据操作锁,它本身不具有数据交换的功能,而是通过控制其他的通信资源(文件,外部设备)来实现进程间通信,它本身只是一种外部资源的标识。信号量在此过程中负责数据操作的互斥、同步等功能。
这是书本上介绍的信号量的概念,自己的理解比较简单:
信号量就是具有原子性的计数器,就相当于一把锁,在每个进程要访问临界资源时,必须要向信号量拿个锁”,它才能进去临界资源这个“房间”,并锁上门,不让其他进程进来,此时信号量执行P()操作,锁的数目减少了一个,所以计数器减1,;当它访问完成时,它出来,将锁还给信号量,执行V()操作,计数器加1;然后是下面的进程继续。这也体现了各个进程访问临时资源是互斥的。
“原子性”:表示的是一件事情的两种状态,做了这件事和没做这件事;
“计数器”:信号量通常描述的是临界资源的数目;同时信号量本身就是临界资源,它的目的也是保护临界资源,解决数 据不一致问题;
“临界资源”:不同进程可以看到的那份共同的资源;
“临界区”:多个进程访问临界资源的代码;
“互斥”:任何时刻,只允许一个临时区访问临时资源,并且属性是原子的。
二、我们为什么要使用信号量
为了防止出现因多个程序同时访问一个共享资源而引发的一系列问题,我们需要一种方法,它可以通过生成并使用令牌来授权,在任一时刻只能有一个执行线程访问代码的临界区域。临界区域是指执行数据更新的代码需独占式地执行。而信号量就可以提供这样的一种访问机制,让一个临界区同一时间只有一个线程在访问它,也就是说信号量是用来调协进程对共享资源的访问的。其中共享内存的使用就要用到信号量。
三、写出程序实例
让两个进程分别向显示器(linux下一切皆文件,临界资源)打印AA和BB,当没有信号量进行保护时,会出现数据混乱,例如:“AABBABAAAB...”,为了解决这一问题,我们创建信号量进行保护。打印“AA”或“BB”。
我们要写出代码接口:
创建信号量 creat_sems:
key:ftok函数生成
第一个参数_key,为整型值,是允许其他的进程访问信号量的一个整型的变量。所以的信号都是通过间接的方式获得的,运行的程序会提供一个信号的键值,系统为每一个键值赋予一个信号量,其他的处理函数只能通过对semget函数的返回值进行处理。
第二个参数_nsems,参数表示信号量的编号,几乎总是取值为1.
第三个参数_semflg,信号量的权限。
打开和存取操作与参数semflg中的内容相关。IPC_CREAT如果信号量集在系统内核中不存在,则创建信号量集。IPC_EXCL当和 IPC_CREAT一同使用时,如果信号量集已经存在,则调用失败。如果单独使用IPC_CREAT,则semget()要么返回新创建的信号量集的标识符,要么返回系统中已经存在的同样的关键字值的信号量的标识符。如果IPC_EXCL和IPC_CREAT一同使用,则要么返回新创建的信号量集的标识符,要么返回-1。IPC_EXCL单独使用没有意义。参数nsems指出了一个新的信号量集中应该创建的信号量的个数。
销毁信号量destory_sems()
功能:控制信号量的信息
返回值:成功返回0,失败返回-1
第一个参数semid:信号量标识码,还是通过semget来获得的,是semget函数的一个返回值
第二个参数semnum:信号量的编号,当用到信号量数组的时候,这个编号就会起到作用
第三个参数cmd:为允许的命令
第四个参数是个联合体:
初始化信号量init_sem()
与销毁信号量所用函数相同,但参数不同
p、v操作:
函数功能:用户改变信号量的值.
返回值:函数调用成功返回0,失败返回-1
参数semid:系统分配给该信号量的一个ID号,通过semget函数的返回值来获得,也称为信号的标识码
参数sops:一个指向信号量结构体数组的指针,信号量的结构体至少有3个成员。
nsops:进行操作信号量的个数,即sops结构变量的个数,需大于或等于1。最常见设置此值等于1,只完成对一个信号量的操作
注意这里的结构体sembuf
struct sembuf
{
unsigned short sem_num;操作信号在信号集中的编号,第一个信号的编号为0,第二个为1,一次后推
short sem_op;信号资源,如果有就分配,如果为负值就等待(wait),如果正值就分配信号资源
short sem_flg;信号操作标志,有两种状态,一个是SEM_UNDO,另一个是SEM_NOWAIT
}
SEM_NOWAIT//对信号的操作不能满足是,semop()函数就会阻塞,并立即返回,同时设置错误信息。
SEM_UNDO//程序结束时(无论是否正常结束),保证信号值会被重新设为semop()调用前的值。这样做
的目的在于避免在异常情况下结束时未将锁定资源解锁,造成该资源永远锁定。
flag一般为0,若flag包含IPC_NOWAIT,则该操作为非阻塞操作。若flag包含SEM_UNDO,则当进程退出的时候会还原该进程的信号量操作,这个标志在某些情况下是很有用的,比如某进程做了P操作得到资源,但还没来得及做V操作时就异常退出了,此时,其他进程就只能都阻塞在P操作上,于是造成了死锁。若采取SEM_UNDO标志,就可以避免因为进程异常退出而造成的死锁。
下面写一个小例子声明一个结构体:
struct sembuf sem1_opt_wakeup[1]={0,1,SEM_UNDO};
struct sembuf sem1_opt_wait[1]={1,-1,SEM_UNDO};
只有将 sem_flg 指定为 SEM_UNDO 标志后,semadj (所指定信号量针对调用进程的调整值)才会更新。 此外,如果此操作指定SEM_UNDO,系统更新过程中会撤消此信号灯的计数(semadj)。此操作可以随时进行---它永远不会强制等待的过程。调用进程必须有改变信号量集的权限。
comn.h文件
comn.c文件:
各函数接口
test_sems测试文件