呼延冰枫 发表于 2025-6-10 11:46:15

信号量

信号量

一、信号量的概念

Linux系统提供了一种名称叫做信号量(Semaphore)的IPC资源,信号量的英文也可以翻译为信号灯,信号量本质上其实是一个数字(非负整数),用来表示一种资源的数量,当多个进程或者线程争夺这些稀缺资源的时候,可以使用信号量来保证他们合理地、秩序地使用这些资源。
Linux系统中一般把这些可能被进程或者线程共同访问资源称为临界资源(Critical Resources),而程序中访问这些临界资源的代码段被称为临界区(Critical Zone)。
把申请信号量的操作称为P(Passeren)操作,把归还信号量的操作称为V(vrijgeven)操作。
(1) P操作
P操作指的是请求分配资源,也就是说每完成一次P操作,则信号量资源会减一,当信号量资源为0时,如果有进程或者线程请求分配资源,则会导致阻塞,直到有进程释放信号量。
(2) V操作
V操作指的是请求释放资源,也就是说每完成一次V操作,则信号量资源会加一,使用完资源之后应该及时释放,否则其他进程申请资源时如果资源不够,则会导致阻塞。
注意:P操作和V操作属于原子操作,也就是进程在进行P操作或者V操作的过程中不会被打断,也就是不管资源申请成功还是申请失败都是一次性完成。
二、创建信号量集

对信号量进行申请和释放的前提是必须创建信号量,Linux系统中提供了一个名称叫做semget()的函数,利用该函数可以创建或者打开信号量。
(1)函数参数
第一个参数指的是IPC对象的键值key,用户可以调用ftok()函数由系统生成一个key并把生成的key作为参数。
第二个参数指的是信号量集中的信号量元素数量,因为系统中的公共资源可能有多种,为了防止进程间对这些资源进行抢占,就需要为每一种资源设置一个信号量,所以信号量元素的数量可以理解为公共资源的种类。
第二个参数指的是创建或者打开信号量集的标志,用户可以选择使用标志IPC_CREAT或者IPC_EXCL,另外还可以指定信号量集的权限,权限采用八进制,比如0666,和open函数类似。
(2)返回值
函数调用成功,则会返回信号量集的标识符(一个非负整数),函数调用失败,则会返回-1。
三、操作信号量集

创建好一个信号量集之后,用户就可以对信号量集中的信号量资源进行PV操作,Linux系统提供了一个名字叫做semop()的函数,用户通过该函数可以实现对信号量进行申请或者释放。
(1) 函数参数
第一个参数指的是要操作的信号量集的标识符ID,该标识符可以通过semget()函数的返回值得到。
第二个参数指的是一个指向struct sembuf的结构体指针,结构体中有三个成员,如下所示:
struct sembuf

{
        unsignedshortsem_num;
        shortsem_op;
        shortsem_flg;
};sem_num成员指的是信号量集中的信号量元素的下标,通过可以访问信号量集中的某个信号量,就相当于可以访问某种公共资源。
sem_op成员指的是对选中的信号量的操作,常见的操作就是P/V操作,
​        ①如果该成员是正整数,则表示释放信号量,相当于完成V操作。
​        ②如果该成员是负整数则表示申请信号量,相当于完成P操作。
​        ③如果该成员的值为0时,就称为等零操作,即阻塞等待直到对应的信号量元素的值为零。
sem_flg成员指的是对选中的信号量的操作标志,常见的标志有IPC_NOWAIT和SEM_UNDO。
第三个参数指的是要完成P/V操作的结构体数组的元素个数,利用该参数可以指定要操作多少个信号量。
注意:进行P操作时,如果信号量的值小于要申请的数量,则会导致进程阻塞,进行V操作时永远不会阻塞。
(2) 返回结果
函数调用成功,则返回0,函数调用失败,则返回-1,并会设置对应的错误码,用户可以根据错误码检查错误。
四、控制信号量集

Linux系统内核需要对创建成功的信号量集进行维护,所以会记录每个信号量集的属性和信息,Linux系统提供了一个名称叫做semctl()的函数接口,用户可以调用该函数接口来实现设置信号量集的属性、获取信号量集的属性、删除信号量集等操作。
五、练习

