实验四、使用信号量进行互斥与同步

一、实验目的

本实验介绍在 Linux 中使用信号量进行进程同步、互斥的方法。读者可以通过实验进一步理解进程间同步与互斥、临界区与临界资源的概念与含义,并学会 Linux 信号量的基本使用方法。

二、实验环境

  • 硬件环境:计算机一台,局域网环境;
  • 软件环境:Linux Ubuntu 操作系统,gcc 编译器;

三、实验内容和步骤

  1. 参考:

    • System V:
      Unix众多版本中的一支,最初由AT&T定义,目前为第四个版本,其中定义了较为复杂的API。
    • POSIX:
      Portable Operating System Interface,IEEE为了统一Unix接口而定义的标准,定义了统一的API接。Linux即支持System API,又支持POSIX API。
  2. 信号量:
    Linux提供两种信号量:
    * 内核信号量:用于内核中资源共享控制
    * 用户态信号量:主要包括POSIX信号量和SYSTEM V信号量。

    其中 POSIX 信号量分为两类:
    * 无名信号量:主要用于线程间同步,也可用于进程间(由fork产生)同步。
    * 有名信号量:既可用于进程间同步,也可用于线程间同步。

    POSIX有名信号量主要包括:

    1. sem_t* sem_open(const char *name, int oflag, mode_t mode, int value):

      • name: 文件名路径,如mysem,会创建/dev/shm/sem.mysem
      • oflag:O_CREATEO_CREATE | O_EXCL
      • O_CREATE:如信号量存在,则打开之,如不存在,则创建
      • O_CREATE | O_EXCL:如信号量已存在,则返回 error
      • mode:信号量访问权限,如 0666
      • value:信号量初始化值
    2. int sem_wait(sem_t *sem):

      • 测试指定的信号量的值,相当于P操作
      • 若 sem > 0,则减1立刻返回
      • 若 sem = 0,则睡眠直到 sem > 0,此时立刻减1,然后返回
      • int sem_post(sem_t *sem)
      • 释放资源,相当于V操作
      • 信号量 sem 的值加1
      • 唤醒正在等待该信号量的进程/线程
    3. int sem_close(sem_t *sem):

      • 关闭有名信号量
      • 进程中,如果使用完信号量,应使用该函数关闭有名信号量
      • int sem_unlink(const char *name)
      • 删除系统中的信号量
        如果有任何进程/线程引用这个信号量,sem_unlink函数不会起到任何作用,即只有最后一个使用该信号量的进程来执行sem_unlink才有效。
  3. 实验步骤

    1. 通过实例查看不使用互斥时的情况:
      创建一个 C 程序,4-1.cpp,代码如下:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      #include "stdio.h"
      #include "stdlib.h"
      #include "unistd.h"
      int main(int argc,char *argv[]) {
      char message = 'x';
      if(argc > 1){
      message = argv[1][0];
      }
      for(int i=0; i<10; i++){
      printf ("%c", message);
      fflush(stdout);
      sleep(rand()%3);
      printf ("%c", message);
      fflush(stdout);
      sleep(rand()%2);
      }
      sleep(10);
      exit(0);
      }

      编译链接为 a 文件,同时运行两个进程,命令如下:

      1
      2
      gcc 4-1.cpp -o a
      ./a & ./a o

      观察 x 和 o 的出现规律,并分析原因:
      在未使用信号量控制互相排斥的程序中,x、o 的出现是无规律,两个进程只是并发进行,随机出现x、o。

    2. 使用信号量来对临界资源进行互斥:
      创建一个 C 程序,4-2.cpp,代码如下:

      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
      #include "stdio.h"
      #include "stdlib.h"
      #include "sys/types.h"
      #include "sys/ipc.h"
      #include "semaphore.h"
      #include "fcntl.h"
      #include "sys/stat.h"
      #include "unistd.h"

      int main(int argc, char *argv[]) {
      char message = 'x';
      if(argc >1) {
      message = argv[1][0];
      }
      sem_t *mutex = sem_open("mysem", O_CREAT, 0666, 1);
      for(int i = 0; i < 10; i++) {
      sem_wait(mutex);
      printf("%c", message);
      fflush(stdout);
      sleep(rand()%3);
      printf("%c", message);
      fflush(stdout);
      sem_post(mutex);
      sleep(rand()%2);
      }
      sleep(10);
      sem_close(mutex);
      sem_unlink("mysem");
      exit(0);
      }

      编译链接为 a 文件,同时运行两个进程,命令如下:

      1
      2
      gcc 4-2.cpp -o a
      ./a & ./a o

      观察 X 和 O 的出现规律,并分析原因:
      X 和 O 呈现偶数个交替出现。该程序使用信号量 mutex 进行进程间的互斥操作。因为只有在当前进程执行完两次printf();之后,才会释放信号量 mutex ,所以只会输出偶数个 x 或者 o ,交替输出。进程互斥,使用信号量之后,必然会有进程先访问 mutex ,执行、释放之后才能再次执行下一个进程。

    3. 使用信号量来模拟下象棋红黑轮流走子的情况
      编写两个 C 语言程序red_chess.cpp以及black_chess.cpp,分别模拟下象棋过程中红方走子和黑方走子过程。走子规则:红先黑后,红、黑双方轮流走子,到第10步,红方胜,黑方输。

      编程思路:
      设置一下两个同步信号量:
      * hei:初值为1,代表黑方已经走子,轮到红方走子(满足棋规“红先黑后”)。
      * hong: 初值为0,代表红方尚未走子。

      红棋走子之前,先测试信号量 hei ,判断黑方是否已经走子,如是,则轮到红方走子,否则阻塞等待黑子走子,由于 hei 的初值为1,因此一定是红方先走。红方走子完毕后,置信号量 hong = 1,通知黑方走子。

      黑方走子之前,先测试信号量 hong ,判断红方是否已经走子,如是,则轮到黑方走子,否则阻塞等待红方走子,由于 hong 的初值为0,因此在红方没有走子之前,黑方不会走子。黑方走子完毕后,置信号量hei = 1,通知红方走子。

      red_chess.cpp

      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
      #include "stdio.h"
      #include "stdlib.h"
      #include "sys/types.h"
      #include "sys/ipc.h"
      #include "semaphore.h"
      #include "fcntl.h"
      #include "sys/stat.h"
      #include "unistd.h"

      int main() {
      sem_t *hei = sem_open("chess_black_sem", O_CREAT, 0666, 1);
      sem_t *hong = sem_open("chess_red_sem", O_CREAT, 0666, 0);

      for(int i = 0; i < 10; i++) {
      sem_wait(hei);
      if(i != 9) {
      printf("Red chess had moved, black chess go!\n");
      } else {
      printf("Red chess win!!\n");
      }
      fflush(stdout);
      sem_post(hong);
      }
      sleep(10);
      sem_close(hei);
      sem_close(hong);
      sem_unlink("chess_red_sem");
      sem_unlink("chess_black_sem");
      exit(0);
      }

      black_chess.cpp

      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
      #include "stdio.h"
      #include "stdlib.h"
      #include "sys/types.h"
      #include "sys/ipc.h"
      #include "sys/stat.h"
      #include "semaphore.h"
      #include "fcntl.h"
      #include "unistd.h"

      int main() {
      sem_t *hei = sem_open("chess_black_sem", O_CREAT, 0666, 1);
      sem_t *hong = sem_open("chess_red_sem", O_CREAT, 0666, 0);
      for(int i = 0; i < 10; i++) {
      sem_wait(hong);
      if(i != 9) {
      printf("Black chess had moved, red chess go!\n");
      } else {
      printf("Black chess lost!!\n");
      }
      fflush(stdout);
      sem_post(hei);
      }
      sleep(10);
      sem_close(hei);
      sem_close(hong);
      sem_unlink("chess_red_sem");
      sem_unlink("chess_black_sem");
      exit(0);
      }

      编译链接为 red black 文件,同时运行两个进程,命令如下:

      1
      2
      3
      gcc red_chess.cpp -o red
      gcc black_chess.cpp -o black
      ./red & ./black

