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

来源:互联网 发布:晚会暖场小游戏知乎 编辑:程序博客网 时间:2024/06/18 04:52

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


一、什么是信号量

    信号量的主要用途是保护临界资源,进程根据信号量用于判断能否访问某些共享资源,除了用于访问控制以为,还可以用于进程间的同步。当信号量的值只能取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获得信号量之后才会往里写东西,打印出来的效果如下:

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

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

0 0