信号量知识汇总

1、什么是信号量
  信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问,保护忙资源。
2、信号量的用途
  保护共享资源,使得资源在一个时刻只有一个进程(线程 ),信号量的值为正的时候,说明它空闲。所测试的线程可以锁定而使用它。若为0,说明它被占用,测试的线程要进入睡眠队列中,等待被唤醒
3、信号量的分类 
  1)内核信号量,由内核控制路径使用 
  2)用户态进程使用的信号量 ,分为POSIX信号量和 SYSTEMV信号量。
   其中:POSIX信号量,分为有名信号量和无名信号量;有名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。无名 信号量,其值保存在内存中。
3.1、信号量机制简介
   当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。
4、不同信号量使用
4.1、内核信号量
4.1.1、内核信号量构成
  内核信号量类似于自旋锁 ,锁关闭,内核控制路径不执行,当内核控制路径要获取忙资源是,进程挂起;当资源释放是,进程重新运行。只有睡眠函数可以获得内核信号量。
  内核信号量是 struct semaphore 类型的对象,它在<asm/semaphore.h>中定义: 
  struct semaphore { 
                atomic_t count; 
                int sleepers; 
                wait_queue_head_t wait; 
                   } 
 其中:
  count:相当于信号量的值,大于0,资源空闲;等于0,资源忙,但没有进程等待这 个保护的资源;小于0,资源不可用,并至少有一个进程等待资源。
  wait:存放等待队列链表的地址,当前等待资源的所有睡眠进程都会放在这个链表中。 
  sleepers:存放一个标志,表示是否有一些进程在信号量上睡眠。
4.1.2、内核信号量的相关函数
 1)初始化: 
  void sema_init (struct semaphore *sem, int val); 
  void init_MUTEX (struct semaphore *sem); //sem 的值置为 1,表示资源空闲 
  void init_MUTEX_LOCKED (struct semaphore *sem); //sem的值置为0,表示资 源忙
 2)申请内核信号量所保护的资源: 
  void down(struct semaphore * sem); // 可引起睡眠 
  int down_interruptible(struct semaphore * sem); //down_interruptible能被信 号打断 
  int down_trylock(struct semaphore * sem); // 非阻塞函数,不会睡眠,无法锁定 资源则 
3)释放内核信号量所保护的资源: 
  void up(struct semaphore * sem); 
4.1.3、内核信号量的常用 函数
  当多个线程同时访问相同的资源时,可能会引发“竞态“,因此我们必须对共享资源进行并发控制。 解决并发控制的最常用方法是自旋锁与信号量(绝大多数时候作为互斥锁使用) 
  ssize_t globalvar_write(struct file *filp,const char *buf,size_t len,loff_t*off) 
   { 
   //获得信号量 
   if(down_interruptible(&sem)) 
    { 
     return - ERESTARTSYS; 
    } 
   //将用户空间的数据复制到内核空间的 global_var 
   if(copy_from_user(&global_var, buf, sizeof(int))) 
    { 
    up(&sem); 
    return - EFAULT; 
    } 
    //释放信号量 
    up(&sem); 
    return sizeof(int); 
    } 
4.2、用户进程信号量(POSIX 信号量与SYSTEM V 信号量 )
    1、对于POSIX,信号量是个非负整数,常用于线程间同步 ;SYSTEM V信号量则是一个或多个信号量的集合,它对应的是一个信号量结构体,常用于进程间同步 。
    2POSIX 信号量的引用头文件是“<semaphore.h>”,SYSTEM V 信号量的引用头 
文件是“<sys/sem.h>”
    3System V 信号量是复杂的,Posix 信号量是简单。
