ZUCC_操作系统原理实验_实验八 进程通信——信号量

操作系统原理实验报告

课程名称操作系统原理实验
实验项目名称实验八 进程通信——信号量

实验目的

  1. 了解 Linux 系统的进程间通信机构 (IPC)
  2. 理解 Linux 关于信号量的概念;
  3. 掌握 Linux 支持 system V 信号量的系统调用;
  4. 巩固进程同步概念。

实验内容

  1. 在多个进程通过共享内存进行通信时,使用信号量进行同步控制,
  2. 使用系统调用:semget ( ),semop ( ),semctl ( ).

实验步骤

一、基本概念

System V 信号量是一个或多个信号量的集合,它对应的是一个信号量结构体,这个结构体是为 System V IPc 服务的,信号量只不过是它的一部分,常用于进程间同步。

二、有关 System V 信号量的系统调用

System V 信号量需使用下列头文件:

  • #include <sys/types.h>
  • #include <sys/ipc.h>
  • #include <sys/sem.h>

1、semget 函数原型:得到一个信号量集标识符或创建一个信号量集对象

2、semop 函数原型:完成对信号量的 P 操作或 V 操作

3、semctl 函数原型:得到一个信号量集标识符或创建一个信号量集对象

三、例程,使用信号量进行进程同步和互斥控制

例程1:两个进程并发执行时,交错输出内容。

观察进程并发执行结果,理解输出内容交替的原因

image-20220507190923957

这里并行执行了两个 s1 兄弟进程,一个默认不带参数打印 .,另外一个带参数打印 0,并利用管道写入文件 s1.out。这里可以看到进程树中两个进程虽然进程号相邻但并无父子关系。对于每一个进程,进入循环后连续20次输出字符,其中每次输出完后随机睡眠几秒钟,然后再次循环。最后睡眠3秒后输出进程死亡信息并结束进程。

因此,我们可以看到,这里利用 & 达成了并行执行两个进程的效果,因此输出的结果中 .0 随机分布,但由于相同的字符间最多间隔3秒的延迟,因此也会有交替的效果。

例程2:修改例程 1,使用 System V 信号量进行互斥控制

编译链接通过后,多次运行例程,观察进程并发执行结果,并思考下述问题:

image-20220509232043853

1、和例程1比较,观察输出的变化,体会互斥信号量的作用;

现在输出的结果在中间的部分 .0 两个两个相继出现。

因此现在的进程中利用 System V 信号量实现了互斥控制,即对于输出这个临界区一次只能有一个进程在打印结果,同时由于 System V 信号量包裹了进程的一个循环,且一个循环中分别打印两个字符,因此可以看到在中间的部分 .0 两个两个相继出现。

2、比较 System V 信号量和POSIX 信号量的异同。

image-20220509232548089

使用上的区别

  1. System V的信号量是信号量集,可以包括多个信号灯(有个数组),每个操作可以同时操作多个信号灯。Posix是单个信号灯
  2. Posix信号量在有些平台并没有被实现,比如:SUSE8而System V信号量大多数LINUX/UNIX都已经实现。两者都可以用于进程和线程间通信。
  3. 一般来说,System V信号量用于 进程间同步、Posix 有名信号灯既可用于线程间的同步,又可以用于进程间的同步、Posix无名信号灯用于同一个进程的不同线程间,如果无名信号量要用于进程间同步,信号量要放在共享内存中。
  4. Posix有两种类型的信号量,有名信号量和无名信号量。有名信号量像System V信号量一样由一个名字标识。
  5. Posix通过sem_open单一的调用就完成了信号量的创建、初始化和权限的设置,而System V要两步。也就是说Posix信号是多线程,多进程安全的,而System V不是,可能会出现问题。
  6. SystemV信号量通过一个int类型的值来标识自己(类似于调用open()返回的fd),而sem_open函数返回sem_t类型(长整形)作为Posix信号量的标识值。
  7. 对于System V信号量你可以控制每次自增或是自减的信号量计数,而在Posix里面,信号量计数每次只能自增或是自减1。
  8. Posix无名信号量提供一种非常驻的信号量机制。

https://blog.csdn.net/luren2015/article/details/107739149

例程三,实现用 Systerm V信号量对两个(或多个)通过共享内存传递信息的并发进程进行同步控制。

说明:运行时需先启动reader,因为使用的信号量、共享的存都是在 reader 中申请的。然后再启动 writer,参数为 reader 申请的共享内存的标识符。

writer可以通过菜单退出:reader 可在writer 退出后,按 Ctl+C 退出,并在退出时自动删除最初申请信号量。