设计一个程序,作为进程A,进程A专门创建一个信号量集,要求信号量集中有1个信号量,对信号量集合中的信号量进行设置,要求集合中的信号量的初值为1,然后再设计2个程序,分别是进程B和进程C,要求进程B和进程C使用进程A创建的信号量集合中的信号量实现互斥访问。
进程A
/***************************************************
*
*file name:        semA.c
*        author       :zzlyx1239.126.com
*        date       :2025/04/3
*        brief    :进程A,用于对共享内存中的数据进行修改
*         note       :None
*
*CopyRight(c) 2025 zzlyx1239.126.com All Reserved
***************************************************/

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>

struct sembuf
{
        unsignedshortsem_num;//信号量集中的信号量元素的下标
        shortsem_op;//对选中的信号量的操作,常见的操作就是P/V操作
        shortsem_flg; //成员指的是对选中的信号量的操作标志
};

// 定义信号量联合体
union semun {
    int            val;    /* Value for SETVAL */
    struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
    unsigned short*array;/* Array for GETALL, SETALL */
    struct seminfo*__buf;
};


int main()
{
        //0.打开信号量集,如果不存在则创建,如果存在则打开
        int sem_id = semget(ftok(".",20),1,IPC_CREAT|IPC_EXCL|0644);
        if (-1 == sem_id)
        {
                fprintf(stderr, "semget error,errno:%d,%s\n",errno,strerror(errno));
                sem_id = semget(ftok(".",20),1,0644);
        }
    //1.打开共享内存段,如果不存在则创建,如果存在则打开
        int shm_id = shmget(ftok(".",10),256,IPC_CREAT|IPC_EXCL|0644);
        if (-1 == shm_id)
        {
                fprintf(stderr, "shmget error,errno:%d,%s\n",errno,strerror(errno));
                shm_id = shmget(ftok(".",10),256,0644);
        }
    //2.把共享内存段连接到进程空间shmat()默认是可读可写默认会初始化为0
        int *pshm = (int *)shmat(shm_id,NULL,0);
        if (pshm == (void *)-1)
        {
                fprintf(stderr, "shmat error,errno:%d,%s\n",errno,strerror(errno));
                return -1;
        }
        *pshm=0;
        union semun semn;    //定义一个信号量联合体 变量
    semn.val = 1;       //将信号量联合体中的val的值赋值为1
    semctl(sem_id,0,SETVAL,semn);      //通过semctl函数接口将信号量的值设为1

        while(1);
        return 0;
}进程B
/***************************************************
*
*file name:        semB.c
*        author       :zzlyx1239.126.com
*        date       :2025/04/3
*        brief    :进程B,用于对共享内存中的数据进行修改
*         note       :None
*
*CopyRight(c) 2025 zzlyx1239.126.com All Reserved
***************************************************/

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdbool.h>

struct sembuf
{
        unsignedshortsem_num;//信号量集中的信号量元素的下标
        shortsem_op;//对选中的信号量的操作,常见的操作就是P/V操作
        shortsem_flg; //成员指的是对选中的信号量的操作标志
};
struct sembuf sem={0,1,0};
int shm_id;
int sem_id;
char *pshm;
/*********************************************************
*
*function name:p
*         brief               :申请访问权限
*         author               :zzlyx1239.126.com
*         parameter       :
*         version               :
*         note               :       
*
*        CopyRight(c) 2025 zzlyx1239.126.com All Reserved
***********************************************************/
bool p(int sem_id)
{
        sem.sem_op=-1;
        int a=semop(sem_id,&sem,0);
        if(a=-1)
        {
                fprintf(stderr, "semop error,errno:%d,%s\n",errno,strerror(errno));
                return false;
        }
        return true;
}

/*********************************************************
*
*function name:v
*         brief               :V操作
*         author               :zzlyx1239.126.com
*         parameter       :
*         version               :
*         note               :       
*
*        CopyRight(c) 2025 zzlyx1239.126.com All Reserved
***********************************************************/
bool v(int sem_id)
{
        sem.sem_op=1;
        int a=semop(sem_id,&sem,0);
        if(a=-1)
        {
                fprintf(stderr, "semop error,errno:%d,%s\n",errno,strerror(errno));
                return false;
        }
        return true;
}


