《Linux C编程实战》笔记:信号量

信号量在操作系统的书里一般都有介绍,这里就只写书上说的了。

信号量是一个计数器,常用于处理进程或线程的同步问题,特别是对临界资源访问的同步。临界资源可以简单地理解为在某一时刻只能由一个进程或线程进行操作的资源,这里的资源可以是一段代码、一个变量或某种硬件资源。信号量的值大于或等于0时表示可供并发进程使用的资源实体数;小于0时代表正在等待使用临界资源的进程数。

与消息队列类似,Linux内核也为每个信号量集维护了一个semid_ds数据结构示例。该结构定义在头文件linux/sem.h中。

cpp 复制代码
struct semid_ds {
	struct ipc_perm	sem_perm;		/* 对信号进行操作的许可权,和上一节的消息队列一样的 */
	__kernel_old_time_t sem_otime;		/* 对信号量进行操作的最后时间 */
	__kernel_old_time_t sem_ctime;		/* 对信号量进行修改的最后时间 */
	struct sem	*sem_base;		/* 指向第一个信号量 */
	struct sem_queue *sem_pending;		/* 等待处理的挂起操作 */
	struct sem_queue **sem_pending_last;	/* 最后一个正在挂起的操作 */
	struct sem_undo	*undo;			/* 撤销的请求 */
	unsigned short	sem_nsems;		/* 数组中的信号量个数 */
};

信号量的创建与使用

信号量集的创建与打开

信号量集和消息队列一样,创建了之后在进程间使用。Linux下使用semget创建或打开信号集,这个函数定义在头文件sys/sem.h中

cpp 复制代码
int semget(key_t key, int nsems, int semflg);
  • key:一个键值,用于唯一标识信号量集。通常可以使用 ftok() 函数来生成该键值。
  • nsems:指定要创建或访问的信号量集中的信号量数量。如果只是打开信号量集,取0即可
  • semflg:这个参数和创建消息队列里的msgflg使用方式一样,通过IPC_CREATE,IPC_EXCL等标志来标志操作方式,具体看上一篇文章

semget() 函数成功时返回一个非负整数,表示信号量集的标识符(或称为信号量集描述符),失败返回-1

示例代码1

下面函数演示创建一个信号量集并对其中所有信号量进行初始化

cpp 复制代码
int createsem(const char *pathname,int proj_id,int members,int init_val){
    key_t msgkey;
    int index,sid;
    union semun semopts;//这个结构体后面会讲

    if((msgkey=ftok(pathname,proj_id))==-1){
        perror("ftok error!\n");
        return -1;;
    }
    if((sid=semget(msgkey,members,IPC_CREAT|0666))==-1){
        perror("semget call failed.\n");
        return -1;
    }

    //后面是初始化操作
    semopts.val=init_val;
    for(index=0;index<members;index++){
        semctl(sid,index,SETVAL,semopts);
    }
    return sid;
}

信号量的操作

信号量的值与相应资源的使用情况有关。当它的值大于0时,表示当前可用资源的数量。当它的值小于0时,其绝对值表示等待使用该资源的进程个数。信号量的值仅能由PV操作来改变。在Linux下,PV操作通过调用semop实现。该函数定义在头文件sys/sem.h中

cpp 复制代码
int semop(int semid, struct sembuf *sops, size_t nsops);
  • semid:信号量集的标识符,通常是由 semget() 函数返回的值。
  • sops:一个指向结构数组的指针,每个结构体描述了对单个信号量的操作。sembuf 结构体包含以下字段:
    • sem_num:要操作的信号量在信号量集中的索引。
    • sem_op:操作类型,可以是正数表示增加信号量的值,负数表示减少信号量的值,0 表示等待信号量值变为 0。
    • sem_flg:标志位,用于指定操作的行为,比如操作的方式(阻塞或非阻塞)等。
  • nsops:操作的数量,即 sops 数组中元素的个数。
  • 成功返回0,失败返回-1

当然还是要看一下sembuf这个结构体具体的内容

