图一给出了linux 所支持的各种IPC手段,在本文接下来的讨论中,为了避免概念上的混淆,在尽可能少提及Unix的各个版本的情况下,所有问题的讨论最终都会归结到Linux环境下的进程间通信上来。并且,对于Linux所支持通信手段的不同实现版本(如对于共享内存来说,有Posix共享内存区以及System V共享内存区两个实现版本),将主要介绍Posix API。
进程通信有如下一些目的:
1)数据传输:一个进程需要将它的数据发送给另一个进程,发送的数据量在一个字节到几M字节之间
2)共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。
3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
4)资源共享:多个进程之间共享同样的资源。为了作到这一点,需要内核提供锁和同步机制。
5)进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。
进程间通信方式类型:
1)UNIX进程间通信方式:
信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)
3)报文(Message)队列(消息队列)更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字
ipc提供方:
方法 | 提供方(操作系统或其他环境) |
---|---|
文件 | 多数操作系统 |
信号 | 多数操作系统 |
Berkeley套接字 | 多数操作系统 |
消息队列 | 多数操作系统 |
管道 | 所有的 POSIX 系统, Windows. |
命名管道 | 所有的 POSIX 系统, Windows. |
信号量 | 所有的 POSIX 系统, Windows. |
共享内存 | 所有的 POSIX 系统, Windows. |
Message passing (不共享) |
用于 MPI规范,Java RMI, CORBA, MSMQ, MailSlot 以及其他. |
Memory-mapped file | 所有的 POSIX 系统, Windows. |
与直接共享内存地址空间的多线程编程相比,IPC的缺点:
1)采用了某种形式的内核开销,降低了性能;
2)几乎大部分IPC都不是程序设计的自然扩展,往往戏剧性增加了程序复杂度。
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
if(pipe(pipe_fd)<0){
printf("pipe create error\n");
return -1;
}
else
printf("pipe create success\n");
close(pipe_fd[0]);
close(pipe_fd[1]);
return 0;
}
#include
#include
#include
#include
#include
int main()
{
int pipe_fd[2];
pid_t pid;
char buf_r[100];
char* p_wbuf;
int r_num;
memset(buf_r,0,sizeof(buf_r));数组中的数据清0;
if(pipe(pipe_fd)<0){//创建管道
printf("pipe create error\n");
return -1;
}
if((pid=fork())==0){//子进程(读者)
printf("\n");
close(pipe_fd[1]);//子进程不需要管道写端
sleep(2);
if((r_num=read(pipe_fd[0],buf_r,100))>0){//从管道读取数据到缓冲区
printf("%d numbers read from be pipe is %s\n",r_num,buf_r);
}
close(pipe_fd[0]);//关闭管道读端
exit(0);
}else if(pid>0){//父进程(写者)
close(pipe_fd[0]);//父进程不需要管道读端
if(write(pipe_fd[1],"Hello",5)!=-1)
printf("parent write success!\n");
if(write(pipe_fd[1]," Pipe",5)!=-1)
printf("parent wirte2 succes!\n");
close(pipe_fd[1]);//关闭管道写端
sleep(3);
waitpid(pid,NULL,0);//暂时停止目前进程的执行,直到有信号来到或子进程结束
exit(0);
}
return 0;
}
#include
#include
#include
#include
#define BUFSIZE 1024
int main(){
FILE *fp;
char *cmd="ps -ef";
char buf[BUFSIZE];
buf[BUFSIZE]='\0';
if((fp=popen(cmd,"r"))==NULL)
perror("popen");
while((fgets(buf,BUFSIZE,fp))!=NULL)
printf("%s",buf);
pclose(fp);
exit(0);
retutn 0;
}
#include
#include
#include
#include
#include
#include
#include
#define FIFO "/tmp/myfifo"
int main(int argc,char** argv)
{
char buf_r[100];
int fd;
int nread;
if((mkfifo(FIFO,O_CREAT|O_EXCL)<0)&&(errno!=EEXIST))//创建命名管道
{
printf("cannot create fifoserver\n");
return -1;
}
printf("Preparing for reading bytes....\n");
memset(buf_r,0,sizeof(buf_r));//初始化缓冲区
fd=open(FIFO,O_RDONLY|O_NONBLOCK,0);//打开命名管道的读端
if(fd==-1)
{
perror("open");
exit(1);
}
while(1){//一直从命名管道读数据
memset(buf_r,0,sizeof(buf_r));
if((nread=read(fd,buf_r,100))==-1){
if(errno==EAGAIN)
printf("no data yet\n");
}
printf("read %s from FIFO\n",buf_r);
sleep(1);
}
pause();
unlink(FIFO);//删除命名管道的文件
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#define FIFO_SERVER "/tmp/myfifo"
main(int argc,char** argv)
{
if(argc==1)
{
printf("Please send something\n");
return -1;
}
int fd;
char w_buf[100];
int nwrite;
fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0);
if(fd==-1 )
{
if(errno==ENXIO)printf("open error;no reading process\n");
return -1;
}
strcpy(w_buf,argv[1]);//需要写的数据,拷贝到写缓冲区
if((nwrite=write(fd,w_buf,100))==-1)
{//写有误
if(errno==EAGAIN)//写缓存已满
printf("The FIFO has not been read yet. Please try later\n");
}
else
printf("write %s to the FIFO\n",w_buf);
reutrn 0;
}
#include
#include
#include
#include
#include
int main()
{
pid_t pid;
int ret;
if((pid==fork())<0)//生成子进程
{
perro("fork");
exit(1);
}
if(pid==0)//子进程
{
raise(SIGSTOP);
exit(0);
}
else
{//父进程
printf("pid=%d\n",pid);
if((waitpid(pid,NULL,WNOHANG))==0)//等待子进程
{
if((ret=kill(pid,SIGKILL))==0) //发送杀死信号到子进程
printf("kill %d\n",pid);
else
{
perror("kill");
}
}
}
return 0;
}
#include
#include
#include
int main()
{
int ret;
ret=alarm(5);
pause();
printf("I have been waken up.\n",ret);
}
#include
#include
#include
void my_func(int sign_no)
{
if(sign_no==SIGINT)//接收到的信号是SIGINT
printf("I have get SIGINT\n");
else if(sign_no==SIGQUIT)//接收到的信号是SIGQUIT
printf("I have get SIGQUIT\n");
}
int main()
{
printf("Waiting for signal SIGINT or SIGQUTI\n");
signal(SIGINT,my_func);//注册信号处理函数
signal(SIGQUIT,my_func);
pasue();
exit(0);
return 0;
}
struct sigaction{//信号动作
void (*sa_handler)(int signo);
sigset_t sa_mask;
int sa_flags;
void (*sa_restore);
};
#include
#include
#include
#include
#include
void my_func(int signum){
printf("If you want to quit,please try SIGQUIT\n");
}
int main()
{
sigset_t set,pendset;//信号集
struct sigaction action1,action2;
if(sigemptyse(&set)<0)
perror("sigemptyset");
if(sigaddset(&set,SIGQUIT)<0)
perror("sigaddset");
if(sigaddset(&set,SIGINT)<0)
perror("sigaddset");
if(sigprocmask(SIG_BLOCK,&set,NULL)<0)
perror("sigprcmask");
else{
printf("blocked\n");
sleep(5);
}
if(sigprocmask(SIG_UNBLOCK,&set,NULL)
perror("sigprocmask");
else
printf("unblock\n");
while(1){
if(sigismember(&set,SIGINT))
{
sigemptyset(&action1.sa_mask);
action1.sa_handler=my_func;
sigaction(SIGINT,&action1,NULL);//注册信号动作(信号处理函数)
}
else if(sigismember(&set,SIGQUIT))
{
sigemptyset(&action2.sa_mask);
action2.sa_handler=SIG_DEL;
sigaction(SIGTERM,&action2,NULL);
}
}
return 0;
}
信号量(英语:Semaphore)又称为信号量、旗语,它以一个整数变量,提供信号,以确保在并行计算环境中,不同进程在访问共享资源时,不会发生冲突。是一种不需要使用忙碌等待(busy waiting)的一种方法。
信号量的概念是由荷兰计算机科学家艾兹格·迪杰斯特拉(Edsger W. Dijkstra)发明的,广泛的应用于不同的操作系统中。在系统中,给予每一个进程一个信号量,代表每个进程目前的状态,未得到控制权的进程会在特定地方被强迫停下来,等待可以继续进行的信号到来。如果信号量是一个任意的整数,通常被称为计数信号量(Counting semaphore),或一般信号量(general semaphore);如果信号量只有二进制的0或1,称为二进制信号量(binary semaphore)。在linux系中,二进制信号量(binary semaphore)又称Mutex。
计数信号量具备两种操作动作,之前称为 V(又称signal())与 P(wait())。 V操作会增加信号量 S的数值,P操作会减少它。
运作方式:
1)初始化,给与它一个非负数的整数值。
2)运行 P(wait()),信号量S的值将被减少。企图进入临界区段的进程,需要先运行 P(wait())。当信号量S减为负值时,进程会被挡住,不能继续;当信号量S不为负值时,进程可以获准进入临界区段。
3)运行 V(又称signal()),信号量S的值会被增加。退出离开临界区段的进程,将会运行 V(又称signal())。当信号量S不为负值时,先前被挡住的其他进程,将可获准进入临界区段。
信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。当公共资源增加时,调用函数sem_post()增加信号量。只有当信号量值大于0时,才能使用公共资源,使用后,函数sem_wait()减少信号量。函数sem_trywait()和函数pthread_ mutex_trylock()起同样的作用,它是函数sem_wait()的非阻塞版本。它们都在头文件/usr/include/semaphore.h中定义。
信号量的数据类型为结构sem_t,它本质上是一个长整型的数。函数sem_init()用来初始化一个信号量。它的原型为:
extern int sem_init __P ((sem_t *__sem, int __pshared,unsigned int __value));
sem为指向信号量结构的一个指针;pshared不为0时此信号量在进程间共享,否则只能为当前进程的所有线程共享;value给出了信号量的初始值。
函数sem_post( sem_t *sem )用来增加信号量的值。当有线程阻塞在这个信号量上时,调用这个函数会使其中的一个线程不在阻塞,选择机制同样是由线程的调度策略决定的。
函数sem_wait( sem_t *sem )被用来阻塞当前线程直到信号量sem的值大于0,解除阻塞后将sem的值减一,表明公共资源经使用后减少。
函数sem_trywait ( sem_t *sem )是函数sem_wait()的非阻塞版本,它直接将信号量sem的值减一。
函数sem_destroy(sem_t *sem)用来释放信号量sem。
例子中一共有4个线程,其中两个线程负责从文件读取数据到公共的缓冲区,另两个线程从缓冲区读取数据作不同的处理(加和乘运算)。
/* File sem.c */
#include
#include
#include
#define MAXSTACK 100
int stack[MAXSTACK][2];//共享缓存
int size=0;
sem_t sem;
/* 从文件1.dat读取数据*/
void ReadData1(void)
{
FILE*fp=fopen("1.dat","r");
while(!feof(fp))
{
fscanf(fp,"%d%d",&stack[size][0],&stack[size][1]);
sem_post(&sem);//信号量加1
__sync_fetch_and_add(&size,1);
}
fclose(fp);
}
/*从文件2.dat读取数据*/
void ReadData2(void)
{
FILE*fp=fopen("2.dat","r");
while(!feof(fp))
{
fscanf(fp,"%d%d",&stack[size][0],&stack[size][1]);
sem_post(&sem);//信号量加1
__sync_fetch_and_add(&size,1);
}
fclose(fp);
}
/*阻塞等待缓冲区的数据*/
void HandleData1(void)
{
while(1)
{
sem_wait(&sem);//信号量减1
printf("Plus:%d+%d=%d\n",stack[size][0],stack[size][1],stack[size][0]+stack[size][1]);//读取缓冲区数据
__sync_fetch_and_sub(&size,1);
}
}
/*阻塞等待缓冲区的数据*/
void HandleData2(void)
{
while(1)
{
sem_wait(&sem);//信号量减1
printf("Multiply:%d*%d=%d\n",
stack[size][0],stack[size][1],stack[size][0]*stack[size][1]);//读取缓冲区数据
__sync_fetch_and_sub(&size,1);
}
}
int main(int argc,char** argc)
{
pthread_t t1,t2,t3,t4;
sem_init(&sem,0,0);//初始化信号量,初始值是0
pthread_create(&t1,NULL,(void*)HandleData1,NULL);
pthread_create(&t2,NULL,(void*)HandleData2,NULL);
pthread_create(&t3,NULL,(void*)ReadData1,NULL);
pthread_create(&t4,NULL,(void*)ReadData2,NULL);
/* 连接程序*/
pthread_join(t1,NULL);
pthread_join(t2,NULL);
pthread_join(t3,NULL);
pthread_join(t4,NULL);
return 0;
}
在Linux下,用命令gcc -lpthread sem.c -o sem生成可执行文件sem。
编辑数据文件1.dat和2.dat,内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10
运行./sem,结果如下:
Multiply:-1*-2=2
Plus:-1+-2=-3
Multiply:9*10=90
Plus:-9+-10=-19
Multiply:-7*-8=56
Plus:-5+-6=-11
Multiply:-3*-4=12
Plus:9+10=19
Plus:7+8=15
Plus:5+6=11
可以看出各个线程间的竞争关系。
数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程修改的缘故。
共享内存的实现分为两个步骤:
1) 创建共享内存,使用shmget函数。
2) 映射共享内存,将这段创建的共享内存映射到具体的进程空间去,使用shmat函数。
共享内存
int shmget(key_t key ,int size,int shmflg)
key标识共享内存的键值:0/IPC_PRIVATE 。
当key的取值为IPC_PRIVATE,则函数shmget将创建一块新的共享内存;如果key的取值为0,而参数中又设置了IPC_PRIVATE这个标志,则同样会创建一块新的共享内存。
size :是这块内存的大小
shmflg: 是这块内存的模式(mode)以及权限标识:
IPC_CREAT 新建(如果已创建则返回目前共享内存的id)
IPC_EXCL 与IPC_CREAT结合使用,如果已创建则则返回错误
然后将“模式” 和“权限标识”进行“或”运算,做为第三个参数。
如:
IPC_CREAT | IPC_EXCL | 0640
例子中的0666为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限。
如0666为权限标识,4/2/1 分别表示读/写/执行3种权限,第一个0是UID,第一个6(4+2)表示拥有者的权限,第二个4表示同组权限,第3个0表示他人的权限。
返回值:
如果成功,返回共享内存表示符,如果失败,返回-1。
映射共享内存
int shmat(int shmid,char *shmaddr,int flag)
参数:
shmid:shmget函数返回的共享存储标识符
flag:决定以什么样的方式来确定映射的地址(通常为0)
返回值:如果成功,则返回共享内存映射到进程中的地址;如果失败,则返回-1。
共享内存解除映射
当一个进程不再需要共享内存时,需要把它从进程地址空间中移除。
int shmdt(char *shmaddr)
实例如下:
创建两个进程,在A进程中创建一个共享内存,并向其写入数据,通过B进程从共享内存中读取数据。
#define TEXT_SZ 2048
struct shared_use_st
{
int written_by_you;
char some_text[TEXT_SZ];
};
#define SHARE_KEY (1234)
/**********************************************************
本程序申请和分配共享内存,然后轮训并读取共享中的数据,直至
* 读到“end”。
**********************************************************/
#include
#include
#include
#include
#include
#include
#include
#include "shm_com.h"
/*
* 程序入口
* */
int main(void)
{
int running=1;
void *shared_memory=(void *)0;
struct shared_use_st *shared_stuff;
int shmid;
/*创建共享内存*/
shmid=shmget((key_t)SHARE_KEY ,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
/*映射共享内存*/
shared_memory=shmat(shmid,(void *)0,0);
if(shared_memory==(void *)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(int)shared_memory);
/*让结构体指针指向这块共享内存*/
shared_stuff=(struct shared_use_st *)shared_memory;
/*控制读写顺序*/
shared_stuff->written_by_you=0;
/*循环的从共享内存中读数据,直到读到“end”为止*/
while(running)
{
if(shared_stuff->written_by_you)
{
printf("You wrote:%s",shared_stuff->some_text);
sleep(1); //读进程睡一秒,同时会导致写进程睡一秒,这样做到读了之后再写
shared_stuff->written_by_you=0;
if(strncmp(shared_stuff->some_text,"end",3)==0)
{
running=0; //结束循环
}
}
}
/*删除共享内存*/
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
/**********************************************************
本程序申请了上一段程序相同的共享内存块,然后循环向共享中
* 写数据,直至写入“end”。
**********************************************************/
#include
#include
#include
#include
#include
#include
#include
#include "shm_com.h"
/*
* 程序入口
* */
int main(void)
{
int running=1;
void *shared_memory=(void *)0;
struct shared_use_st *shared_stuff;
char buffer[BUFSIZ];
int shmid;
/*创建共享内存*/
shmid=shmget((key_t)SHARE_KEY ,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
/*映射共享内存*/
shared_memory=shmat(shmid,(void *)0,0);
if(shared_memory==(void *)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(int)shared_memory);
/*让结构体指针指向这块共享内存*/
shared_stuff=(struct shared_use_st *)shared_memory;
/*循环的向共享内存中写数据,直到写入的为“end”为止*/
while(running)
{
while(shared_stuff->written_by_you==1)
{
sleep(1);//等到读进程读完之后再写
printf("waiting for client...\n");
}
printf("Ener some text:");
fgets(buffer,BUFSIZ,stdin);
strncpy(shared_stuff->some_text,buffer,TEXT_SZ);
shared_stuff->written_by_you=1;
if(strncmp(buffer,"end",3)==0)
{
running=0; //结束循环
}
}
/*删除共享内存*/
if(shmdt(shared_memory)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
#include
#include
#include
#include
#include
#define BUFSZ 4096
int main ( void )
{
int shm_id; /*共享内存标识符*/
shm_id=shmget(IPC_PRIVATE, BUFSZ,0666);//使用IPC_PRIVATE会创建新的共享内存块
//shm_id=shmget(999,BUFSZ,0666|O_CREAT);
if (shm_id < 0 )
{ /*创建共享内存*/
perror( "shmget" ) ;
exit ( 1 );
}
printf ( "successfully created segment : %d \n", shm_id ) ;
system( "ipcs -m"); /*调用ipcs命令查看IPC*/
exit( 0 );
}