Linux信号量操作实例

5 阅读6分钟

要求:使用信号量实现两个进程轮流写入data.txt文件末尾,写入间隔为1秒,每个进程写入10次,内容随意

基本概念

  • 信号量是一种计数器,用来控制对多个进程共享的资源所进行的访问。常常被用做一个锁机制,在某个进程正在对特定的资源进行操作时,信号量可以防止另一个进程去访问它。生产者和消费者的模型是信号量的典型使用
  • 信号量分为两种:system

代码编写思路

  • 根据不同的项目需求选择不同的信号量(二进制,计数型......),以当前题目为例,该需求只有两个进程,因此适合使用二进制信号量(0/1)来实现两个进程的交替进行。
步骤一:创建信号量
  • 使用semget()系统调用创建两个信号量,初始化一个为1(允许执行)另一个为0(等待状态)
    函数: int semget(key_t key,int nsems,int semflg)
    参数:
    1. key 是使用 ftok 创建的键值
    2. nsems 是创建信号量的数量,在本例中创建的数量为 2
    3. semflg 是打开信号量的方式,有两种方式:IPC_CREAT/IPC_EXCL

步骤二:进程同步逻辑
  • 进程1:等待自己的信号量(值为1时继续执行),执行写入操作,然后将另一个信号量设置为1
  • 进程2同理
  • 主要操作包括:
    1.P操作:将信号量值减1,如果值小于0则阻塞进程
    2.V操作: 将信号量值加1,如果值大于等于0则唤醒等待进程

  • P、V操作是通过向已经建立好的信号量(semget()函数),发送命令来完成
步骤三:循环执行
  • 每个进程循环10次,每次执行完写入操作后使用sleep(1)实现1秒间隔
步骤四:清理资源
  • 程序结束时,使用semctl()sem_close()清理信号量资源

关键系统调用
#include <sys/sem.h> // System V信号量 
#include <semaphore.h> // POSIX信号量 
#include <sys/wait.h> //提供wait函数
#include <unistd.h> //包含Unix标准函数声明,如fork(),sleep()等

对于System V信号量:

  • semget(): 创建/获取信号量
  • semop(): 操作信号量(P/V操作)
  • semctl(): 控制信号量

对于POSIX信号量:

  • sem_open(): 创建或打开命名信号量
  • sem_wait(): 等待(P操作)
  • sem_post(): 发送信号(V操作)
  • sem_close(): 关闭信号量

4. 示例逻辑结构

#include <stdio.h>

#include <sys/types.h>  //提供key_t
#include <sys/ipc.h>    //包含进程间的通信(IPC)相关函数
#include <sys/sem.h>    //包含信号量相关函数
#include <unistd.h>     //包含Unix标准函数声明,如fork()
#include <stdlib.h>     //提供exit()函数
#include <sys/wait.h>   //提供wait()函数


// 信号量初始化
sem_t *sem1 = sem_open("/sem1", O_CREAT, 0666, 1);  // 进程1先执行
sem_t *sem2 = sem_open("/sem2", O_CREAT, 0666, 0);  // 进程2等待

// 进程1代码
for(int i = 0; i < 10; i++) {
    sem_wait(sem1);           // 等待信号量1
    // 写入文件操作
    sleep(1);                 // 间隔1秒
    sem_post(sem2);           // 唤醒进程2
}

// 进程2代码
for(int i = 0; i < 10; i++) {
    sem_wait(sem2);           // 等待信号量2
    // 写入文件操作
    sleep(1);                 // 间隔1秒
    sem_post(sem1);           // 唤醒进程1
}

5.代码编写


/*
* 信号量的P、V操作是通过向已经建立好的信号量(通过segmet函数创建的),发送命令来完成的
* P操作函数:对指定的信号量执行等待操作(减1)
* V操作函数:对指定的信号量执行发送操作(加1)
*/
void sem_P(int sem_id,int sem_num)
{
    struct sembuf sem_buf;       //定义sembuf结构体变量,用于描述信号量
    sem_buf.sem_num = sem_num;   //设置要操作的信号量在集合中的索引
    sem_buf.sem_op = -1;         //设置操作类型:-1表示P操作(等待),资源减1
    sem_buf.sem_flg = SEN_UNDO;  //设置操作标志:SEM_UNDO表示进程终止时内核会撤销此操作
    semop(sem_id,&sem_buf,1);    //调用semop()函数执行信号量操作,操作信号量
}

