图一给出了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<unistd.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> 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<unistd.h> #include<memory.h> #include<errno.h> #include<stdio.h> #include<stdlib.h> 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<stdio.h> #include<unistd.h> #include<stdlib.h> #include<fcntl.h> #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<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #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<sys/types.h> #include<sys/stat.h> #include<errno.h> #include<fcntl.h> #include<stdio.h> #include<stdlib.h> #include<string.h> #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<stdio.h> #include<stdlib.h> #include<signal.h> #include<sys/types.h> #include<sys/wait.h> 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<unistd.h> #include<stdio.h> #include<stdlib.h> int main() { int ret; ret=alarm(5); pause(); printf("I have been waken up.\n",ret); }
#include<signal.h> #include<stdio.h> #include<stdlib.h> 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<sys/types.h> #include<unistd.h> #include<signal.h> #include<stdio.h> #include<stdlib.h> 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 <stdio.h> #include <pthread.h> #include <semaphore.h> #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 <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #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 <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #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 <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdlib.h> #include <stdio.h> #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 ); }