本文主要介绍下在多进程中使用信号量semaphore的方法。我们先来看下sem_init的原型,熟悉决定进程共享或者线程共享的方法:
#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
通过设置pshared的值来控制信号量是属于进程间共享还是线程间共享,若pshared为0表明是多线程共享,否则就是多进程间共享。接下来我们实验思路是:创建两个进程,一个进程负责读取用户在界面输入的数据,然后存入本地的test.txt文件;另一个进程负责读取该文件,然后在标准输出上显示读取的内容。为此,我们需要创建两个个支持两个进程访问的信号量sem1和sem2,读文件时需要获取sem1信号,读取结束后释放sem2信号;写文件需要获取sem2信号,写文件结束后方式sem1信号。sem2的初始值为1,sem1的初始值为0,以保证先写入再进行读取,源代码如下,稍后挑关键内容进行解释:
#include<stdio.h>
#include<stdlib.h>
#include<pthread.h>
#include<semaphore.h>
#include<string.h>
#include<sys/mman.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#define BUF_SIZE 30
void readfile(sem_t* psem1,sem_t* psem2)
{
FILE* fp;
char buf[BUF_SIZE];
int str_len,str_seek=0;
while(1)
{
sem_wait(psem1);
fp=fopen("data.txt","r+");
if(fp==NULL)
return ;
memset(buf,0,sizeof(BUF_SIZE));
fseek(fp,str_seek,SEEK_SET);
str_len=fread(buf,sizeof(char),BUF_SIZE-1,fp);
buf[str_len]=0;
str_seek+=str_len;
fputs("output:",stdout);
puts(buf);
fclose(fp);
sem_post(psem2);
}
}
void writefile(sem_t* psem1,sem_t* psem2)
{
FILE* fp;
char buf[BUF_SIZE];
while(1)
{
sem_wait(psem2);
fp=fopen("data.txt","a");
if(fp==NULL)
return;
memset(buf,0,BUF_SIZE);
fputs("Input:",stdout);
fgets(buf,BUF_SIZE,stdin);
fwrite(buf,sizeof(char),strlen(buf),fp);
fclose(fp);
sem_post(psem1);
}
}
int main()
{
int pid;
int fd1,fd2;
void* pv;
sem_t* psem1;
sem_t* psem2;
fd1=open("data1",O_CREAT|O_RDWR|O_TRUNC,0666);
fd2=open("data2",O_CREAT|O_RDWR|O_TRUNC,0666);\
ftruncate(fd1,8192);
ftruncate(fd2,8192);
//lseek(fd,5000,SEEK_SET);
psem1=(sem_t*)mmap(NULL,sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd1,0);
psem2=(sem_t*)mmap(NULL,sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED,fd2,0);
sem_init(psem1,1,0);
sem_init(psem2,1,1);
pid=fork();
if(pid==0)
{
puts("进入子进程");
writefile(psem1,psem2);
}
else
{
puts("进入父进程");
readfile(psem1,psem2);
}
sem_destroy(psem1);
sem_destroy(psem2);
munmap(psem1,sizeof(sem_t));
munmap(psem2,sizeof(sem_t));
close(fd1);
close(fd2);
return 0;
}
为了能够跨进程使用semaphore,我们引入了跨进程的技术mmap,第61、第62行分别打开了两个mmap需要映射的文件,和我们平时用的open函数不同,这里面为程序赋予了该文件的666权限。这点很重要,因为mmap需要映射的本地文件必须明确赋予其可读写的权限,否则无法通信。
第63行和第64行分别设置两个本地映射文件的大小,以保证有充分的空间在mmap中映射并容纳我们定义的sem_t变量。这点也很重要,如果空间不够会造成总线错误。
第66行和第67行分别利用mmap在共享内存中映射了两个sem_t类型的指针,这就是我们需要sem_init的信号量。
第68、69行开始初始化信号量。
70行fork了两个进程,在子进程中我们进行写操作,在主进程中我们进行读操作。读写操作的代码比较简单,在这里不再多说。
第81到86行在使用完信号量后分别是销毁信号量、释放共享内存、关闭文件操作符。
程序写到这里基本上完成了这个实验,可以看下执行的结果:
[Hyman@Hyman-PC semphare]$ ./a.out
进入父进程
进入子进程
Input:你好
Output:你好
Input:
我们可以简单总结下在多进程中使用信号量的步骤:
(1)open()用于进行mmap映射的文件,得到文件操作符fd;
(2)把映射文件用ftruncate或者fseek重新设置大小,以保证有足够的空间容纳我们需要传递的sem_t变量;
(3)利用mmap函数在共享内存中创建sen_t类型的指针。
(4)用sem_init()函数初始化第(3)步中创建的指针,也就得到了我们需要的信号量。
(5)用sem_wait()和sem_post()函数进行信号量的等待和释放。
(6)用sem_destroy()销毁信号量。
(7)用munmap()释放共享内存以及用close()函数关闭文件操作符。
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone git@github.com:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL40