• Linux Day13 ---信号量


    一、信号量

    1.1 一些概念

    用来管理对资源的访问

    一个特殊的变量,只允许对它进行等待(wait)和发送信号(signal),代表可用资源个数,

    取0,1 二值信号量

    取 3,5 计数信号量

    p操作:原子减一,代表获取资源,可能阻塞

    v操作:原子加一 代表释放资源,不会阻塞

    临界区:真正执行数据更新的代码需要独占式地执行(即临界资源所在位置)

    临界资源:只有一个进程可以进入这个临界代码并拥有对资源独占式的访问权(同一时刻只能执行一个代码)

    1.2 信号量的机制

    #include

    1.2.1 semget函数

    作用:创建一个新信号量或取得一个已有信号量的键;

    定义:int semget(key_t key,int num_sems,int sem_flags);

    key:整数值,不相关的进程可以通过它访问同一个信号量。

    num_sems:参数指定需要的信号量数目,他几乎总是取值为1.

    sem_flag:类似于文件的访问权限,一般用IPC_CREAT:创建一个新信号量,即使给出的键是一个已有信号量的键,也不会产生错误。一般还要用到IPC_EXCL:确保创建出一个新的唯一的信号量,如果信号量已存在,将返回错误。

    这个函数在成功时返回一个整数,即其他信号量函数将用到的信号量标识符。

    如果失败返回-1。

    1.2.2 semop函数

    作用:semop()对信号量进行改变,做 P 操作或者 V 操作

    定义:int semop(int semid, struct sembuf *sops, unsigned nsops);
    sem_id:是由semget返回的信号量标识符
    sem_ops是指向一个结构数组的指针,一下是成员:
     struct sembuf
     {
     unsigned short sem_num; //指定信号量集中的信号量下标 ,一般取0
    short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
     short sem_flg; //SEM_UNDO 
    };
    semop()成功返回 0,失败返回-1
    1.2.3 semctl函数
    作用: semctl()控制信号量
    定义: int semctl(int semid, int semnum, int cmd, ...)
    sem_id:是由semget返回的信号量标识符
    semnum:是信号量编号,当需要用到成组的信号量时,用这个参数,一般取值为0,表示唯一一个信号量
    cmd 选项: SETVAL IPC_RMID
    union semun
    {
     int val;
    struct semid_ds *buf;
     unsigned short *array;
     struct seminfo *_buf;
     };
    一般取值:SETVAL:用来把信号量初始化一个已知的值
    IPC_RMID:用来删除一个无需继续使用的信号量标识符
     semctl()对于SETVAL和IPC_RMID成功返回 0,失败返回-1

    1.2.3 实现上述函数

    头文件 sem.h

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. union semun{
    8. int val;
    9. };
    10. void sem_init();
    11. void sem_p();
    12. void sem_v();
    13. void sem_destroy();
    函数封装
    1. #include "sem.h"
    2. static int semid=-1;
    3. void sem_init()
    4. {
    5. semid=semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//创建一个新的信号量
    6. if(semid==-1){
    7. semid=semget((key_t)1234,1,0600);//表明这个信号量是已存在的信号量
    8. if(semid==-1)
    9. {
    10. printf("semget err\n");//这个信号量创建失败
    11. }
    12. }
    13. else{
    14. union semun a;
    15. a.val=1;
    16. if(semctl(semid,0,SETVAL,a)==-1)//初始化该信号量
    17. {
    18. printf("semctl err\n");
    19. }
    20. }
    21. }
    22. void sem_p()
    23. {
    24. struct sembuf sem;
    25. sem.sem_flg=SEM_UNDO;
    26. sem.sem_num=0;
    27. sem.sem_op=-1;//p操作
    28. if(semop(semid,&sem,1)==-1){
    29. printf("sem_p err\n");
    30. }
    31. }
    32. void sem_v()
    33. {
    34. struct sembuf sem;
    35. sem.sem_flg=SEM_UNDO;
    36. sem.sem_num=0;
    37. sem.sem_op=1;//v操作
    38. if(semop(semid,&sem,1)==-1){
    39. printf("sem_v err\n");
    40. }
    41. }
    42. void sem_destroy()
    43. {
    44. if(semctl(semid,0,IPC_RMID)==-1)//删除该信号量
    45. {
    46. printf("sem_destory err\n");
    47. }
    48. }

    1.2.4 举个栗子

    进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符‘a’表示开始使用打印
    机,输出第二个字符‘a’表示结束使用,b 进程操作与 a 进程相同。(由于打印机同一时刻
    只能被一个进程使用,所以输出结果不应该出现 abab)
    step 1:不引入信号量
    会发现a和b有时候会出现争抢资源的情况,即(a打印一次未结束就开始打印b)
    step 2:引入信号量

    定义一个信号量初始化值为1 ,当a进程拿到这个信号量,就进行p操作,让这个信号量为0,此时b进程没有可以使用的信号量就会等待a进程结束,a进程结束后会v操作,让信号量为1 ,此时b进程就可以执行,依次循环下去。

    a.c

    1. #include"sem.h"
    2. int main()
    3. {
    4. sem_init();//
    5. for(int i = 0; i < 5; i++)
    6. {
    7. //p
    8. sem_p();
    9. printf("a");
    10. fflush(stdout);
    11. int n = rand() % 3;
    12. sleep(n);
    13. printf("a");
    14. fflush(stdout);
    15. sem_v();
    16. n = rand() % 3;
    17. sleep(n);
    18. }
    19. sem_destroy();
    20. return 0;
    21. }

    b,c

    1. #include"sem.h"
    2. int main()
    3. {
    4. sem_init();//
    5. for(int i = 0; i < 5; i++)
    6. {
    7. //p
    8. sem_p();
    9. printf("b");
    10. fflush(stdout);
    11. int n = rand() % 3;
    12. sleep(n);
    13. printf("b");
    14. fflush(stdout);
    15. sem_v();
    16. n = rand() % 3;
    17. sleep(n);
    18. }
    19. return 0;
    20. }
    step3:结果

    此时a和b各是成对出现

    二、共享内存

    2.1 一些概念

    共享内存为多个进程之间共享和传递数据提供了一种有效的方式。共享内存是先在物理
    内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访
    问共享内存中的地址,就好像它们是由 malloc 分配的一样。如果某个进程向共享内存写入了
    数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供
    同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。
    两个进程在进行通信,共用同一个物理内存,逻辑上不同,物理上共存。
    一些区别
    与无名管道相比:共享内存必须写一次读一次,管道可以多次写入

    2.2 共享内存的机制

      #include
      #include
    #include

    2.2.1 shmget()

     shmget()用于创建或者获取共享内存
    int shmget(key_t key, size_t size, int shmflg);
     key: 不同的进程使用相同的 key 值可以获取到同一个共享内存,即共享内存的标识符
     size: 创建共享内存时,指定要申请的共享内存空间大小 ,以字节为单位指定需要的共享的内存容量
     shmflg: IPC_CREAT:新的共享内存段, IPC_EXCL
     

     shmget()成功返回共享内存的 ID, 失败返回-1

    2.2.2 shmat()

     shmat()将申请的共享内存的物理内存映射到当前进程的虚拟地址空间上
     shmat()成功返回返回共享内存的首地址,失败返回 NULL
     shmaddr:一般给 NULL,由系统自动选择映射的虚拟地址空间
     shmflg: 一般给 0, 可以给 SHM_RDONLY 为只读模式,其他的为读写
     void* shmat(int shmid, const void *shmaddr, int shmflg);
     

    2.2.3 shmdt()

     shmdt()断开当前进程的 shmaddr 指向的共享内存映射
     shmdt()成功返回 0, 失败返回-1
    注意:将共享内存分离并未删除它,只是使该共享内存对当前进程不再可用。
     int shmdt(const void *shmaddr);

    2.2.4 shmctl()

     shmctl()控制共享内存
     shmctl()成功返回 0,失败返回-1
     cmd: IPC_RMID
     int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    2.3 举个栗子

    进程 a 从键盘循环获取数据并拷贝到共享内存中,进程 b 从共享内存中获
    取并打印数据。要求进程 a 输入一次,进程 b 输出一次,进程 a 不输入,进程 b 也不输出。
    step 1:不加信号量

    这时候无法控制读取端的速度,这个速度很快,上述图片是因为我使用了睡眠函数,睡眠了一段时间。
    step 2:使用信号量

    step 3:代码实现

    main.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "sem.h"
    7. int main()
    8. {
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    10. if ( shmid == -1 )
    11. {
    12. printf("shmget err\n");
    13. exit(1);
    14. }
    15. char* s = (char*)shmat(shmid,NULL,0);
    16. if ( s == (char*)-1)
    17. {
    18. printf("shmat err\n");
    19. exit(1);
    20. }
    21. sem_init();
    22. while( 1 )
    23. {
    24. printf("input\n");
    25. char buff[128] = {0};
    26. fgets(buff,128,stdin);
    27. sem_p(SEM1);
    28. strcpy(s,buff);
    29. sem_v(SEM2);
    30. if ( strncmp(buff,"end",3) == 0)
    31. {
    32. break;
    33. }
    34. }
    35. shmdt(s);
    36. }

    test.c

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "sem.h"
    7. int main()
    8. {
    9. int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
    10. if ( shmid == -1 )
    11. {
    12. printf("shmget err\n");
    13. exit(1);
    14. }
    15. char * s = (char*)shmat(shmid,NULL,0);
    16. if ( s == (char*)-1)
    17. {
    18. printf("shmat err\n");
    19. exit(1);
    20. }
    21. sem_init();
    22. while( 1 )
    23. {
    24. sem_p(SEM2);
    25. if ( strncmp(s,"end",3) == 0 )
    26. {
    27. break;
    28. }
    29. printf("read:%s\n",s);
    30. sem_v(SEM1);
    31. }
    32. shmdt(s);
    33. shmctl(shmid,IPC_RMID,NULL);
    34. sem_destroy();
    35. }
    结果
  • 相关阅读:
    个人商城系统开源(注册)
    物联网的应用——工业自动化
    音频录制和处理软件 Audio Hijack mac中文版说明
    Linux下快速搭建YApi接口管理平台
    华为OD机试 - 玩牌高手 - 动态规划(Java 2023 B卷 100分)
    292.Nim游戏 | 877.石子游戏 | 319.灯泡开关
    CSS-列表属性篇
    随便写一写
    excel数据分析常用50个函数
    【图解 HTTP】简单的HTTP协议
  • 原文地址:https://blog.csdn.net/hello_world_juns/article/details/132847001