美文网首页Linux系统编程
Linux下的信号量使用

Linux下的信号量使用

作者: 小pb | 来源:发表于2019-11-01 17:00 被阅读0次

信号量

信号量原语

进程同步的主要方式之一。具体概念参考[《《操作系统概念精要》基本概念整理之进程同步篇(二)》],(https://www.jianshu.com/p/45c546d98e1c),这里不再赘述。
一般只有wait()和signal()操作,称为P,V操作。
Linux下的System V信号量的API都定义在 <sys/sem.h>中,包括三个系统调用,semget(), semop(), semctl()。他们被设计为操作一组信号量,即信号量集,而不是一个单独的信号量。因此这些接口看上去会被我们期望的要复杂一些。

semget系统调用
#include <sys/sem.h>                                                                     
                                                                                         
// 创建一个新的信号量集,或者获取一个已经存在的信号量集                                   
// key用来标识一个全局唯一的信号量集,                                                    // 使用信号量通信的两个进程需要把这个值设置为相同的                                      
// num_sems 指定要创建的/获取信号量集中的信号量的数目                                    
// 如果是创建那个num_sems必须设置,如果获取可以为0                                       
// sem_flags是一组标志,低端的9个bit 是该信号量的权限                                    
// 其格式和含义与open的mode的参数相同,                                                  // 此外它还可以和IPC_CREAT标志按位运算创建新的信号量集合                                 
int semget(key_t key, int num_sems, int sem_flags);  

semget成功时返回一个正整数值,它是信号量集的标识符。semget失败返回-1,并设置errno。

在创建信号量集合时,与之关联的内核数据结构体为semid_ds江北创建并初始化。 semid_ds的结构的定义为:

struct ipc_perm {                                                                        
  key_t key;  // 键值                                                                    
  uid_t uid;  // 所有者的有效用户id                                                      
  gid_t gid;  // 所有者的有效组id                                                        
  uid_t cuid;                                                                            
  git_t cgid;                                                                            
  mode_t mode;   //访问其他填充字段                                                      
};                                                                                       
                                                                                         
struct semid_ds {                                                                        
  struct ipc_perm sem_perm;  // 信号量的操作权限                                         
  unsigned long int sem_nsems; // 该信号量集中的信号量数目                               
  time_t sem_otime;       //  最后一次调用semop的时间                                    
  time_t sem_ctime;       //  最后一次调用semctl的时间                                   
};                

semop 系统调用

semop系统调用改变信号量的值,即执行P,V操作。
一般的信号量的P,V操作会修改内核中的这些变量:

  unsigned short int semval;  // 信号量的值                                                
  unsigned short int semzcnt; // 等待信号量变为0的进程数量                                 
  unsigned short int semncnt;  // 等待信号量值增加的进程数量                               
  pid_t sempid; // 上一次执行semop操作的进程id 

semop的定义如下:

  #include<sys/sem.h>
   
  // sem_id参数是由semget 调用返回的信号量标识符                                           
  // struct sembuf {                                                                       
  //   unsigned short int sem_num;  // 信号量集中信号量的编号                              
  //   short int sem_op;    // 成员操作类型, 可选值为正整数,0和负整数                                                               
  //   short int sem_flg;      // sem_flg和sem_op共同操作决定最后的操作
  // }                                                                                     
  int semop(int sem_id, struct sembuf* sem_op, size_t num_sem_ops); 

sem_num,成员是信号量中信号量的编号,0表示是信号量集中的第一个信号量。sem_op成员直到操作类型,可选值为正整数,0和 负整数。每种类型的操作的行为有收到sem_flag成员的影响,sem_flg可选值为IPC_NOWAIT和SEM_UNDO。 IPC_NOWAIT表示无论信号量操作是否成功,semop的调用都将立即返回,这类似于非阻塞I/O操作。SEM_UNDO的含义是,当进程退出时,取消正在进行的semop操作。

具体的来说,sem_op和sem_flg按如下方式来影响semop最后的执行结果:

  • 如果sem_op大于0,则semop将被操作的信号量的值semval 增加sem_op。该操作要求调用进程在对被操作信号量集上拥有写权限。
  • 如果sem_op等于0,表示这是一个“等待0”操作。该操作要求调用进程对呗操作信号量拥有读权限。如果此时信号量的值是0,则调用立即成功返回。如果信号量的值不是0,则semop操作失败或者阻塞进程以等待信号量变为0。
    在这种情况下需要看下,sem_flg的设置。如果IPC_NOWAIT标志被指定,semop立即返回一个错误,并设置errno为EAGAIN。如果未指定IPC_NOWAIT,则信号量的值semzcnt 加1,进程被投入睡眠,知道下列3个条件之一发生:
    1. 信号量的值变为0,此时系统将该信号量semzcnt 减1.
    2. 被操作的信号量所在的信号量集被进程移除,此时,semop调用失败返回,errno被设置为EIDRM。
    3. 调用被信号中断,此时,semop调用失败返回,errno被设置EINTR,同时系统将信号量的semzcnt减1。
  • sem_op小于0,则表示对信号量进行减操作。即期望获得信号量,该操作要求调用进程对呗操作信号量集拥有写权限。如果信号的值semval大于或者等于sem_op的绝对值,则semop操作成功,调用进程立即获得信号量,并且系统将该信号量的semval减去sem_op的绝对值。如果信号量的值semval小于sem_op的绝对值,则,semop失败返回或者阻塞等待信号量可用。在这种情况下,IPC_NOWAIT的标志被指定时,semop立即返回一个错误,并设置errno为EAGAIN。如果为指定IPC_NOWAIT标志,则信号量的semncnt加1,进程被投入睡眠知道下面三个条件满足:
    1. 信号量的值semval大于等于sem_op的绝对值,此时信号量的semncnt减1,并将semval减去sem_op的绝对值。
    2. 被操作信号量所在的信号量集被进程删除,调用返回失败,errno被设置为EIDRM;
    3. 调用被信号中断,此时semop调用失败返回,errno被设置为EINTR,同时系统将semncnt值减1。

semop系统调用的第三个参数是num_sem_ops指定要执行的操作个数,即,sem_ops数组中元素的个数。sem_op对数组sem_ops中每个元祖顺序执行操作,并且该操作为原子操作.

semctl 系统调用

semctl系统调用允许调用者对信号量进行直接控制。

#include <sys/sem.h>

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

sem_id参数是由semget 返回的信号量集标识符。
sem_num, 指定被操作的信号量在信号量集中的编号。
command 参数执行要执行的命令。
有的命令需要调用者传递第四个参数,第四个是用户自定义的参数,但<sys/sem.h>中定义了它的推荐格式。

union semun {                                                                            
  int val;     // 用于SETVAL命令                                                         
  struct semid_ids* buf;   // 用于IPC_STAT和IPC_SET命令                                  
  unsigned short* array;   // 用于GETALL和SETALL命令                                     
  struct seminfo* _buf;    // 用于IPC_INFO命令                                           
};                                                                                       
                                                                                         
struct seminfo {                                                                         
  int semmap;   // Linux 内核没有使用                                                    
  int semmni;   // 系统最多可以拥有的信号量集数目                                        
  int semmns;   // 系统最多可以拥有的信号量的数目                                        
  int semmnu;   // Linux 内核没有使用                                                    
  int semmsl;   // 一个信号量集最多允许包含的信号量数目                                  
  int semopm;   // semop一次最多能执行的sem_op操作数目                                   
  int semume;   // Linux内核没有使用                                                     
  int semusz;   // sem_undo 结构体的大小                                                 
  int semvmx;   // 最大允许的信号量值                                                    
  int semaem;   // 最多允许的UNDO的次数                                                  
}; 

以下是semctl的command的参数:

信号量semctl.png

使用信号量进行通信:
代码来源于:Linux高性能服务器编程


// semget的调用者可以给其key传递一个特殊的键值IPC_PRIVATE(值为0)
// 这样无论信号量是否存在,semget都将创建一个新的信号量。使用该键值
// 创建的信号量
// 并非像它的名字那样是进程私有的。其他进程,尤其子进程,也有办法来访问这个信号量。
// 下面的例子就是在父子进程间使用一个IPC_PRIVATE信号量来进行同步。

#include <sys/sem.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

union semun {
  int val;  // 用于setvalue;
  struct semid_ds* buf;  // 用于ipc_stat 和ipc_set 命令
  unsigned short* array;  // 用于getall 和setall 命令
  struct seminfo* __buf;  // 用于ipc_info命令
};

// op为-1时执行p操作,op为1执行v操作。
// semop操作
void pv( int sem_id, int op) {
  struct sembuf sem_b;
  sem_b.sem_num = 0;  // 信号集中编号为1的信号量
  sem_b.sem_op = op;  // 执行op操作, 大于0,执行+1操作, 小于0 执行-1 操作
  sem_b.sem_flg = SEM_UNDO;  // 信号量操作进程退出时取消正在进行op操作。
  semop(sem_id, &sem_b, 1);  // 操作信号量的个数只有一个
}

int main(int argc, char* argv[]) {

  // 创建一个信号量集,它创建一个新的信号集, 权限为读写
  int sem_id = semget(IPC_PRIVATE, 1, 0666);

  // semctl中第四个参数的格式
  union semun sem_un;
  sem_un.val = 1;

  // SETVAL社会semun.buf的数据成员复制到信号量集关联的内核数据结构中,
  // 同时内核数据中的semid_ds。sem_ctime被更新。
  semctl(sem_id, 0, SETVAL, sem_un);

  // 复制一个新进程
  pid_t pid = fork();

  if (pid < 0) {
    return -1;
  } else if (pid == 0) {
    printf("child try to get binary sem\n");
    // 在父子进程共享IPC_PRIVATE 信号量的关键在于二者都可以操作该信号量的标识符
    // sem_id;
    pv (sem_id, -1);
    printf("child get the sem and would releases it after 20 seconds\n");
    sleep(20);
    pv (sem_id, 1);
    exit(0);
  } else {
    printf("parents try to get binary sem\n");
    pv (sem_id, -1);
    printf("parent get the sem and would releases it after 20 seconds\n");
    sleep(20);
    pv (sem_id, 1);
  }

  waitpid(pid, NULL, 0);
  // 立即移除信号集,唤醒所有等待该信号集的进程
  semctl(sem_id, 0, IPC_RMID, sem_un);

  return 0;
}

相关文章

  • python多进程爆破验证码

    推荐在linux环境下使用,找到之后可以使用信号量signal,Ctrl + C可以退出所有进程.

  • Linux下的信号量使用

    信号量 信号量原语 进程同步的主要方式之一。具体概念参考[《《操作系统概念精要》基本概念整理之进程同步篇(二)》]...

  • 自己实现消息队列msg queue linux C

    因为不仅仅信号量,共享内存、消息队列在NDK下都不能用,所以之前使用Linux 下IPC的消息队列,msgget/...

  • 哈工大操作系统实验(四)进程同步

    本次实验比较简单,相较于前几届需要在linux0.11 底下实现信号量,这次只需要在linux下写个利用信号量解决...

  • c++ semaphore信号量的使用

    前言 目前网上可以查找到很多关于信号量的实现文章,但是讲解在linux下使用semaphore的文章比较少;c++...

  • Linux 信号量

    信号量在在Linux中有着举足轻重的地位,Linux中一些异常错误也由信号量触发,另外信号量也可以用来做进程间的通...

  • Linux线程同步

    Linux下提供了多种方式来处理线程同步,最常用的是互斥锁、条件变量和信号量。 Linux线程同步-----互斥锁...

  • iOS多线程异步信号量

    使用GCD信号量写了一个小demo,在不使用调度组和线程依赖的情况下,可以使用信号量git链接: https://...

  • IOS信号量(PV操作)

    一、信号量的简单介绍: 1.信号量: 信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设...

  • 学习笔记5

    Linux多线程同步机制 - 信号量信号量函数定义如下:include int semctl(int sem_i...

网友评论

    本文标题:Linux下的信号量使用

    本文链接:https://www.haomeiwen.com/subject/spafbctx.html