四、实验总结

对程序结果进行分析,并结合操作系统课程中讲授的原理,总结信号量在进程同步和互斥中所起的作用。

思考:

  1. 对信号量的操作如果没有成对出现,会导致什么现象发生?
    缺少 P 操作就不能访问临界资源,缺少 V 操作就不能释放临界资源,如果临界资源不够用,下一个等待临界资源的进程就一直得不到执行。

  2. 用于实现同步、互斥的信号量再出现的位置上各有什么特点?
    同一进程的 P,V 操作应成对出现,实现互斥的信号量成对的P原语在 V 原语之前,实现同步的信号量成对的 P 原语在 V 原语之后。

  3. 如果改成“黑先红后” ,红、黑双方轮流走子,到第20步,黑方胜,红方输,如何编程实现?

    red_chess.cpp

    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
    #include "stdio.h"
    #include "stdlib.h"
    #include "sys/types.h"
    #include "sys/ipc.h"
    #include "semaphore.h"
    #include "fcntl.h"
    #include "sys/stat.h"
    #include "unistd.h"

    int main() {
    sem_t *hei = sem_open("chess_black_sem", O_CREAT, 0666, 1);
    sem_t *hong = sem_open("chess_red_sem", O_CREAT, 0666, 0);

    for(int i = 0; i < 20; i++) {
    sem_wait(hei);
    if(i != 19) {
    printf("Red chess had moved, black chess go!\n");
    } else {
    printf("Red chess lost!\n");
    }
    fflush(stdout);
    sem_post(hong);
    }
    sleep(20);
    sem_close(hei);
    sem_close(hong);
    sem_unlink("chess_red_sem");
    sem_unlink("chess_black_sem");
    exit(0);
    }

    black_chess.cpp

    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
    #include "stdio.h"
    #include "stdlib.h"
    #include "sys/types.h"
    #include "sys/ipc.h"
    #include "sys/stat.h"
    #include "semaphore.h"
    #include "fcntl.h"
    #include "unistd.h"

    int main() {
    sem_t *hei = sem_open("chess_black_sem", O_CREAT, 0666, 1);
    sem_t *hong = sem_open("chess_red_sem", O_CREAT, 0666, 0);
    for(int i = 0; i < 20; i++) {
    sem_wait(hong);
    if(i != ) {
    printf("Black chess had moved, red chess go!\n");
    } else {
    printf("Black chess win!!\n");
    }
    fflush(stdout);
    sem_post(hei);
    }
    sleep(20);
    sem_close(hei);
    sem_close(hong);
    sem_unlink("chess_red_sem");
    sem_unlink("chess_black_sem");
    exit(0);
    }

    编译链接为 red black 文件,同时运行两个进程,命令如下:

    1
    2
    3
    gcc red_chess.cpp -o red
    gcc black_chess.cpp -o black
    ./red & ./black

实验四、使用信号量进行互斥与同步
https://flowerdown.org/posts/20230526-222618.html
作者
Unrealfeather
发布于
2023年5月26日
许可协议