Linux系统编程

Linux下的信号量使用

2019-11-01  本文已影响0人  小pb

信号量

信号量原语

进程同步的主要方式之一。具体概念参考[《《操作系统概念精要》基本概念整理之进程同步篇(二)》],(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最后的执行结果:

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;
}
上一篇 下一篇

猜你喜欢

热点阅读