启动 reader 进程后,查看 ipc,可以看到新建的共享内存以及信号量数组

image-20220509234737050

输入共享内存并启动 writer 进程后,进行进程通讯,查看 ipc

image-20220509234638016

image-20220509234707386

启动第二个writer 进程后,进行进程通讯,查看 ipcimage-20220509235009828image-20220509235034304启动第二个 reader 进程,进行进程通讯,查看 ipc

image-20220509235533181

注意,这里新建的 reader 进程会新建一个共享内存,因此之前 writer 进程发送的数据不会被收到。可以通过修改 myshmget 函数内的参数 IPC_PRIVATE 达到预期效果。

直接退出一个 reader 进程,control+c 退出另外一个进程,查看 ipc

image-20220510000206173

1、理解共享内存和信号量相关的系统调用;

这是一个单缓冲区的生产者-消费者问题。改程序首先执行 reader 部分,在此部分完成信号量的创建(一个互斥信号量为1,一个同步信号量为0)、共享内存的创建并映射到虚拟空间、并准备销毁进程之前销毁共享内存,完成上述任务后陷入循环,等待其他进程发送信息,且在收到信息后的输出阶段利用 locksemunlocksem 函数完成对临界区的互斥控制。

对于 writer 部分,启动时链接输入的共享内存的 shmid,然后声明缓冲区并陷入循环等待键入。当输入1时,将键盘缓冲区数据写入共享内存并发送信号给 reader,然后等待 reader 完成后继续执行;当输入2时,退出进程。

值得注意的是,这个程序里利用 mysemgetmysemctl 包装了 semgetsemctl 操作,并用 locksemunlocksem 对其在进行包装,最后展示为P、V操作。

2、用ipcs 观察和理解共享内存和信号量信息;

当启动一个 reader 进程后,自动生成一个共享内存和信号量组;

再启动任意 writer 进程后,只是共享内存的链接量增加了,表明了 writer 进程链接到了reader 进程;

当再启动第二个 reader 进程后,自动生成一个新的共享内存和新的信号量组;

正常退出(control+c)reader 进程后,共享内存和信号量组会注销;直接关闭reader 进程后,共享内存和信号量组仍然存在;

3、尝试运行多个 writer,能否正确同步?请分析代码解释你的判断

可以。多个 writer 进程链接到同一个 reader 进程后,改变的只是共享内存的链接量。因为在这里临界区缓存大小为1,CPU控制一次只有一个进程可以在临界区内读、写,同时因为 writer 进程调用的 write 方法内也利用P、V操作使得发送成为互斥操作,无法被其他 writer 进程打断,因此多个 writer 进程不影响 reader 进程的接收。

四、编程题

修改共享内存实验中的例程3,将忙等待改成用信号量对读写进程进行同步控制。

pro

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
/* The union for semctl may or may not be defined for us.This code,defined
 in linux's semctl() manpage,is the proper way to attain it if necessary */
#if defined (__GNU_LIBRARY__)&& !defined (_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun{
    int val; /* value for SETVAL */
    struct semid_ds *buf; /* buffer for IPC_STAT,IPC_SET */
    unsigned short int *array; /* array for GETALL,SETALL */
    struct seminfo *__buf; /* buffer for IPC_INFO */
};
#endif
#define SHMDATASIZE 1000
#define BUFFERSIZE (SHMDATASIZE - sizeof(int))
#define SN_READ 0
#define SN_WRITE 1
int Semid = 0; /* 用于最后删除这个信号量 */
void writer(int shmid);
void delete(void);
void sigdelete(int signum);
void locksem(int semid,int semnum);
void unlocksem(int semid,int semnum);
void write(int shmid,int semid,char *buffer);
int mysemget(key_t key,int nsems,int semflg);
int mysemctl(int semid,int semnum,int cmd,union semun arg);
int mysemop(int semid,struct sembuf *sops,unsigned nsops);
int myshmget(key_t key,int size,int shmflg);
void *myshmat(int shmid,const void *shmaddr,int shmflg);

