linux应用编程笔记(12)信号量详解及互斥编程

摘要: 总结了信号量的机制,以及各个信号量操作的函数,最后通过公示栏问题,将信号量机制引入加深了理解。


一、什么是信号量

    信号量的主要用途是保护临界资源,进程根据信号量用于判断能否访问某些共享资源,除了用于访问控制以为,还可以用于进程间的同步。当信号量的值只能取0或者1的时候,叫二值信号量,当可以取任意非负值的时候叫做计数信号量。

二、公示栏问题

    有一个公示栏,甲乙都想往里写东西,甲写了一段文字之后,有事离开了一下,这时候乙再往里写了一些东西,甲回来再接着自己之前的写,这样整个公示栏就混乱了。这其实抽象的是两个进程同时访问一个资源,但是这个资源在一个时间内只能被一个进程访问,这样就会造成数据混乱,为了避免这种情况,一种比较好的解决办法就是信号量的互斥编程。


三、将公示栏问题程序化

    我们将公示栏用文件代替,甲乙同学用AB两个进程代替,这样相应的对公示栏的操作就可以转化为AB两个进程对文件的操作。

    首先是A进程,往文件里写入“数学课取消”,在写完“数学课”的时候,休息一段时间,然后接着写“取消”

    B进程往文件里写入“英语课考试”。

    程序分别如下:

<span style="font-size:18px;">processA.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
 
int main(void)
{
    int fd;
    /*打开公示栏并判断是否打开成功*/
    fd=open("./board.txt",O_RDWR|O_APPEND);
    if(fd==-1)
       {
           printf("openerror!\n");
           exit(0);
           }
    /*写入一部分*/
    write(fd,"math class ",12);
    /*休息*/
    sleep(20);
    /*接着写*/
    write(fd,"is cancle",10);
    /*关闭公告栏,写完了*/
    close(fd);
    return 0;
    }</span>
<span style="font-size:18px;">
bprocess.c的内容如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
 
int main(void)
{
    int fd;
    /*打开公示栏*/
    fd=open("./board.txt",O_RDWR|O_APPEND);
    if(fd==-1)
       {
           printf("openerror!\n");
           exit(0);
           }
    /*往里写入内容*/
    write(fd,"englishexam",15);
    /*关闭公示栏,写完了*/
    close(fd);
    return 0;
    }</span>

    编译运行,然后先运行a再运行b,在当前目录下使用ctrl+shit+T来创建一个新的窗口,运行完了之后,就可以看到board.txt里面写入了如下内容,和我们之前预计的情况是一样的,这种情况在操作系统里面是经常可以看到的。



四、信号量操作函数

1.创建/打开信号量

函数名semget

函数原型int semget(key_t key,int nsems,int semflg);

函数功能获取信号量集合的标识符

           当key键值指定的信号量集合不存在,并且semflg等于IPC_CREAT的时候,就创建信号量集合,并和键值关联,返回与之对应的标识符。

头文件#include <sys/types.h> #include <sys/ipc.h>#include <sys/sem.h>

返回值成功返回信号量集合的标识符,失败返回-1

参数说明

    key_t key:键值。那么什么是键值?我们在open一个文件的时候,是通过文件名来访问这个文件的,然后open返回的时候,就会返回这个文件的句柄,也就是我们的fd,这里也是一样,当我们使用semget的时候,成功了就会返回信号量集合的标识符,相当于文件的fd,但是之前我们怎么去访问这个信号量呢,那就是通过这个key,也就是相当于文件名,这个key就是键值,在系统中其实就是一个数字。

    如何获得键值?有两种方法:第一种是任意指定一个数字,但是这个数字有可能被别的IPC对象,例如消息队列,共享内存使用了,这样与创建的新的信号量关联的时候就会失败。第二种方法是使用一个函数来构造,构造出来的键值是可用的,不会已经被别人用过的:

    key_t ftok(char*fname,int id);

    那么这个ftok是如何工作的呢?两个参数,一个是我们要使用的文件名,另一个是我们的项目id,ftok会根据这个两个数字组合成一个信号量,当ab进程使用的时候根据文件名和项目id就会访问到相同的信号量集合。

    int semflg:标志,可以取IPC_CREAT,意思是当前没有key对应的信号量集合的时候,就创建它,这一点类似open,通过文件名打开,如果文件不存在就自己创建它,这个很好理解吧。

    int nsems:创建信号量集合里面包含的信号量数目


2.操作信号量函数

函数名semop

函数原型int semop(int semid,struct sembuf *sops,unsigned nsops);

函数功能对信号量进行操作,包括获取和释放

头文件#include <sys/ipc.h> #include <sys/types.h>#include <sys/sem.h>

返回值成功返回0,失败返回-1

参数说明

    int semid:要操作的信号量集合的标识符

    struct sembuf *sops:要操作信号量集合里哪一个信号量,以及对信号量是进行+还是-,这里+意味着获取信号量,-意味着释放信号量,其中的sem_flg是在无法获取信号量之后是等待还是什么都不做就退出。

    unsigned nsops:要操作多少个信号量


3.信号量初始值检查和设置函数

函数名:semctl

函数原型:int semctl(int semid,int semnum,int cmd,…);

函数功能:检查信号量集合中指定的信号量当前的值为多少

头文件:#include <sys/ipc.h> #include <sys/types.h> #include<sys/sem.h>

返回值:失败返回-1,成功根据cmd给入的参数返回响应的值,这里我们是GETVAL

参数说明:同上,只是这里的cmd需要注意,可以man 2semctl查看


五、利用信号量互斥控制公示栏

    进入aprocess程序,将信号量互斥机制加进去,a在写之前要获取信号量,无法获得就不能写,获得之后才能写,写完了释放,这样b就可以获得再去写,写完了也要释放,这当中还要进行信号量初始值的检查。

