进程间通信顾名思义就是进程之间的通信机制,英文简称IPC,进程见通信主要有一下几种方式:
信号(signal):亲缘进程和非亲缘进程都可以, 也可以进程自己给自己递送信号。
信号量(semaphore):主要是线程间和亲缘进程间的同步手段,不做数据传输之用。
命名信号量:可以在非亲缘进程间使用。
管道:只允许亲缘进程间的通讯。
命名管道(FIFO):除了亲缘进程可以通讯外,非亲缘进程也可以通讯。
共享内存:多个进程可以访问同一块内存空间,是最快的IPC方式。在进程间传递数据时无须任何内存的拷贝 。
套接字:最通用的进程间通讯方式,它提供了一种让不同机器上进程间通讯方式。
1.管道
管道是半双工的,当需要双向通讯时,需要两个管道。只能用于父子进程和兄弟进程等有亲缘关系的进程。数据写入时,放在管道的结尾。
#include
int pipe(int pipefd[2]);
pipe用来创建一个管道,这个管道用于父子进程间的通讯。pipefd[0]是管道的读端,pipefd[1]是管道的写端。由于管道是单向的,所有一个进程需要关闭写端或者读端。写端不存在时,读端会收到文件结束符。读端不存在时,写端会收到SIGPIPE信号。成功返回0,失败返回-1.
缺点:只能用于亲缘进程间通讯。数据读取时,从管道的头开始读取。
以下是实现代码
#include #include #include #include #include #include #include main() { int pipe_fd[2]; pid_t pid; char r_buf[100]; char w_buf[4]; char* p_wbuf; int r_num; int cmd; bzero(r_buf,sizeof(r_buf)); bzero(w_buf,sizeof(r_buf)); p_wbuf=w_buf; if(pipe(pipe_fd)<0) { printf("pipe create error/n"); return -1; } if((pid=fork())==0) { close(pipe_fd[1]); r_num=read(pipe_fd[0],r_buf,100); printf( "read num is %d the data read from the pipe is %d/n",r_num,atoi(r_buf)); close(pipe_fd[0]); exit(-1); } else if(pid>0) { close(pipe_fd[0]);//read strcpy(w_buf,"111"); if(write(pipe_fd[1],w_buf,4)!=-1){ printf("parent write over/n"); } close(pipe_fd[1]);//write printf("parent close fd[1] over/n"); wait(NULL); } }
2.命名管道(FIFO)
命名管道可以在所有进程间使用,克服了管道只能在亲缘进程通讯的限制。命名管道与一个路径名相关联,以文件的形式存在于文件系统中。只要能访问该文件的进程就可以使用命名管道。命名管道也是先进先出,虽然以文件形式实现单不支持seek等操作。
创建FIFO
#include
#include
int mkfifo(const char * pathname, mode_t mode)
第一个参数为路径名,第二个为创建类型。跟create函数的参数一样。进程只要打开这个文件,就可以往这个文件读和写。当多个进程往fifo里写时,linux只保证PIPE_BUF大小的字节数是原子的。
成功返回0,失败返回-1。
下面代码分别实现一个写管道和读管道
#include #include #include #include #include #include #define FIFO_SERVER "./fifoserver" int main(int argc,char** argv) { int fd; int real_wnum; char w_buf[]={"this is fifo"}; if((mkfifo(FIFO_SERVER,S_IRWXU )<0)&&(errno!=EEXIST)){ printf("cannot create fifoserver/n"); } //fd=open(FIFO_SERVER,O_WRONLY|O_NONBLOCK,0); fd=open(FIFO_SERVER,O_WRONLY,0); if(fd==-1 && errno==ENXIO){ printf("open error; no reading process/n"); } real_wnum=write(fd,w_buf,sizeof(w_buf)); if(real_wnum==-1) { if(errno==EAGAIN) printf("write to fifo error; try later/n"); } else{ printf("real write num is %d/n",real_wnum); } }
#include #include #include #include #include #include #include #define FIFO_SERVER "./fifoserver" main(int argc,char** argv) { char r_buf[4096]; int fd; int r_size; int ret_size; r_size=100; bzero(r_buf,sizeof(r_buf)); //fd=open(FIFO_SERVER,O_RDONLY|O_NONBLOCK,0); fd=open(FIFO_SERVER,O_RDONLY,0); if(fd==-1){ printf("open %s for read error/n"); exit(-1); } else { ret_size=read(fd,r_buf,r_size); if(ret_size==-1 && errno==EAGAIN){ printf("no data avlaible/n"); } printf("real read bytes %d/n",ret_size); unlink(FIFO_SERVER); } }
popen,pclose 函数
#include
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);
popen的作用相当于创建一个管道,然后再fork一个子进程,最后执行命令。根据命令的不同这个管道是读的或者写的。
失败返回NULL
#include #include int main(int argc, char **argv) { char line[4096]; FILE *fin; int i=0; fin = popen("ls -l", "r"); if ( NULL == fin){ printf("error popen/n"); return -1; } while(true){ if ( NULL == fgets(line, 4096,fin) ){ break; } printf("%d %s/n",++i,line); } pclose(fin); return 0; }
3.共享内存
共享内存是操作系统把同一块物理内存映射到不同进程的地址空间。
它的优点是效率高,无须拷贝 。由于是多个进程可以自由读写共享内存,所以需要同步机制。
mmap函数
void* mmap ( void * addr , size_t len , int prot , int flags , int fd , off_t offset )
mmap在进程地址空间创建一个映射。它既可以把一个文件映射到内存,也可以映射一块内存,实现进程间内存共享。
addr为共享内存的起始地址,为NULL时,内核会自动选择一个起始地址。length为共享内存的长度。
prot指明了共享内存保护状态:PROT_EXEC,PROT_READ,PROT_WRITE,PROT_NONE
flags MAP_SHARED 共享此内存,MAP_PRIVATE 只有该进程可见。MAP_ANONYMOUS为匿名映射。
#include #include #include int main(int argc, char ** argv) { char *area; pid_t pid; int i; area = mmap(NULL, 100, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS,-1,0); if ( MAP_FAILED == area ){ printf("map failed/n"); return -1; } pid = fork(); if( pid < 0 ){ printf("fork error/n"); return -1; } if ( pid == 0){ for(i=0; i<10; i++){ area[i] = 'a'; } } else{ for(i=10; i<20; i++){ area[i] = 'b'; } wait(NULL); printf("%s/n",area); } }
系统V共享内存
#include
#include
#include
int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
key_t ftok(const char *pathname, int proj_id);
ftok生成一个key,作为shmget的第一个参数。pathname必须为调用进程可以访问的。 proj_id的低8bit有效。pathname和proj_id共同组成一个key。
shmget 用来获取指定size的共享内存。第一个参数用来标识共享内存,第二个参数为共享内存的大小,第三个参数shmflg有IPC_CREAT和IPC_EXCL,最为重要的是在shmflg中指明访问权限,跟open的mode参数一样。否则会出现permission denied等错误。
失败返回-1。
shmat 把共享内存映射到进程空间。第一个参数为shmget的返回值,第二个参数可以为NULL,意味着有内核来选择映射的地址,第三个为 映射到进程空间时共享内存的权限。shmdt解除进程对共享内存的映射。shmctl控制共享内存。其中shmctl(shmid, IPC_RMID,0)用来从系统中移除共享内存。
#include #include #include #include #include #include #include #include int main(int argc, char** argv) { int shm_id,i; key_t key; char* name = "/dev/zero"; char *shm; key = ftok(name,0); if(key==-1){ printf ("ftok error %s /n",strerror(errno)); return -1; } shm_id=shmget(key,4096,IPC_CREAT|0600); if(shm_id==-1) { printf("shmget error %s/n",strerror(errno)); return -1; } shm=shmat(shm_id,NULL,0); if( -1 ==shm){ printf("shmat failed %s/n", strerror(errno)); return -1; } strncpy(shm,"hello shm",10); // if(shmdt(shm)==-1){ printf("shmdt error %s/n",strerror(errno)); return -1; } }
#include #include #include #include #include int main(int argc, char** argv) { int shm_id,i; key_t key; char* name = "/dev/zero"; char *shm; key = ftok(name,0); if(key == -1){ printf("ftok error/n"); } shm_id = shmget(key,4096,IPC_CREAT); if(shm_id == -1){ printf("shmget error/n"); return -1; } shm = shmat(shm_id,NULL,0); printf("content is %s/n",shm); if(shmdt(shm) == -1){ printf(" detach error/n"); } }
4.信号量
#include
int sem_init (sem_t *sem, int pshared, unsigned int value);
int sem_destroy(sem_t * sem)
sem_init初始化一个无名信量,pshared为1时,该信号量可以在进程间使用,为0时只在当前进程中使用。value信号灯的初值。
sem_destroy释放有sem_init初始化后的信号量。释放时候必须确保没有任何线程或者进程在使用此信号量,否则程序出现未定义行为。
成功返回0,失败返回非0.
#include
int sem_wait(sem_t * sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec*abs_timeout);
sem_wait函数是原子操作,把信号量的值减去1。如果此时信号量的值已经是0,那么调用sem_wait的线程会阻塞在这个信号量上,直到信号量的值变为正数。
sem_trywait只是检查信号量的值是否大于0,如果信号量等于0,调用线程并不阻塞,errno设置为EAGAIN
sem_timedwait增加了阻塞的时间限制,其余跟sem_wait一样。时间到时errno设置为ETIMEDOUT
成功返回0,失败返回-1。
#include
int sem_post(sem_t *sem);
int sem_getvalue(sem_t * sem, int * sval)
sem_post原子的增加信号量的值,如果信号量的值变成大于0了,那么阻塞在sem_wait中的其中一个线程会被唤醒。
sem_getvalue获取当前信号量中的值,如果有线程阻塞在sem_wait中,返回值可以为0也可以为一个负值,该负值的绝对值等于等待线程数。
成功返回0,失败返回-1.
#include #include #include #include #define N_PRODUCER 1 #define N_CONSUMER 2 #define BUFF_SIZE 5 pthread_once_t once=PTHREAD_ONCE_INIT; pthread_t producers[N_PRODUCER]; pthread_t consumers[N_CONSUMER]; sem_t full; sem_t empty; typedef struct buff_tag{ pthread_mutex_t mutex; }buff_t; buff_t buff; void once_init_entry(void){ pthread_mutex_init(&buff.mutex,NULL); sem_init(&full,0,0); sem_init(∅,0,BUFF_SIZE); } void* producer(void *arg){ pthread_t self=pthread_self(); int full_,empty_; for(;;){ sem_wait(∅); pthread_mutex_lock(&buff.mutex); sem_getvalue(&full, &full_); sem_getvalue(∅, &empty_); printf("producer full value %d, empty value %d/n", full_, empty_); pthread_mutex_unlock(&buff.mutex); sem_post(&full); sleep(1); } } void* consumer(void *arg){ pthread_t self=pthread_self(); int full_,empty_; for(;;){ sem_wait(&full); pthread_mutex_lock(&buff.mutex); sem_getvalue(&full, &full_); sem_getvalue(∅, &empty_); printf("cosumer full value %d, empty value %d/n", full_, empty_); pthread_mutex_unlock(&buff.mutex); sem_post(∅); sleep(3); } } int main(int argc,char** argv) { pthread_once(&once,once_init_entry); int i; for(i=0;i