长生栈 长生栈
首页
  • 编程语言

    • C语言
    • C++
    • Java
    • Python
  • 数据结构和算法

    • 全排列算法实现
    • 动态规划算法
  • CMake
  • gitlab 安装和配置
  • docker快速搭建wordpress
  • electron+react开发和部署
  • Electron-创建你的应用程序
  • ImgUI编译环境
  • 搭建图集网站
  • 使用PlantUml画时序图
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

Living Team

编程技术分享
首页
  • 编程语言

    • C语言
    • C++
    • Java
    • Python
  • 数据结构和算法

    • 全排列算法实现
    • 动态规划算法
  • CMake
  • gitlab 安装和配置
  • docker快速搭建wordpress
  • electron+react开发和部署
  • Electron-创建你的应用程序
  • ImgUI编译环境
  • 搭建图集网站
  • 使用PlantUml画时序图
  • 友情链接
关于
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 文件操作

  • Linux进程和线程

  • Linux信号

  • 进程间通信

    • 无名管道
    • 有名管道
    • 信号量
      • 信号量简介
      • PV操作
      • 信号量处理函数
        • semget函数
        • semop函数
        • semctl函数
      • 信号量使用案例
        • 示例代码
        • 输出
        • 分析
    • 共享内存
    • 消息队列
  • Socket

  • C语言
  • 进程间通信
DC Wang
2023-02-22
目录

信号量

# 信号量

# 信号量简介

为了防止出现多个程序同时访问一个共享资源而引发的问题,需要采取一种机制,使得在任一时刻,只有一个进程可以访问到临界区域,即信号量机制。

  • 信号量分类
    • 二进制信号量(二元信号量):取值为0、1
    • 通用信号量:取值可为0或任意正整数。

# PV操作

  • P操作:

    • 如果信号量的值大于0,就给它减1;
    • 如果信号量的值等于0,就挂起该进程的执行。
  • V操作:

    • 如果有其他进程因等待信号量而被挂起,就让它恢复运行;
    • 如果没有进程因等待信号量而挂起,就给它加1。

对普通变量进行加减是不行的,因为在c/c++等语言中,没有一个原子操作可以完成变量检测和加减的功能。

# 信号量处理函数

以下几个函数用于处理信号量操作

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int num_sems, int sem_flags);
int semop(int sem_id, struct sembuf* sem_ops, size_t num_sem_ops);
int semctl(int sem_id, int sem_num, int command, ...);
1
2
3
4
5
6

# semget函数

该函数的作用是创建一个新信号量,或取得一个已有信号量。

int semget(key_t key, int num_sems, int sem_flags);
1
  • key:整数值。不相关的进程可以通过它来访问同一个信号量。

    程序对信号量的访问都是间接的,它先提供一个键,再由系统生成一个信号量标识符,其他进程都通过semget函数返回信号量标识符。

  • num_sems:整数值。需要的信号量数目,一般总是1。

  • sem_flags:一组标志,代表该信号量的权限。IPC_CREAT表示创建一个新的信号量(即使对应键已经创建了信号量,也不会错误),可以和权限位按位或。

# semop函数

该函数的作用是改变信号量的值