4.2.1POSIX 信号量 
4.2.1.1、无名信号量
    无名信号量常用于多线程间的同步,同时也用于相关进程间的同步 ,即它是多个进程(线程)的共享变量,无名信号量要保护的变量也必须是多个进程 (线程)的共享变量。
    1无名信号量相关函数
    int sem_init(sem_t *sem, int pshared, unsigned int value); //初始化函数
      pshared==0 用于同一多线程的同步; 
      pshared>0用于多个相关进程间的同步(即由fork产生的) 
    int sem_getvalue(sem_t *sem, int *sval); //取回信号量sem 的当前值,把该值保存到sval中。
      若有1个或更多的线程或进程调用 sem_wait 阻塞在该信号量上,该函数返回两种值: 返回 0 或返回阻塞在该信号量上的进程或线程数目
    int sem_wait(sem_t *sem); // 阻塞的函数 ,测试所指定信号量的值,它的操作是原子的。 
      sem>0,那么它减 1并立即返回。 
      sem==0,则睡眠直到 sem>0,此时立即减 1,然后返回。
    int sem_trywait(sem_t *sem); //非阻塞的函数 ,其他的行为和 sem_wait 一样
      sem==0,不是睡眠,而是返回一个错误 EAGAIN
    int sem_post(sem_t *sem); //释放资源。把指定的信号量sem的值加 1; 呼醒正在等待该信号量的任意线程。
    2)无名信号量的应用
    1、无名信号量多线程同步
     注明:线程 1 先执行完,然后线程 2 才继 续执行,直至结束 
     #include <pthread.h> 
     #include <semaphore.h> 
     #include <sys/types.h> 
     #include <stdio.h> 
     #include <unistd.h> 
     int number; // 被保护的全局变量 
     sem_t sem_id1, sem_id2; 
     void* thread_one_fun(void *arg) 
    { 
      sem_wait(&sem_id1); 
      printf("thread_one have the semaphore\n"); 
      number++; 
      printf("number = %d\n",number); 
      sem_post(&sem_id2); 
    } 
     void* thread_two_fun(void *arg) 
   { 
    sem_wait(&sem_id2); 
    printf("thread_two have the semaphore \n"); 
    number--; 
    printf("number = %d\n",number); 
    sem_post(&sem_id1); 
   } 
   int main(int argc,char *argv[]) 
  { 
   number = 1; 
   pthread_t id1, id2; 
   sem_init(&sem_id1, 0, 1); // 空闲的 
   sem_init(&sem_id2, 0, 0); // 忙的 
   pthread_create(&id1,NULL,thread_one_fun, NULL); 
   pthread_create(&id2,NULL,thread_two_fun, NULL); 
   pthread_join(id1,NULL); 
   pthread_join(id2,NULL); 
   printf("main,,,\n") 
   return 0; 
  } 
  2、无名信号量在相关进程间的同步
   注明:相关进程,是因为本程序中共有2个进程,其中一个是另外一个的子进程.
   对于fork来说,子进程只继承了父进程的代码副本,mutex理应在父子进程 中是相互独立的两个变量,但由于在初始化 mutex 的时候,pshared=1 指定了mutex 处于共享内存区域,所以此时mutex变成了父子进程共享的一 个变量。此时,mutex 就可以用来同步相关进程了。
   #include <semaphore.h> 
   #include <stdio.h> 
   #include <errno.h> 
   #include <stdlib.h> 
   #include <unistd.h> 
   #include <sys/types.h> 
   #include <sys/stat.h> 
   #include <fcntl.h> 
   #include <sys/mman.h> 
   int main(int argc, char **argv) 
   { 
    int fd,i,count=0,nloop=10,zero=0,*ptr; 
    sem_t mutex; 
    //open a file and map it into memory 
    fd = open("log.txt",O_RDWR|O_CREAT,S_IRWXU); 
    write(fd,&zero,sizeof(int)); 
    ptr = mmap( NULL,sizeof(int),PROT_READ| PROT_WRITE,MAP_SHARED,fd,0 ); 
    close(fd); 
   /* create, initialize semaphore */ 
    if( sem_init(&mutex,1,1) < 0) // 
   { 
    perror("semaphore initilization"); 
    exit(0); 
   } 
    if (fork() == 0) 
   { /* child process*/ 
    for (i = 0; i < nloop; i++) 
   { 
    sem_wait(&mutex); 
    printf("child: %d\n", (*ptr)++); 
  sem_post(&mutex); 
  } 
  exit(0); 
  } 
  /* back to parent process */ 
  for (i = 0; i < nloop; i++) 
  { 
   sem_wait(&mutex); 
   printf("parent: %d\n",(*ptr)++); 
   sem_post(&mutex); 
  } 
   exit(0); 
  } 