int main()
{
        //0.打开信号量集,如果不存在则创建,如果存在则打开
        sem_id = semget(ftok(".",20),1,IPC_CREAT|IPC_EXCL|0644);
        if (-1 == sem_id)
        {
                fprintf(stderr, "semget error,errno:%d,%s\n",errno,strerror(errno));
                sem_id = semget(ftok(".",20),1,0644);
        }
    //1.打开共享内存段,如果不存在则创建,如果存在则打开
        shm_id = shmget(ftok(".",10),256,IPC_CREAT|IPC_EXCL|0644);
        if (-1 == shm_id)
        {
                fprintf(stderr, "shmget error,errno:%d,%s\n",errno,strerror(errno));
                shm_id = shmget(ftok(".",10),256,0644);
        }
    //2.把共享内存段连接到进程空间shmat()默认是可读可写默认会初始化为0
        pshm = (char *)shmat(shm_id,NULL,0);
        if (pshm == (void *)-1)
        {
                fprintf(stderr, "shmat error,errno:%d,%s\n",errno,strerror(errno));
                return -1;
        }
        //申请访问共享内存
        while(1)
        {
                //P操作,申请权限
                p(sem_id);

                //向共享内存中写入数据
                *pshm+=1;

                //V操作,释放
                v(sem_id);
        }
        shmdt(pshm);
    return 0;
}进程C
/***************************************************
*
*file name:        semCC.c
*        author       :zzlyx1239.126.com
*        date       :2025/04/3
*        brief    :进程B,用于对共享内存中的数据进行修改
*         note       :None
*
*CopyRight(c) 2025 zzlyx1239.126.com All Reserved
***************************************************/

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <sys/ipc.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <stdbool.h>

struct sembuf
{
        unsignedshortsem_num;//信号量集中的信号量元素的下标
        shortsem_op;//对选中的信号量的操作,常见的操作就是P/V操作
        shortsem_flg; //成员指的是对选中的信号量的操作标志
};
struct sembuf sem={0,1,0};
int shm_id;
int sem_id;
char *pshm;
/*********************************************************
*
*function name:p
*         brief               :申请访问权限
*         author               :zzlyx1239.126.com
*         parameter       :
*         version               :
*         note               :       
*
*        CopyRight(c) 2025 zzlyx1239.126.com All Reserved
***********************************************************/
bool p(int sem_id)
{
   sem.sem_op=-1;
   int a=semop(sem_id,&sem,0);
   if(a=-1)
   {
           fprintf(stderr, "semop error,errno:%d,%s\n",errno,strerror(errno));
           return false;
   }
   return true;
}

/*********************************************************
*
*function name:v
*         brief               :V操作
*         author               :zzlyx1239.126.com
*         parameter       :
*         version               :
*         note               :       
*
*        CopyRight(c) 2025 zzlyx1239.126.com All Reserved
***********************************************************/
bool v(int sem_id)
{
   sem.sem_op=1;
   int a=semop(sem_id,&sem,0);
   if(a=-1)
   {
           fprintf(stderr, "semop error,errno:%d,%s\n",errno,strerror(errno));
           return false;
   }
   return true;
}

int main()
{
        //0.打开信号量集,如果不存在则创建,如果存在则打开
        sem_id = semget(ftok(".",20),1,IPC_CREAT|IPC_EXCL|0644);
        if (-1 == sem_id)
        {
                fprintf(stderr, "semget error,errno:%d,%s\n",errno,strerror(errno));
                sem_id = semget(ftok(".",20),1,0644);
        }
    //1.打开共享内存段,如果不存在则创建,如果存在则打开
        shm_id = shmget(ftok(".",10),256,IPC_CREAT|IPC_EXCL|0644);
        if (-1 == shm_id)
        {
                fprintf(stderr, "shmget error,errno:%d,%s\n",errno,strerror(errno));
                shm_id = shmget(ftok(".",10),256,0644);
        }
    //2.把共享内存段连接到进程空间shmat()默认是可读可写默认会初始化为0
        pshm = (char *)shmat(shm_id,NULL,0);
        if (pshm == (void *)-1)
        {
                fprintf(stderr, "shmat error,errno:%d,%s\n",errno,strerror(errno));
                return -1;
        }
        //申请访问共享内存
        while(1)
        {
                //P操作,申请权限
                p(sem_id);

                //打印共享内存中的数据
                printf("share memory data=%d\n",*pshm);

                //V操作,释放
                v(sem_id);
        }
        shmdt(pshm);
    return 0;
}
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: 信号量