int semop(int sem_id, struct sem_buf* sem_ops, size_t num_sem_ops);
1
  • sem_id:由semget返回的信号量标识符。

  • sem_ops:指向一个结构数组的指针,该结构描述了信号量操作情况

    /* Structure used for argument to `semop' to describe operations.  */
    struct sembuf
    {
      unsigned short int sem_num;	/* 信号量编号,如果不使用一组信号量,则取值一般为0 */
      short int sem_op;		        /* 一次操作中希望改变的值,可以为-1,即P操作,也可以为1,即V操作 */
      short int sem_flg;	        /* 通常设为SEM_UNDO,指示操作系统将跟踪当前进程对这个信号量的修改情况,
                                       如果这个进程没有释放信号量即终止,那么操作系统将自动释放该进程持有的信号量。*/
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
  • num_sem_ops:上述结构数组中元素的个数。

# semctl函数

该函数允许直接控制信号量信息。

int semctl(int sem_id, int sem_num, int command, ...);
1
  • 如果存在第四个参数,将会是一个union semun结构。这个需要程序员自己定义。

    union semun 
    {
        int val; //信号量初始值
        struct semid_ds *buf;        
        unsigned short int *array;  
        struct seminfo *__buf;      
    };
    
    1
    2
    3
    4
    5
    6
    7

# 信号量使用案例

用两个不同字符的输出代表进入和离开缓冲区。

如果程序启动时带有一个参数,它将在进入和退出临界区域时打印字符X,而程序的其他运行实例将在进入和退出临界区域时打印字符O。

因为在任意给定时刻,只有一个进程能进入临界区域,因此字符X和O都应成对出现。

# 示例代码

// sem.c
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
union semun 
{
    int val;                     
    struct semid_ds *buf;        
    unsigned short int *array;  
    struct seminfo *__buf;      
};
static int set_semvalue(void);
static void del_semvalue(void);
static int semaphore_p(void);
static int semaphore_v(void);
static int sem_id;

int main(int argc, char *argv[])
{
    int i;
    int pause_time;
    char op_char = 'O';
    srand((unsigned int)getpid());  
    sem_id = semget((key_t)1234, 1, 0666 | IPC_CREAT);
    if (argc > 1) 
    {
        if (!set_semvalue()) 
        {
            fprintf(stderr, "Failed to initialize semaphore\n");
            exit(EXIT_FAILURE);
        }
        op_char = 'X';
        sleep(5);
    }
    for(i = 0; i < 10; i++) 
    {        
        if (!semaphore_p()) 
            exit(EXIT_FAILURE);
        printf("%c", op_char);
        fflush(stdout);
        pause_time = rand() % 3;
        sleep(pause_time);
        printf("%c", op_char);
        fflush(stdout);
        if (!semaphore_v()) 
            exit(EXIT_FAILURE);
        pause_time = rand() % 2;
        sleep(pause_time);
    }   
    printf("\n%d - finished\n", getpid());
    if (argc > 1) 
    {    
        sleep(10);
        del_semvalue();
    }    
    exit(EXIT_SUCCESS);
}
static int set_semvalue(void)
{
    union semun sem_union;
    sem_union.val = 1;
    if (semctl(sem_id, 0, SETVAL, sem_union) == -1) 
        return(0);
    return(1);
}
static void del_semvalue(void)
{
    union semun sem_union;  
    if (semctl(sem_id, 0, IPC_RMID, sem_union) == -1)
        fprintf(stderr, "Failed to delete semaphore\n");
}
static int semaphore_p(void)
{
    struct sembuf sem_b;   
    sem_b.sem_num = 0;
    sem_b.sem_op = -1; /* P() */
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1) 
    {
        fprintf(stderr, "semaphore_p failed\n");
        return(0);
    }
    return(1);
}
static int semaphore_v(void)
{
    struct sembuf sem_b;
    
    sem_b.sem_num = 0;
    sem_b.sem_op = 1; /* V() */
    sem_b.sem_flg = SEM_UNDO;
    if (semop(sem_id, &sem_b, 1) == -1) 
    {
        fprintf(stderr, "semaphore_v failed\n");
        return(0);
    }
    return(1);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101

# 输出

$ ./sem a &
[1] 3705
$ ./sem
OOXXXXOOXXOOOOOOOOOOOOOOOOXXX
3706 - finished
$ XXXXXXXXXX#没有输入,这是后台进程的输出。
3705 - finished
1
2
3
4
5
6
7

# 分析

O和X都是成对出现的,说明在临界区访问时是独占的。P操作和V操作之间是临界区。

  • set_semvalue() 函数将信号量设置为 1。
  • del_semvalue() 函数删除信号量。
  • semaphore_p() 函数执行 P 操作。
  • semaphore_v() 函数执行 V 操作。
编辑 (opens new window)
#Linux#进程间通信#IPC
上次更新: 2023/03/31, 22:34:04
有名管道
共享内存

← 有名管道 共享内存→

最近更新
01
ESP32-网络摄像头方案
06-14
02
ESP32-PWM驱动SG90舵机
06-14
03
ESP32-实时操作系统freertos
06-14
更多文章>
Theme by Vdoing | Copyright © 2019-2025 DC Wang All right reserved | 辽公网安备 21021102001125号 | 吉ICP备20001966号-2
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式