int main(int argc,char *argv[]){
    int shmid;
    scanf("%d", &shmid);
    writer(shmid);
}
void writer(int shmid){
    int semid;
    void *shmdata;
    char *buffer;

    /* 将该共享内存映射到进程的虚存空间 */
    shmdata = myshmat(shmid,0,0);

    semid = *(int *)shmdata;
    buffer = shmdata + sizeof(int);

    while(1){
    	sleep(3);
        printf("waiting for client...\n");
        fflush(stdout);
        write(shmid,semid,buffer);
    }
}
void delete(void){
    printf("\n quit; delete the semaphore %d \n",Semid);

    /* 删除信号量 */
    if(semctl(Semid,0,IPC_RMID,0) == -1){
        printf("Error releasing semaphore.\n");
    }
}
void sigdelete(int signum){
    /* Calling exit will conveniently trigger the normal delete item. */
    exit(0);
}
void locksem(int semid,int semnum){
    struct sembuf sb;

    sb.sem_num = semnum;
    sb.sem_op = -1;
    sb.sem_flg = SEM_UNDO;

    mysemop(semid,&sb,1);
}
void unlocksem(int semid,int semnum){
    struct sembuf sb;

    sb.sem_num = semnum;
    sb.sem_op = 1;
    sb.sem_flg = SEM_UNDO;

    mysemop(semid,&sb,1);
}
void waitzero(int semid,int semnum){
    struct sembuf sb;

    sb.sem_num = semnum;
    sb.sem_op = 0;
    sb.sem_flg = 0; /* No modification so no need to undo */
    mysemop(semid,&sb,1);
}
void write(int shmid,int semid,char *buffer){
    locksem(semid,SN_READ);
    printf("Enter some text: ");
    fflush(stdout);
    fgets(buffer,BUFFERSIZE,stdin);
    unlocksem(semid,SN_WRITE);
}

int mysemget(key_t key,int nsems,int semflg){
    int retval;

    retval = semget(key,nsems,semflg);
    if(retval == -1){
        printf("semget key %d,nsems %d failed: %s ",key,nsems,strerror(errno));
        exit(255);
    }
    return retval;
}
int mysemctl(int semid,int semnum,int cmd,union semun arg){
    int retval;

    retval = semctl(semid,semnum,cmd,arg);
    if(retval == -1){
        printf("semctl semid %d,semnum %d,cmd %d failed: %s",semid,semnum,cmd,strerror(errno));
        exit(255);
    }
    return retval;
}

int mysemop(int semid,struct sembuf *sops,unsigned nsops){
    int retval;

    retval = semop(semid,sops,nsops);
    if(retval == -1){
        printf("semop semid %d (%d operations) failed: %s",semid,nsops,strerror(errno));
        exit(255);
    }
    return retval;
}
int myshmget(key_t key,int size,int shmflg){
    int retval;

    retval = shmget(key,size,shmflg);
    if(retval == -1){
        printf("shmget key %d,size %d failed: %s",key,size,strerror(errno));
        exit(255);
    }
    return retval;
}
void *myshmat(int shmid,const void *shmaddr,int shmflg){
    void *retval;

    retval = shmat(shmid,shmaddr,shmflg);
    if(retval == (void*) -1){
        printf("shmat shmid %d failed: %s",shmid,strerror(errno));
        exit(255);
    }
    return retval;
}
int myshmctl(int shmid,int cmd,struct shmid_ds *buf){
    int retval;

    retval = shmctl(shmid,cmd,buf);
    if(retval == -1){
        printf("shmctl shmid %d,cmd %d failed: %s",shmid,cmd,strerror(errno));
        exit(255);
    }
    return retval;
}

con

/**** reader_writer1.c 一个利用共享内存进行进程通信的程序 ****/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
/* The union for semctl may or may not be defined for us.This code,defined
 in linux's semctl() manpage,is the proper way to attain it if necessary */
#if defined (__GNU_LIBRARY__)&& !defined (_SEM_SEMUN_UNDEFINED)
/* union semun is defined by including <sys/sem.h> */
#else
/* according to X/OPEN we have to define it ourselves */
union semun{
    int val; /* value for SETVAL */
    struct semid_ds *buf; /* buffer for IPC_STAT,IPC_SET */
    unsigned short int *array; /* array for GETALL,SETALL */
    struct seminfo *__buf; /* buffer for IPC_INFO */
};
#endif
#define SHMDATASIZE 1000
#define BUFFERSIZE (SHMDATASIZE - sizeof(int))
#define SN_READ 0
#define SN_WRITE 1
int Semid = 0; /* 用于最后删除这个信号量 */
void reader(void);
void delete(void);
void sigdelete(int signum);
void locksem(int semid,int semnum);
void unlocksem(int semid,int semnum);
int mysemget(key_t key,int nsems,int semflg);
int mysemctl(int semid,int semnum,int cmd,union semun arg);
int mysemop(int semid,struct sembuf *sops,unsigned nsops);
int myshmget(key_t key,int size,int shmflg);
void *myshmat(int shmid,const void *shmaddr,int shmflg);