<span style="font-size:18px;">aprocess.c内容如下:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
int main(void)
{
    int fd;
    key_t key;//键值
    int semid;
    int retval;
    struct sembuf sops;
    /*创建信号量*/
    key= ftok("/home/passionbird",1);//这里可以利用一个目录创建多个键值,只要他们的项目编号不一样就可以
    semid=semget(key,1,IPC_CREAT);//将键值传入,我们这里信号量集合里就只有一个信号量,因为还没有,所以需要创建,加上IPC_CREAT
   
    /*检查信号量的初始值*/
    retval=semctl(semid,0,GETVAL);
    printf("theinit value is:%d\n",retval);
    retval=semctl(semid,0,SETVAL,1);//设置为1
    printf("the init value is:%d\n",retval);//两处打印是为了确保初始值为1
    /*打开公示栏并判断是否打开成功*/
    fd=open("./board.txt",O_RDWR|O_APPEND);
    if(fd==-1)
       {
           printf("open error!\n");
           exit(0);
           }
    /*获取信号量*/
    sops.sem_num= 0;//因为只有一个信号量,所以在操作数组中的编号为0
    sops.sem_op= -1;//-1即获取走了信号量
    semop(semid,&sops,1);    //传入返回的semid,值操作一个信号量,这里需要提前顶一个struct sembuf
    /*写入一部分*/
    write(fd,"mathclass ",12);
    /*休息*/
    sleep(20);
    /*接着写*/
    write(fd,"iscancle",12);
    /*释放信号量*/
    sops.sem_num= 0;//因为只有一个信号量,所以在操作数组中的编号为0
    sops.sem_op= +1;//+1即释放了信号量,写成+1是为了便于理解
    semop(semid,&sops,1);   
    /*关闭公告栏,写完了*/
    close(fd);
    return 0;
    }
 
bprocess.c内容如下;
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/ipc.h>
#include <sys/sem.h>
 
int main(void)
{
    int fd;
    key_t key;
    int semid;
    int retval;
    struct sembuf sops;
    /*打开公示栏*/
    fd=open("./board.txt",O_RDWR|O_APPEND);
    /*打开信号量*/
    key= ftok("/home/passionbird",1);//利用相同的键值可以关联同一个信号量
    semid=semget(key,1,IPC_CREAT);//将键值传入,使得a,b打开的信号量是同一个信号量,这里已经有了不会再创建
    if(fd==-1)
       {
           printf("openerror!\n");
           exit(0);
           }
    /*获取前想查看初始值*/
    retval=semctl(semid,0,GETVAL);
    printf("theinit value is:%d\n",retval);     
    /*获取信号量*/
    sops.sem_num= 0;//因为只有一个信号量,所以在操作数组中的编号为0
    sops.sem_op= -1;//-1即获取走了信号量
    semop(semid,&sops,1);    //传入返回的semid,值操作一个信号量,这里需要提前顶一个struct sembuf
    /*往里写入内容*/
    write(fd,"englishexam",15);
    /*释放信号量*/
    sops.sem_num= 0;//因为只有一个信号量,所以在操作数组中的编号为0
    sops.sem_op= +1;//+1即释放了信号量,写成+1是为了便于理解
    semop(semid,&sops,1);   
    retval=semctl(semid,0,GETVAL);
    printf("theinit value is:%d\n",retval); 
    /*关闭公示栏,写完了*/
    close(fd);
    return0;
    }
 </span>

    最终编译运行,打开两个窗口,想运行a,在运行b,b会在运行的时候等待a休息结束写完释放信号量,b获得信号量之后才会往里写东西,打印出来的效果如下:

    这下数据就不混乱了,数学课取消,英语课考试。

    这篇帖子就总结到这里,如有不正确的地方还请指出,大家共同进步!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 中,可以使用 `semaphore.h` 头文件中提供的函数来实现互斥信号量。下面是一个简单的示例代码,实现了两个进程的互斥访问共享资源: ```c #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> #define KEY 1234 // 信号量的键值 int main() { int semid, pid; struct sembuf sops; // 创建信号量 semid = semget(KEY, 1, IPC_CREAT | IPC_EXCL | 0666); if (semid < 0) { perror("semget error"); exit(1); } // 初始化信号量 if (semctl(semid, 0, SETVAL, 1) < 0) { perror("semctl error"); exit(1); } // 创建子进程 pid = fork(); if (pid < 0) { perror("fork error"); exit(1); } if (pid == 0) { // 子进程 while (1) { // 进入临界区前等待信号量 sops.sem_num = 0; sops.sem_op = -1; sops.sem_flg = 0; semop(semid, &sops, 1); // 访问共享资源 printf("Child process is accessing the shared resource.\n"); sleep(1); // 离开临界区后释放信号量 sops.sem_op = 1; semop(semid, &sops, 1); } } else { // 父进程 while (1) { // 进入临界区前等待信号量 sops.sem_num = 0; sops.sem_op = -1; sops.sem_flg = 0; semop(semid, &sops, 1); // 访问共享资源 printf("Parent process is accessing the shared resource.\n"); sleep(1); // 离开临界区后释放信号量 sops.sem_op = 1; semop(semid, &sops, 1); } } return 0; } ``` 在上面的代码中,首先通过 `semget` 函数创建了一个键值为 `KEY` 的信号量,然后通过 `semctl` 函数对其进行初始化。接着创建了一个子进程,子进程和父进程分别进入临界区前等待信号量,访问共享资源后离开临界区释放信号量。这样就实现了两个进程的互斥访问共享资源。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值