摘要: 总结了信号量的机制,以及各个信号量操作的函数,最后通过公示栏问题,将信号量机制引入加深了理解。
一、什么是信号量
信号量的主要用途是保护临界资源,进程根据信号量用于判断能否访问某些共享资源,除了用于访问控制以为,还可以用于进程间的同步。当信号量的值只能取0或者1的时候,叫二值信号量,当可以取任意非负值的时候叫做计数信号量。
二、公示栏问题
有一个公示栏,甲乙都想往里写东西,甲写了一段文字之后,有事离开了一下,这时候乙再往里写了一些东西,甲回来再接着自己之前的写,这样整个公示栏就混乱了。这其实抽象的是两个进程同时访问一个资源,但是这个资源在一个时间内只能被一个进程访问,这样就会造成数据混乱,为了避免这种情况,一种比较好的解决办法就是信号量的互斥编程。
三、将公示栏问题程序化
我们将公示栏用文件代替,甲乙同学用AB两个进程代替,这样相应的对公示栏的操作就可以转化为AB两个进程对文件的操作。
首先是A进程,往文件里写入“数学课取消”,在写完“数学课”的时候,休息一段时间,然后接着写“取消”
B进程往文件里写入“英语课考试”。
程序分别如下:
processA.c
#include
#include
#include
#include
#include
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;
}
bprocess.c的内容如下:
#include
#include
#include
#include
#include
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;
}
编译运行,然后先运行a再运行b,在当前目录下使用ctrl+shit+T来创建一个新的窗口,运行完了之后,就可以看到board.txt里面写入了如下内容,和我们之前预计的情况是一样的,这种情况在操作系统里面是经常可以看到的。
四、信号量操作函数
1.创建/打开信号量
函数名:semget
函数原型:int semget(key_t key,int nsems,int semflg);
函数功能:获取信号量集合的标识符
当key键值指定的信号量集合不存在,并且semflg等于IPC_CREAT的时候,就创建信号量集合,并和键值关联,返回与之对应的标识符。
头文件:#include
返回值:成功返回信号量集合的标识符,失败返回-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
返回值:成功返回0,失败返回-1
参数说明:
int semid:要操作的信号量集合的标识符
struct sembuf *sops:要操作信号量集合里哪一个信号量,以及对信号量是进行+还是-,这里+意味着获取信号量,-意味着释放信号量,其中的sem_flg是在无法获取信号量之后是等待还是什么都不做就退出。
unsigned nsops:要操作多少个信号量
3.信号量初始值检查和设置函数
函数名:semctl
函数原型:int semctl(int semid,int semnum,int cmd,…);
函数功能:检查信号量集合中指定的信号量当前的值为多少
头文件:#include
返回值:失败返回-1,成功根据cmd给入的参数返回响应的值,这里我们是GETVAL
参数说明:同上,只是这里的cmd需要注意,可以man 2semctl查看
五、利用信号量互斥控制公示栏
进入aprocess程序,将信号量互斥机制加进去,a在写之前要获取信号量,无法获得就不能写,获得之后才能写,写完了释放,这样b就可以获得再去写,写完了也要释放,这当中还要进行信号量初始值的检查。
aprocess.c内容如下:
#include
#include
#include
#include
#include
#include
#include
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
#include
#include
#include
#include
#include
#include
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;
}
最终编译运行,打开两个窗口,想运行a,在运行b,b会在运行的时候等待a休息结束写完释放信号量,b获得信号量之后才会往里写东西,打印出来的效果如下:
这下数据就不混乱了,数学课取消,英语课考试。
这篇帖子就总结到这里,如有不正确的地方还请指出,大家共同进步!