cpp 复制代码
struct sembuf
{
  unsigned short int sem_num;	/* 信号在信号量集中的索引 */
  short int sem_op;		/* 操作类型 */
  short int sem_flg;		/* 操作标志 */
};

sem_flg可以设置为IPC_NOWAIT,则调用进程直接返回。如果没设置,semop会阻塞进程直到资源可用。

示例代码2

下面是对一个信号量集中的某个信号进行操作的P、V函数

cpp 复制代码
int sem_p(int semid,int index){//p是消耗资源
    struct sembuf buf={0,-1,IPC_NOWAIT};//所以第二个参数给的是负数,这里是-1
    if(index<0){
        perror("index of array cannot equal a minus value!\n");
        return -1;
    }
    buf.sem_num=index;
    if(semop(semid,&buf,1)==-1){//nsops是1,因为数组大小只有1
        perror("a wrong operation to semaphore ocurred!\n");
        return -1;
    }
    return 0;
}
cpp 复制代码
int sem_v(int semid,int index){
    struct sembuf buf={0,1,IPC_NOWAIT};//v是释放资源,所以sem_op是正数1
    if(index<0){
        perror("index of array cannot equal a minus value!\n");
        return -1;
    }
    buf.sem_num=index;
    if(semop(semid,&buf,1)==-1){
        perror("a wrong operation to semaphore ocurred!\n");
        return -1;
    }
    return 0;
}

信号量集的控制

使用信号量时,往往需要对信号量集进行一些控制操作。比如删除信号量集、对内核维护的信号量集的数据结构semid_ds进行设置,获取信号量集中信号值等。通过semtcl控制函数可以完成这些操作,该函数定义在sys/sem.h,如下图所示:

cpp 复制代码
int semctl(int semid, int semnum, int cmd, ...);
  • semid:信号量集的标识符。
  • semnum:信号量在集合中的索引。
  • cmd:要执行的控制操作。
  • ...:根据 cmd 参数指定的控制操作,可能需要附加参数。

最后的...说明函数的参数是可选的,它依赖于第三个参数cmd,它通过共用体变量semun选择要操作的参数,semun定义在linux/sem.h

cpp 复制代码
union semun {
	int val;			/* 设置某个信号的值等于val for SETVAL */
	struct semid_ds *buf;	/* 存取semid_ds for IPC_STAT & IPC_SET */
	unsigned short *array;	/*  for GETALL & SETALL */
	struct seminfo *__buf;	/* 为控制IPC_INFO 提供的缓存 */
	void *__pad;
};

第二个参数cmd,通过宏来只是操作类型

  1. IPC_STAT:获取信号量集的当前状态,包括信号量集中每个信号量的当前值等信息。

  2. IPC_SET:设置信号量集的状态,可以用于设置信号量集中每个信号量的值。

  3. IPC_RMID:从系统中删除信号量集,释放其占用的资源。

  4. GETALL:获取信号量集中所有信号量的当前值。

  5. GETNCNT:获取在等待信号量值增加的进程数量。

  6. GETPID:获取上次执行 semop 函数的进程的进程ID。

  7. GETVAL:获取特定信号量的当前值。

  8. GETZCNT:获取在等待信号量值变为 0 的进程数量。

  9. SETALL:设置信号量集中所有信号量的值。

  10. SETVAL:设置特定信号量的值。

示例代码3

下面是获取和设置单个新信号的函数

cpp 复制代码
int semval_op(int semid,int index,int cmd){
    if(index<0){
        printf("index cannot be minus!\n");
        return -1;
    }
    if(cmd==GETVAL||cmd==SETVAL)
    return semctl(semid,index,cmd,0);//0只有在SETVAL才有用,表示我们要把该信号量的值设置为0

    printf("function cannot support cmd:%d\n",cmd);
    return -1;
}

之前介绍的时候没有说setctl的返回值,因为也是根据cmd的不同而不同的。比如这个示例代码。如果cmd是GETVAL,那么函数的返回值就是对应信号量的值;如果cmd是SETVAL,那么函数的返回值就是用来标识操作是否成功,成功返回0,失败返回-1.

示例程序4

