实验5 用信号量实现进程互斥

实验5 利用信号量实现进程互斥
【实验目的】
(1) 理解互斥概念、信号量机制及信号量结构;
(2) 掌握信号量的使用方法;
(3) 掌握PV操作的定义;
(4) 掌握PV操作实现互斥的方法。
【实验原理/实验基础知识】
一、 互斥
多进程不能同时访问共享的临界资源的现象称为互斥。
二、 Linux信号量结构
每一个信号量集由一个sem_array结构来描述,该结构定义在文件/usr/src/linux-2.4/include/linux/sem.h中(若实验使用操作系统为ubuntu,则在以下路径中查找/usr/src/linux-header2.6.32-21/include/linux/sem.h)。
三、 有关信号量的系统调用函数

  1. 创建一个新的信号量集或获取一个已经存在的信号量集
    命令格式:int semget(key_t key, int nsems, int semflg)
    返回值:正确时返回信号量集的标识符号,错误时返回-1。
    参数说明:
  1. key:信号量集的key值。
    a) 使用IPC_PRIVATE,由系统产生key值并返回标识符,或者返回key值已存在的信号量集的标识符。
    b) 若key值不使用IPC_PRIVATE,而是由用户指定一个非0的整型数值,则对信号量集的打开或存取操作依赖于semflag参数的取值。
  2. nsems:指定打开或者新创建的信号量集将包括的信号量的数目。如果该key值的信号量集已存在,而semflg只指定IPC_CREAT标志,那么参数nsems必须与原来的值一致,否则也会返回错误信息。该参数最大值在linux/sem.h中定义如下。
    #define SEMMSL 250 /* <= 8000 */
  3. semflg:当key值不为IPC_PRIVATE时使用。
    a) 若只设置semflag的IPC_CREAT位,则创建一个信号量集,如果该信号量集已经存在,则返回其标识符号。
    b) 若设置semflag的IPC_CREAT|IPC_EXCL位,则创建一个新的信号量集,如果该key值的信号量集已经存在,则返回错误信息。
    c) 只设置IPC_EXCL位而不设置IPC_CREAT位没有任何意义。
  1. 对信号量的P、V操作
    命令格式:int semop(int semid, struct sembuf * sops, unsigned nsops);
    返回值:正确时返回0,错误时返回-1。
    参数说明:
    (1) semid:信号量集的标识符号,由semget()得到。
    (2) sops:指向一个sembuf结构数组,该数组的每个元素对应一次信号量操作。其sembuf数据结构如下。
    struct sembuf
    {
    unsigned short sem_num;
    short sem_op;
    short sem_flg;
    }
    其参数含义如下:
    sem_num:标明是信号量集的第几个元素,第一个信号量为0,第二个信号量为1,依次类推。
    sem_op:确定对信号量采取何种操作,可以为负数、正数、零。
  1. sem_op为负数则相当于P操作,从信号量的值中减去sem_op的绝对值:
     差值大于0:表示该进程可以使用临界资源进入临界区;
     差值小于0:在没有指定IPC_NOWAIT的情况下,该进程睡眠,并插入sem_queues等待队列尾部,直到请求的条件得到满足;如果指定了IPC_NOWAIT,则出错返回。
  2. sem_op为正数,此时相当于V操作,把它的值加到信号量中,意味着该进程释放资源。如果是互斥则出临界区,释放临界资源。
  3. sem_op为0,则该进程进入睡眠,直到信号量的值也为0。
    系统会按顺序检查信号量等待队列(sem_pending)中的每一个成员,查看在当前信号量的状态下,其请求的操作是否可以成功,如果可以,则将它从等待队列中唤醒,并插入就绪队列等待调度运行。
    sem_flg:指明操作的执行模式,它有两个标志位。一个是IPC_NOWAIT,指明以非阻塞方式操作信号量。另一个是SEM_UNDO,指明内核为信号量操作保留恢复值。
    (3) nsops:是第二个参数所指向的sembuf结构数组中元素的个数,如果只有一个信号量,则为1。
    实验中使用该系统调用实现P、V操作,使用格式为:
    struct sembuf P,V;
    semop(semid,&P,1);
    semop(semid,&V,1);
  1. 信号量集的控制函数
    命令格式:int semctl(int semid, int semnum, int cmd, union semun arg);
    返回值:正确时根据cmd的不同返回需要的值或0,错误时返回-1。
    参数说明:
    (1) semid:信号量集的标识符,由semget()得到。
    (2) semnum:指定semid信号量集的第几个信号量,在撤销信号量集时,此时参数可以默认。
    (3) cmd用于指定操作类别。其取值如下:
     GETVAL:返回semnum指定的信号量的semval域值。
     SETVAL:置信号量semval域值为arg.val。
     GETPID:返回semnum指定的信号量的sempid,即最近对该信号量进行操作的进程ID。
     GETNCNT:返回semncnt。
     GETZNCT:返回semzcnt。
     GETALL:返回所有信号量的值。
     SETALL:通过对arg.arrary更新所有信号量的值。
     IPC_STAT:获取信号量集的sem_array,存入arg.buf。
     IPC_SET:将arg.buf数据结构的sem_perm.uid、sem_perm.gid、sem_perm.mode成员赋给信号量的sem_array结构。
     IPC_RMID:删除指定信号量集。
     IPC_INFO:获取信号量集相关的信息,存放在arg.buf中。
    (4) arg为5种数据的共用体类型semun,该类型在include/linux/sem.h中定义如下:
    union semun
    {
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *_buf;
    void *_pad;
    };
  1. 该数据表示int数值时,用于GETVAL、SETVAL或GETPID等设置或获取整型数据的操作。
  2. 该数据指向semid_ds(即sem_array)结构数据时,用于IPC_STAT或IPC_SET等针对信号量集进行的操作。
  3. 该数据指向unsigned short数组时,用于GETALL或SETALL。
  4. 该数据指向seminfo结构时,用于IPC_INFO操作,所指向的seminfo结构在include/linux/sem.h中定义,其定义如下:
    struct seminfo
    {
    int semmap;
    int semmni;
    int semmns;
    int semmnu;
    int semmsl;
    int semopm;
    int semume;
    int semusz;
    int semvmx;
    int semaem;
    };
    上述系统调用使用系列头文件:
    #include<sys/types.h>
    #inclue<linux/sem.h>
    四、 信号量及其P、V操作的实现方式
  1. 定义信号量标识符号
    int semid;
  2. 定义信号量数据结构
  1. 定义P、V操作所用的数据结构
    struct sembuf P,V;
  2. 定义给信号量赋初值的参数数据结构
    union semun arg;
  1. 申请只有一个信号量的信号量集
    semid=semget(IPC_PRIVATE,1,IPC_0666);
    IPC_PRIVATE:可由系统产生key值;
    1:表示信号量集中只有一个信号量;
    IPC_0666:表示操作权限。0666表示任意用户可读可写,只设置semflag的IPC_CREAT位,则创建一个信号量集。
  2. 分别对每个信号量semid赋初值
    arg.val=初值;
    semctl(semid,0,SETVAL,arg);
    其中0表示第0个信号量,arg的值由arg.val决定,所以必须事先为arg.val赋值。
  3. 定义信号量P操作
    P.sem_num=0;
    P.sem_op=-1;
    P.sem_flg=SEM_UNDO;
  4. 定义信号量V操作
    V.sem_num=0;
    V.sem_op=1;
    V.sem_flg=SEM_UNDO;
  5. 对信号量semid执行P操作
    semop(mutexid,&P,1);
  6. 对信号量semid执行V操作
    semop(mutexid,&V,1);
  7. 撤销信号量
    semctl(semid,IPC_REID,0);
    信号量非普通变量,对它的赋值操作只能通过semctl(semid,0,SETVAL,arg)进行,其值的修改只能通过P、V操作,而不能使用普通的赋值语句。因此其初值和信号量的P、V操作需要事先定义好后才能在进程中执行。
    【实验环境】VMware Workstation、RedHat
    【实验步骤】
    一、父子进程以非互斥方式共享访问临界资源
  1. 创建父子进程
  2. 父子进程共享一个临界资源,每个进程循环进入该临界区3次。
  3. 父进程每次进入临界区后显示“parent in”,出临界区显示“parent out”。
    printf(“parent in\n”);
    sleep(1);
    printf(“parent out\n”);
  4. 子进程每次进入临界区后显示“child in”,出临界区显示“child out”。
    printf(“child in\n”);
    sleep(1);
    printf(“child out\n”);
  5. 观察执行结果。
    #include<stdio.h>标准输入输出函数库
    #include<stdlib.h>是C语言库头文件之一,包含了以下函数:
    1 字符串转换为数字的函数,包括atoi, atof, strtol等。
    2 随机数函数,包括srand, rand等。
    3 内存分配释放函数,如malloc,calloc,realloc,free等。
    4 程序运行控制函数,如exit, abort等。
    5 系统访问相关函数,如printenv, setenv,system等。
    6 常用算法函数,如qsort, bsearch, abs,div等。
    #include<unistd.h>不是c语言的东西,是linux/unix的系统调用,包含了许多U N I X系统服务的函数原型,例如 read,write和getpid函数。unistd.h在unix中类shu似于window中的windows.h!
    #include<sys/types.h>此头文件还包含适当时应使用的多个基本派生类型。尤其是以下类型更为重要:clock_t 表示系统时间(以时钟周期为单位)。  dev_t 用于设备号。  off_t 用于文件大小和偏移量。  ptrdiff_t 是一种带符号整型,用于对两个指针执行减法运算后所得的结果。  size_t 反映内存中对象的大小(以字节为单位)。  ssize_t 供返回字节计数或错误提示的函数使用。  time_t 以秒为单位计时。  所有这些类型在 ILP32 编译环境中保持为 32 位值,并会在 LP64 编译环境中增长为 64 位值。
    #include<sys/sem.h>是glibc提供的调用接口,#include <linux/sem.h>是系统提供的调用接口,本身不是同一个实现。

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<linux/sem.h>
int mutexid;
int main()
{
int chld,i,j;
while((chld=fork())-1);
if(chld>0)
{
i=1;
while(i<=3)
{
sleep(1);
printf(“parent in.\n”);
sleep(1);
printf(“parent out.\n”);
i++;
}
wait(0);
exit(0);
}
else
{
j=1;
while(j<=3)
{
sleep(1);
printf(“child in.\n”);
sleep(1);
printf(“child out.\n”);
j++;
}
exit(0);
}
}
在这里插入图片描述
在这里插入图片描述
二、PV操作实现进程互斥。以实验步骤一为基础,增加PV操作,实现父子进程互斥访问临界区。
(1) 定义与PV操作相关的数据结构;
(2) 定义信号量,给信号量赋值;
(3) 定义PV操作;
(4) PV操作实现进程互斥。
(5) 观察执行结果,并与实验步骤一的结果比较。
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<sys/types.h>
#include<linux/sem.h>
int mutexid;
int main()
{
int chld,i,j;
struct sembuf P,V;
union semun arg;
untexid = semget(IPC_PRIVATE,1,0666|IPC_CREAT);
arg.val=1;
if(semctl(mutexid,0,SETVAL,arg)
-1)
perror(“semctl setval error.”);
P.sem_num=0;
P.sem_op=-1;
P.sem_flg=SEM_UNDO;
V.sem_num=0;
V.sem_op=1;
V.sem_flg=SEM_UNDO;
while((chld=fork())==-1)
if(chld>0)
{
i=1;
while(i<=3)
{
sleep(1);
semop(mutexid,&P,1);//进入临界区,执行P操作
printf(“parent in.\n”);
sleep(1);
printf(“parent out.\n”);//出临界区,执行V操作
semop(mutexid,&V,1);
i++;
}
wait(0);
semctl(mutexid,IPC_RMID,0);
exit(0);
}
else
{
j=1;
while(j<=3)
{
sleep(1);
semop(mutexid,&P,1);
printf(“child in.\n”);
sleep(1);
printf(“child out.\n”);
semop(mutexid,&V,1);
j++;
}
exit(0);
}
}
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
【实验报告】
填写《信息技术学院学生上机实验报告》。
【思考题】
(1) 实验步骤一的结果说明什么?
(2) P、V操作在各进程代码中的位置应该如何处理?

  • 6
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值