//V操作同理
void sem_V(int sem_id,int sem_num)
{
    struct sembuf sem_buf;
    sem_buf.sem_num = sem_num;
    sem_buf.sem_op  = 1;
    sem_buf.sem_flg = SEM_UNDO;
    semop(sem_id,&sem_buf,1);
}

//信号量数据结构,用于semctl函数的第四个参数
union semun
{
    int val;                  //整形变量,用于设置信号量的初始值
    struct semid_ds *buf;     //semid_ds结构体指针,用于IPC_STAT和IPC_SET命令
    unsigned short *array;    //数组类型,用于GETALL和SETALL
    struct seminfo *__buf;    //信号量内部结构,用于IPC_INFO命令
}


int sem_id;//全局变量,存储信号量集合的标识符

int main()
{
    int ret; //创建一个返回结果变量
    
    key_t key = ftok(".",'a');//创建一个键值
    if(key < 0)
    {
        perror("create key failed ");
        exit(1);        
    }
    
    
    sem_id = semget(key,2,IPC_CREAT,0666);//创建信号量
    if(sem_id < 0)
    {
        perror("create signal failed ");
        exit(2);
    }
    
    union semun sem_union;   //定义union semun联合体变量
    
    sem_union.val = 1;       //初始化第一个信号量(索引0)
    
    ret = semctl(sem_id,0,SETVAL,sem_union);//使用semctl()函数设置信号量的值
                                            //sem_id:信号量标识符
                                            //0:表示要操作的信号量的值
                                            //SETVAL:设置信号量
                                            //sem_union:联合体参数,包含了要设置的值                                          
    if(ret < 0)
    {
        perror("process 1 semctl failed ");
        exit(3);        
    }
    
    
        //信号量0操作与其一致
    sem_union.val = 0;       //初始化第二个信号量
    ret = semctl(sem_id,1,SETVAL,sem_union);
    if(ret<0)
    {
        perror("process 2 semctl failed ");
        exit(4);
    }
    
    pid_t pid = fork();//创建子进程
    
    if(pid == -1)//创建子进程
    {
        perror("create process/fork failed");
        exit(5);        
    }
    else if(pid == 0)//子进程(进程2)
    {
        FILE *fp = fopen("data.txt","a");//子进程打开data.txt文件,以追加模式写入
                                         //使用"a"模式,确保每次数据添加到结尾
        if(fp == NULL)
        {
            perror("process 2 open file failed ");
            exit(6);            
        }
        for()
        {
            sem_P(sem_id,1);   //调用sem_P函数,等待第二个信号量变为可用(等待进程1释放信号量)
            fprintf(fp,"进程2学号:23125042023\n");//写入数据
            fflush(fp);//刷新缓冲区,确保数据立即写入磁盘
            printf("process 2 write data success \n");
            sleep(1);//休眠1秒,实现写入间隔要求
            sem_V(sem_id,0);//执行V操作,释放第一个信号量,通知进程1可以执行        
        
        }
        fclose(fp); //关闭文件
        
        exit(0); //子进程正常退出
        
    }
    else //父进程(进程1)
    {
        //操作和子进程相同,主要信号量的变化
        FILE *fp = fopen("data.txt","a"); 
        if(fp == NULL)
        {
            perror("process 1 open file failed ");
            exit(7);
        }

        for(int i = 0; i < 10; i++)
        {
            sem_P(sem_id,0);
            fprintf(fp,"进程1学号:23125042023\n");
            fflush(fp);
            printf("process 1 write data success \n");
            sleep(1);
            sem_V(sem_id,1);
        }

        fclose(fp);

        wait(NULL);        
    }
    
    ret = semctl(sem_id,IPC_RMID);//删除信号量集合,释放系统资源
        if(ret < 0)
    {
        perror("delete signal failed ");
        exit(8);
    }
    
    
    return 0;
}