4.2.1.2 有名信号量
   把信号量的值保存在文件中 ,既可以用于线程,也可以用于相关进程间,甚至是不相关 进程。
   由于有名信号量的值是保存在文件中的,所以对于相关进程来说,子进程是继承了父 进程的文件描述符,那么子进程所继承的文件描述符所指向的文件是和父进程一样的,当 然文件里面保存的有名信号量值就共享了。 
  1)有名信号量相关函数
   sem_t *sem_open(const char *name, int oflag, mode_t mode , int value); //初始化
   说明:有名信号量使用 sem_open 代替 sem_init,另外在结束的时候要像关闭文件 一样去关闭这个有名信号量。
   功能:打开一个已存在的有名信号量,或创建并初始化一个有名信号量。一个单一的调用就完 成了信号量的创建、初始化和权限的设置
   参数:name 是文件的路径名; 
        Oflag O_CREAT O_CREAT|EXCL 两个取值; 
        mode_t 控制新的信号量的访问权限; 
        Value 指定信号量的初始化值。
   其次有名信号量和无名信号量共享sem_wait sem_post函数
   在退出或是退出处理函数中调用sem_unlink()去删除系统中的信号量
   2)有名信号量应用
    1、服务进程和客户进程都使用 shmget shmat 来获取得一块共 享内存资源。然后利用有名信号量来对这块共享内存资源进行互斥保护。
    <u>File1: server.c </u> 
    #include <sys/types.h> 
    #include <sys/ipc.h> 
    #include <sys/shm.h> 
    #include <stdio.h> 
    #include <semaphore.h> 
    #include <sys/types.h> 
    #include <sys/stat.h> 
    #include <fcntl.h> 
    #define SHMSZ 27 
    char SEM_NAME[]= "vik"; 
    int main() 
    { 
    char ch; 
    int shmid; 
    key_t key; 
    char *shm,*s; 
    sem_t *mutex; 
    //name the shared memory segment 
    key = 1000; 
    //create & initialize semaphore 
    mutex = sem_open(SEM_NAME,O_CREAT,0644,1); 
   if(mutex == SEM_FAILED) 
   { 
   perror("unable to create semaphore"); 
   sem_unlink(SEM_NAME); 
   exit(-1); 
   } 
   //create the shared memory segment with this key 
   shmid = shmget(key,SHMSZ,IPC_CREAT|0666); 
   if(shmid<0) 
   { 
   perror("failure in shmget"); 
   exit(-1); 
   } 
   //attach this segment to virtual memory 
   shm = shmat(shmid,NULL,0); 
   //start writing into memory 
   s = shm; 
   for(ch='A';ch<='Z';ch++) 
   { 
   sem_wait(mutex); 
   *s++ = ch; 
   sem_post(mutex); 
   } 
   //the below loop could be replaced by binary semaphore 
   while(*shm != '*') 
   { 
    sleep(1); 
   } 
   sem_close(mutex); 
   sem_unlink(SEM_NAME); 
   shmctl(shmid, IPC_RMID, 0); 
   exit(0); 
   } 
   
   <u>File 2: client.c</u> 
   #include <sys/types.h> 
   #include <sys/ipc.h> 
   #include <sys/shm.h> 
   #include <stdio.h> 
   #include <semaphore.h> 
   #include <sys/types.h> 
   #include <sys/stat.h> 
   #include <fcntl.h> 
   #define SHMSZ 27 
   char SEM_NAME[]= "vik"; 
   int main() 
   { 
   char ch; 
   int shmid; 
   key_t key; 
   char *shm,*s; 
   sem_t *mutex; 
   //name the shared memory segment 
   key = 1000; 
   //create & initialize existing semaphore 
   mutex = sem_open(SEM_NAME,0,0644,0); 
   if(mutex == SEM_FAILED) 
   { 
   perror("reader:unable to execute semaphore"); 
   sem_close(mutex); 
   exit(-1); 
   } 
   //create the shared memory segment with this key 
   shmid = shmget(key,SHMSZ,0666); 
   if(shmid<0) 
  { 
  perror("reader:failure in shmget"); 
  exit(-1); 
  } 
  //attach this segment to virtual memory 
  shm = shmat(shmid,NULL,0); 
  //start reading 
  s = shm; 
  for(s=shm;*s!=NULL;s++) 
  { 
  sem_wait(mutex); 
  putchar(*s); 
  sem_post(mutex); 
  } 
  //once done signal exiting of reader:This can be replaced by another 
  semaphore 
  *shm = '*'; 
  sem_close(mutex); 
  shmctl(shmid, IPC_RMID, 0); 
  exit(0); 
  } 
