semop函数主要功能是对信号量进行P/V操作。

P操作责把当前进程由运行状态转换为阻塞状态,直到另外一个进程唤醒它。操作为:申请一个空闲资源(把信号量减1),若成功,则退出;若失败,则该进程被阻塞;

V操作负责把一个被阻塞的进程唤醒,它有一个参数表,存放着等待被唤醒的进程信息。操作为:释放一个被占用的资源(把信号量加1),如果发现有被阻塞的进程,则选择一个唤醒之。

semop函数原型如下:

int semop(int semid, struct sembuf  *sops, unsigned nsops);

semid标识其操作的信号量集。

其中sops指向一个如下结构的数组:

struct sembuf{

unsigned short   sem_num;     /* semaphore number: 0, 1, …., nsems-1 */

short   sem_op;      /* semaphore operation: <0, 0, >0 */

short   sem_flag;     /* semaphore flags : 0, IPC_NOWAITE, SEM_UNDO */

};

nops参数指出由sops指向的sembuf结构数组中元素的数目。该数组中每个元素给目标信号量集内某个特定的信号量指定一个操作。这个特定的信号量由sem_num指定,0代表第一个元素,1代表第二个元素,依次类推,直到nsems-1,其中nsems是目标信号量集内成员信号量的数目。

sem_flag的取值有三种:

  1).0代表阻塞调用

 2).IPC_NOWAIT代表非阻塞调用

 3).如果设置了SEM_UNDO标志,那么在进程结束时,相应的操作将被取消,这是比较重要的一个标志位。如果设置了该标志位,那么在进程没有释放共享资源就退出时,内核将代为释放。如果为一个信号量设置了该标志,内核都要分配一个SEM_UNDO结构来记录它,为的是确保以后资源能够安全释放。事实上,如果进程退出了,那么它所占用的资源就释放了,但信号量值却没有改变,此时,信号量值反映的已经不是资源占有的实际情况,在这种情况下,问题的解决就靠内核来完成。这有点像僵尸进程,进程虽然退出了,资源也都释放了,但内核进程表中仍然有它的记录,此时就需要父进程调用waitpid来解决问题了。

semop对信号的操作是由sem_op的值确定的,以下是对sem_op取值的分析:

  • sem_op 为正数时,会把sem_op的值加到操作的信号量的信号值上。如果sem_flg被设置为IPC_UNDO,无论程序正常结束与否,都会把信号值重新设置为调用semop函数前得值。这对应于进程释放占用的资源数。

  • sem_op为负数时,如果要操作的信号量的值大于或者等于sem_op的绝对值,则从信号量值中加上sen_op的值。

  • 如果信号量值小于sem_op的绝对值,则有如下:

    • 如果sem_flg的值为IPC_NOWAIT,那么semop出错,返回EAGAIN。

    • 如果sem_flg没有设置为IPC_NOWAIT,则该信号量的semncnt的值加1,然后此进程挂起,直到此信号量的值大于sem_op的绝对值,才执行semop操作;或者此信号量从系统中删除,此时semop返回EIDRM;或者该挂起进程捕捉到信号,从信号处理程序返回,此时,semop出错返回EINTR。

    • 如果sem_op的值为0,则调用进程希望等到该信号量值变成0。

      若通过kill命令把其中一个进程杀死,且该进程还没有执行V操作释放资源。若使用SEM_UNDO标志,则操作系统将自动释放该进程持有的信号量,从而使得另外一个进程可以继续工作。若没有这个标志,另外进程将P操作永远阻塞。

      因此,一般建议使用SEM_UNDO标志。