int main(int argc,char *argv[]){
    reader();
}
void reader(void){
    union semun sunion;
    int semid,shmid;
    void *shmdata;
    char *buffer;

    /* 首先:我们要创建信号量 */
    semid = mysemget(IPC_PRIVATE,2,SHM_R|SHM_W);
    Semid = semid;

    /* 在进程离开时,删除信号量 */
    atexit(&delete);
    signal(SIGINT,&sigdelete);

    /* 信号量 SN_READ 初始化为 1(锁定),SN_WRITE 初始化为 0(未锁定)*/
    sunion.val = 1;
    mysemctl(semid,SN_READ,SETVAL,sunion);

    sunion.val = 0;
    mysemctl(semid,SN_WRITE,SETVAL,sunion);

    /* 现在创建一块共享内存 */
    shmid = myshmget(IPC_PRIVATE,SHMDATASIZE,IPC_CREAT|SHM_R|SHM_W);
    printf("shmid:%d\n", shmid);
    /* 将该共享内存映射到进程的虚存空间 */
    shmdata = shmat(shmid,0,0);

    /* 将该共享内存标志为已销毁的,这样在使用完毕后,将被自动销毁*/
    shmctl(shmid,IPC_RMID,NULL);

    /* 将信号量的标识符写入共享内存,以通知其它的进程 */

    *(int *)shmdata = semid;

    buffer = shmdata + sizeof(int);

    while(1){
        locksem(semid,SN_WRITE);
        printf("You wrote: %s\n",buffer);
        unlocksem(semid,SN_READ);
    }
}
void delete(void){
    printf("\n quit; delete the semaphore %d \n",Semid);

    /* 删除信号量 */
    if(semctl(Semid,0,IPC_RMID,0) == -1){
        printf("Error releasing semaphore.\n");
    }
}
void sigdelete(int signum){
    /* Calling exit will conveniently trigger the normal delete item. */
    exit(0);
}
void locksem(int semid,int semnum){
    struct sembuf sb;

    sb.sem_num = semnum;
    sb.sem_op = -1;
    sb.sem_flg = SEM_UNDO;

    mysemop(semid,&sb,1);
}
void unlocksem(int semid,int semnum){
    struct sembuf sb;

    sb.sem_num = semnum;
    sb.sem_op = 1;
    sb.sem_flg = SEM_UNDO;

    mysemop(semid,&sb,1);
}
void waitzero(int semid,int semnum){
    struct sembuf sb;

    sb.sem_num = semnum;
    sb.sem_op = 0;
    sb.sem_flg = 0; /* No modification so no need to undo */
    mysemop(semid,&sb,1);
}
void write(int shmid,int semid,char *buffer){
    printf("\n wait for reader to read in information ...");
    fflush(stdout);

    locksem(semid,SN_READ);
    printf("finish \n");
    printf("please input information: ");
    fgets(buffer,BUFFERSIZE,stdin);
    unlocksem(semid,SN_WRITE);
}

int mysemget(key_t key,int nsems,int semflg){
    int retval;

    retval = semget(key,nsems,semflg);
    if(retval == -1){
        printf("semget key %d,nsems %d failed: %s ",key,nsems,strerror(errno));
        exit(255);
    }
    return retval;
}
int mysemctl(int semid,int semnum,int cmd,union semun arg){
    int retval;

    retval = semctl(semid,semnum,cmd,arg);
    if(retval == -1){
        printf("semctl semid %d,semnum %d,cmd %d failed: %s",semid,semnum,cmd,strerror(errno));
        exit(255);
    }
    return retval;
}

int mysemop(int semid,struct sembuf *sops,unsigned nsops){
    int retval;

    retval = semop(semid,sops,nsops);
    if(retval == -1){
        printf("semop semid %d (%d operations) failed: %s",semid,nsops,strerror(errno));
        exit(255);
    }
    return retval;
}
int myshmget(key_t key,int size,int shmflg){
    int retval;

    retval = shmget(key,size,shmflg);
    if(retval == -1){
        printf("shmget key %d,size %d failed: %s",key,size,strerror(errno));
        exit(255);
    }
    return retval;
}
void *myshmat(int shmid,const void *shmaddr,int shmflg){
    void *retval;

    retval = shmat(shmid,shmaddr,shmflg);
    if(retval == (void*) -1){
        printf("shmat shmid %d failed: %s",shmid,strerror(errno));
        exit(255);
    }
    return retval;
}
int myshmctl(int shmid,int cmd,struct shmid_ds *buf){
    int retval;

    retval = shmctl(shmid,cmd,buf);
    if(retval == -1){
        printf("shmctl shmid %d,cmd %d failed: %s",shmid,cmd,strerror(errno));
        exit(255);
    }
    return retval;
}

image-20220510015803754


版权声明:本文为OwemShu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。