4.2.2SYSTEM V 信号量
  信号量值的集合,而不是单个信号量。相关的信号量操作函数由<sys/ipc.h>引 用。 
4.2.2.1、信号量结构体
  struct semid_ds { 
            struct ipc_perm sem_perm; /* 信号量集的操作许可权限 */ 
            struct sem *sem_base; /* 某个信号量sem结构数组的指针,当前信号量集 中的每个                    信号量对应其中一个数组元素 */ 
            ushort sem_nsems; /* sem_base 数组的个数 */ 
            time_t sem_otime; /* 最后一次成功修改信号量数组的时间 */ 
            time_t sem_ctime; /* 成功创建时间 */ 
                  } 
  struct sem { 
            ushort semval; /* 信号量的当前值 */ 
            short sempid; /* 最后一次返回该信号量的进程 ID */ 
            ushort semncnt; /* 等待 semval 大于当前值的进程个数 */
            ushort semzcnt; /* 等待 semval 变成 0 的进程个数 */ 
             }; 
 4.2.2.2、相关函数
   SYSTEM V 信号量是 SYSTEM V IPC(SYSTEM V 进程间通信)的组成部分,其 他的有 SYSTEM V 消息队列,SYSTEM V 共享内存。而关键字和 IPC描述符无疑是它们的 共同点。
   IPC描述符相当于引用 ID ,要想使用 SYSTEM V 信号量(MSGSHM),就必 须用 IPC 描述符来调用信号量。而 IPC描述符是内核动态提供的(通过 semget 来获取), 用户无法让服务器和客户事先认可共同使用哪个描述符,所以有时候就需要到关键字KEY 来定位描述符。
   KEY只会固定对应一个描述符(由内核完成),假如服务器和 客户事先认可共同使用某个 KEY,那么大家就都能定位到同一个描述符,也就能定位到同 一个信号量,这样就达到了 SYSTEM V 信号量在进程间共享的目的。
   int semget(key_t key, int nsems, int oflag) //创建和打开信号量 
   (1) nsems>0 : 创建一个信的信号量集,指定集合中信号量的数量,一旦创建就不能更 改。 
   (2) nsems==0 : 访问一个已存在的集合 
   (3) 返回的是一个称为信号量标识符的整数,semop semctl 函数将使用它。 
   (4) 创建成功后信号量结构被设置: 
   .sem_perm uid gid 成员被设置成的调用进程的有效用户 ID 和有效组 ID 
   .oflag 参数中的读写权限位存入 sem_perm.mode 
   .sem_otime 被置为 0,sem_ctime 被设置为当前时间 
   .sem_nsems 被置为 nsems 参数的值 
   该集合中的每个信号量不初始化,这些结构是在 semctl,用参数 SET_VAL,SETALL 初始化的。
   int semop(int semid, struct sembuf *opsptr, size_t nops); //设置信号量的值 
   (1)semid: semget 返回的 semid 
   (2)opsptr: 指向信号量操作结构数组 
   (3)nops : opsptr 所指向的数组中的 sembuf 结构体的个数 
    struct sembuf { 
           short sem_num; // 要操作的信号量在信号量集里的编号, 
           short sem_op; // 信号量操作
          short sem_flg; // 操作表示符 
                  }; 
   (4) sem_op 是正数,其值就加到 semval ,即释放信号量控制的资源 
       sem_op 0,那么调用者希望等到 semval 变为 0,如果 semval 0 就返回; 
       sem_op 是负数,那么调用者希望等待 semval 变为大于或等于 sem_op 的绝对 值
   (5) sem_flg 
      SEM_UNDO 由进程自动释放信号量 
      IPC_NOWAIT 不阻塞
    int semctl(int semid, int semum, int cmd, ../* union semun arg */);//信号集实行控制操作 
     semid 是信号量集合; 
     semnum 是信号在集合中的序号; 
     semum 是一个必须由用户自定义的结构体,在这里我们务必弄清楚该结构体的组成: 
     union semun 
     { 
       int val; // cmd == SETVAL 
       struct semid_ds *buf // cmd == IPC_SET 或者 cmd == IPC_STAT 
       ushort *array; // cmd == SETALL,cmd = GETALL 
     }; 
      val只有 cmd ==SETVAL 时才有用,此时指定的 semval = arg.val 
      array 指向一个数组,cmd==SETALL ,就根据 arg.array 来将信号量集的所 有值都赋值;cmd ==GETALL ,就将信号量集的所有值返回到 arg.array 指定的数 组中
      buf 指针只在 cmd==IPC_STAT IPC_SET 时有用,作用是 semid 所指向的信号量 集