信号量一般用于处理访问临界资源的同步问题。下面也是两个程序,server和client。server创建一个信号量集,并对信号量循环减1,相当于分配资源。client执行时检查信号量,如果其值大于0表示有资源可用,继续执行,如果小于等于0代表资源已经分配完毕,进程client退出。

server:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <unistd.h> 
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;  // Buffer for IPC_INFO (Linux-specific)
};

#define MAX_RESOURCE 5
int main(int argc,char **argv,char **environ){
    key_t key;
    int semid;
    struct sembuf sbuf={0,-1,IPC_NOWAIT};//-1代表使用资源
    union semun semopts;
    if((key=ftok(".",'s'))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((semid=semget(key,1,IPC_CREAT|0666))==-1){//1表面信号量集只有一个信号量
        perror("semget error!\n");
        exit(1);
    }
    semopts.val=MAX_RESOURCE;
    if(semctl(semid,0,SETVAL,semopts)==-1){//把信号量集的那个信号量的值设置成MAX_RESOURCE
        perror("semctl error!\n");
        exit(1);
    }
    while (1)
    {
        if(semop(semid,&sbuf,1)==-1){//程序循环减少信号量的值,也就是隔3秒使用一个资源
            perror("semop error!\n");
            exit(1);
        }
        sleep(3);
    }
    exit(0);
}

编译这个文件的问题很多,按照书上所说的应该加上linux/sem.h这个头文件,因为union semun就是在这个头文件定义的,但是它和 <sys/sem.h>一起包含的话会出现很多的重复定义,最后只能不包含linux/sem.h,自己去定义union semun了。

而如果只用linux/sem.h的话像semctl这些函数又没有定义,程序也用不了。

所以感觉要么是书上有错误,要么是书太老了,很多东西都改了导致照书上敲有问题

client:

cpp 复制代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include <sys/types.h>
#include <sys/sem.h>
//把sys/ipc.h删了是因为我发现sem.h里就已经包含了它
#include <unistd.h> 
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;  // Buffer for IPC_INFO (Linux-specific)
};
int main(int argc,char **argv,char **environ){
    key_t key;
    int semid,semval;
    union semun semopts;
    if((key=ftok(".",'s'))==-1){
        perror("ftok error!\n");
        exit(1);
    }
    if((semid=semget(key,1,IPC_CREAT|0666))==-1){
        perror("semget error!\n");
        exit(1);
    }

    while (1)
    {
        if((semval=semctl(semid,0,GETVAL))==-1){//去获取信号量的值
            perror("semctl error!\n");
            exit(1);
        }
        if(semval>0){//大于0说明还有资源可用
            printf("Still %d resources can be used\n",semval);
        }
        else{
            printf("No more resources can be used!\n");
            break;
        }
        sleep(3);
    }
    exit(0);
}

运行结果:

稍微解释一下 ,先执行server,再执行client,资源不是从5开始应该是因为client不是第一时间执行,漏了两个。而server在信号量为0后就自动退出也是因为struct sembuf sbuf={0,-1,IPC_NOWAIT};设置了IPC_NOWAIT,这样在semop(semid,&sbuf,1)==-1这个判断中不会阻塞而是直接返回-1从而退出程序。

相关推荐
真真-真真几秒前
WebXR
linux·运维·服务器
轩辰~22 分钟前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
雨中rain1 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
oneouto1 小时前
selenium学习笔记(二)
笔记·学习·selenium
sealaugh321 小时前
aws(学习笔记第十九课) 使用ECS和Fargate进行容器开发
笔记·学习·aws
Bessssss2 小时前
centos日志管理,xiao整理
linux·运维·centos
s_yellowfish2 小时前
Linux服务器pm2 运行chatgpt-on-wechat,搭建微信群ai机器人
linux·服务器·chatgpt
豆是浪个2 小时前
Linux(Centos 7.6)yum源配置
linux·运维·centos
vvw&2 小时前
如何在 Ubuntu 22.04 上安装 Ansible 教程
linux·运维·服务器·ubuntu·开源·ansible·devops
我一定会有钱2 小时前
【linux】NFS实验
linux·服务器