4.2.2.3、相关应用
    #include  <sys/types.h> 
    #include  <sys/ipc.h> 
    #include  <sys/sem.h> 
    #include  <stdio.h> 
    static int nsems; 
    static int semflg; 
    static int semid; 
    int  errno=0; 

       union semun { 
             int val; 
             struct semid_ds *buf; 
             unsigned short *array; 
               }arg; 
       int main() 
          { 
            struct sembuf sops[2]; //要用到两个信号量,所以要定义两个操作数组 
            int rslt; 
            unsigned short argarray[80]; 
            arg.array = argarray; 
            semid = semget(IPC_PRIVATE, 2, 0666); 
            if(semid < 0 ) 
           { 
            printf("semget failed. errno: %d\n", errno); 
            exit(0); 
           } 
         //获取 0th 信号量的原始值 
          rslt = semctl(semid, 0, GETVAL); 
          printf("val = %d\n",rslt); 
         //初始化 0th 信号量,然后再读取,检查初始化有没有成功 
         arg.val = 1; // 同一时间只允许一个占有者 
         semctl(semid, 0, SETVAL, arg); 
         rslt = semctl(semid, 0, GETVAL); 
         printf("val = %d\n",rslt); 
         sops[0].sem_num = 0; 
         sops[0].sem_op = -1; 
         sops[0].sem_flg = 0; 
         sops[1].sem_num = 1; 
         sops[1].sem_op = 1; 
         sops[1].sem_flg = 0; 
         rslt=semop(semid, sops, 1); //申请 0th 信号量,尝试锁定 
         if(rslt < 0 ) 
         { 
         printf("semop failed. errno: %d\n", errno); 
         exit(0); 
         } 
          //可以在这里对资源进行锁定 
         sops[0].sem_op = 1; 
         semop(semid, sops, 1); //释放 0th 信号量 
         rslt = semctl(semid, 0, GETVAL); 
         printf("val = %d\n",rslt); 
         rslt=semctl(semid, 0, GETALL, arg); 
        if (rslt < 0) 
        { 
        printf("semctl failed. errno: %d\n", errno); 
        exit(0); 
        } 
   printf("val1:%d val2: %d\n",(unsigned int)argarray[0],(unsigned int)argarray[1]); 
        if(semctl(semid, 1, IPC_RMID) == -1) 
       { 
          Perror(“semctl failure while clearing reason”); 
       } 
        return(0); 
       } 






    

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值