LINUX进程间通信(IPC)学习手记

linux间进程通信的方法在前一篇文章中已有详细介绍。http://blog.csdn.net/jmy5945hh/article/details/7350564

本篇对六种IPC方法进行简单的代码测试。

由于博主最近在研究父子进程间通信,因此实例代码都基于此。如果要应用于无亲缘关系进程间通信,稍加修改即可。

篇幅有限,本文没有列出函数定义。相信你知道去哪里查询他们。

1/管道(Pipe)及有名管道(named pipe):

管道测试代码:

#include 
#include 
#include 
#include 
#include 

#define MAX 4096

int main(int argc, char *argv[]) {
	int pipefd[2], recvBytes, sendBytes;
	pid_t pid;
	unsigned char sendBuf[MAX], recvBuf[MAX];

	memset(sendBuf, 0x00, MAX);
	memset(recvBuf, 0x00, MAX);
	if(pipe(pipefd) < 0) { //创建管道
		perror("pipe:");
		exit(1);
	}

	if((pid = fork()) == 0) { //子进程从管道读取
		close(pipefd[1]);
		sleep(2);
		if((recvBytes = read(pipefd[0], recvBuf, MAX)) == -1) {
			perror("read");
			exit(1);
		}
		printf("Receive %d Bytes data:%s\n", recvBytes, recvBuf);
		close(pipefd[1]);
	}
	else if(pid > 0) { //父进程向管道写入
		close(pipefd[0]);
		strcpy(sendBuf, "Pipe test!");
		if((sendBytes = write(pipefd[1], sendBuf, MAX)) == -1) {
			perror("write");
			exit(1);
		}
		printf("Send %d Bytes data:%s\n", sendBytes, sendBuf);
		close(pipefd[0]);
		sleep(5);
	}

	exit(0);
}

运行结果:

jimmy@MyPet:~/code/ipc$ gcc -o -ggdb3 ipc ipc1.c 
jimmy@MyPet:~/code/ipc$ ./ipc 
Send 4096 Bytes data:Pipe test!
Receive 4096 Bytes data:Pipe test!

非正常测试结果及总结:

1)注释掉子进程中的接收部分,测试结果为父进程依然能向管道发送数据。

2)注释掉父进程中的发送部分,测试结果为子进程依然能从管道接收数据,recvBytes = 0。

3)子进程改为关闭pipefd[0]从pipefd[1]接收,父进程改为关闭pipefd[1]从pipefd[0]发送,测试结果为发送和接收均失败,提示“BAD FILE DESCRIPTOR”。说明了在创建管道时管道对于读写接口已经有了严格的定义,pipefd[0]为读端,pipefd[1]为写端,不能随意更改。读端写,写端读都会产生错误。

4)在父进程写入之前关闭子进程读端,在gdb调试情况下父进程写入时产生“Program received signal SIGPIPE, Broken pipe.”,管道破裂。

5)无名管道只能用于具有亲缘关系的父子进程间通信。

6)管道是半双工的,因此要达到全双工通信创建管道是必须创建两条。


有名管道测试代码:

#include 
#include 
#include 
#include 
#include 
#include 

#define FIFO_SERV "/home/jimmy/code/ipc/fifo_serv" //管道名,是一个文件
#define MAX 4096

int main(int argc, char *argv[]) {
	int pipewrfd, piperdfd, sendBytes, recvBytes;
	unsigned char sendBuf[MAX], recvBuf[MAX];
	pid_t pid;

	memset(sendBuf, 0x00, MAX);
	memset(recvBuf, 0x00, MAX);

	if((pid = fork()) == 0) { //子进程
		if((piperdfd = open(FIFO_SERV,O_RDONLY,0)) == -1) { //只读方式打开有名管道
			perror("child open FIFO_SERV");
			exit(1);	
		}

		recvBytes = read(piperdfd, recvBuf, MAX);
		if(recvBytes == -1 && errno == EAGAIN)
				printf("no data avlaible\n");
		printf("recv %d Bytes data:%s\n", recvBytes, recvBuf);
		pause();
		unlink(FIFO_SERV);
	}
	else if(pid > 0) {
		if((mkfifo(FIFO_SERV, O_CREAT|O_EXCL|0777) < 0) && (errno != EEXIST)) //创建有名管道
			printf("cannot create fifoserver\n");

   		if((pipewrfd = open(FIFO_SERV, O_WRONLY,0)) == -1) { //只写方式打开有名管道
			perror("father open FIFO_SERV");
			exit(1);
		}
		strcpy(sendBuf, "Name pipe test!");
		sendBytes = write(pipewrfd, sendBuf, MAX); //向管道写入
		if(sendBytes == -1 && errno == EAGAIN)
			printf("write to fifo error; try later\n");
	}
}

运行结果:

jimmy@MyPet:~/code/ipc$ gcc -g -o fifo fifo.c
jimmy@MyPet:~/code/ipc$ ./fifo 
recv 4096 Bytes data:Name pipe test!

非正常测试结果及总结:

1)管道名错误,无法创建有名管道

2)注释掉子进程中的接收部分,测试结果为父进程依然能向管道发送数据。

3)注释掉父进程中的发送部分,测试结果为子进程依然能从管道接收数据,recvBytes = 0。

4)有名管道可以看作无名管道的升级版,克服了只能在亲缘关系进程间使用的门槛。

5)由于需要通过文件操作,在通信速度上不具有优势


2/信号(Signal)

信号通信测试代码:

#include 
#include 
#include 
#include 

void op(int, siginfo_t*, void*);

int main(int argc,char**argv) {
	struct sigaction act;
	int sig;
	pid_t fpid, spid;		
	
	if((spid =fork()) == 0) {
		int signum;
		union sigval mysigval;
		signum = 5; //信号值取5
		fpid = getppid(); //获取父进程ID
		mysigval.sival_int = 8; // 用于测试

		if(sigqueue(fpid, signum, mysigval) == -1) //子进程发送信号
			printf("send error\n");
		sleep(2);
	}

	else if(spid > 0) {
		struct sigaction act;
	 	int sig = 5;
	
		sigemptyset(&act.sa_mask);
		act.sa_sigaction=op;
		act.sa_flags=SA_SIGINFO;
		if(sigaction(sig, &act, NULL) < 0) //父进程指定对于信号的处理
			printf("install sigal error\n");
		sleep(2);
	}
}

void op(int signum, siginfo_t *info, void *myact) { // 信号处理函数
	printf("the int value is %d \n", info->si_int);
}

运行结果:

jimmy@MyPet:~/code/ipc$ gcc -g -o ipc ipc.c 
jimmy@MyPet:~/code/ipc$ ./ipc
the int value is 8 

非正常测试结果及总结

1)将子进程中的signum改为与父进程中的sig值不一致,不能进行信号通信。

2)信号通信是惟一的进程间异步通信模式。

3)可以在终端输入kill -l查看系统支持的信号。其中前半部分(<=32)是不可靠信号,后半部分是可靠信号。

4)进程对于实时信号的默认处理方式都是终止进程。


3/报文(Message)队列(消息队列)

消息队列测试代码:

#include 
#include 
#include 
#include 

void msg_stat(int,struct msqid_ds );

int main() {
	int gflags,sflags,rflags;
	pid_t pid;
	key_t key;
	int msgid;
	int reval;
	struct msgsbuf {
        int mtype;
        char mtext[1];
    } msg_sbuf;
	struct msgmbuf {
	    int mtype;
    	char mtext[10];
    } msg_rbuf;
	struct msqid_ds msg_ginfo,msg_sinfo;
	char* msgpath = "./msgqueue";

	key = ftok(msgpath, 'a'); // 获取打开消息队列的key
	gflags = IPC_CREAT;
	msgid = msgget(key , gflags); // 打开消息队列,返回消息队列ID
	if(msgid == -1) {
	    printf("msg create error\n");
    	return -1;
	}
	msg_stat(msgid, msg_ginfo);//输出消息队列属性

	if((pid = fork()) == 0) {
		sflags = IPC_NOWAIT;
		msg_sbuf.mtype = 10;
		msg_sbuf.mtext[0] = 'a';
		if((reval = msgsnd(msgid, &msg_sbuf, sizeof(msg_sbuf.mtext), sflags)) == -1) //向消息队列发送消息
		    printf("message send error\n");
		msg_stat(msgid, msg_ginfo);//发送一个消息后,输出消息队列属性
	}
	
	else if(pid > 0) {
		sleep(1);
		rflags = IPC_NOWAIT | MSG_NOERROR;
		if((reval = msgrcv(msgid, &msg_rbuf, 4, 10, rflags)) == -1) // 从消息队列读取消息
    		printf("read msg error\n");
		else
    		printf("\nread from msg queue %d bytes\n",reval);
		msg_stat(msgid, msg_ginfo);//从消息队列中读出消息后,输出消息队列属性
	
		if((reval = msgctl(msgid, IPC_RMID, NULL)) == -1) { //删除消息队列
		    printf("unlink msg queue error\n");
		    return -1;
		}
	}

	return 0;
}

void msg_stat(int msgid, struct msqid_ds msg_info) {
	int reval;
	sleep(1);
	reval = msgctl(msgid, IPC_STAT, &msg_info); // 消息队列状态存入msg_info
	if(reval == -1) {
	    printf("get msg info error\n");
	    return;
	}
	printf("\n");
	printf("Current number of bytes on queue: %d\n", msg_info.msg_cbytes);
	printf("Number of messages in queue : %d\n", msg_info.msg_qnum);
	printf("Max number of bytes on queue : %d\n", msg_info.msg_qbytes);
	printf("Last send pid : %d time : %s", msg_info.msg_lspid, ctime(&(msg_info.msg_stime)));
	printf("Last receive pid : %d time: %s", msg_info.msg_lrpid, ctime(&(msg_info.msg_rtime)));
	printf("Last change time : %s", ctime(&(msg_info.msg_ctime)));
	printf("Msg uid : %d\n", msg_info.msg_perm.uid);
	printf("Msg gid : %d\n", msg_info.msg_perm.gid);
}

运行结果:

jimmy@MyPet:~/code/ipc$ gcc -w -o ipc ipc.c
jimmy@MyPet:~/code/ipc$ sudo ./ipc

Current number of bytes on queue: 0
Number of messages in queue : 0
Max number of bytes on queue : 16384
Last send pid : 0 time : Thu Jan  1 07:00:00 1970
Last receive pid : 0 time: Thu Jan  1 07:00:00 1970
Last change time : Thu Mar 22 10:56:53 2012
Msg uid : 0
Msg gid : 0

read from msg queue 1 bytes

Current number of bytes on queue: 0
Number of messages in queue : 0
Max number of bytes on queue : 16384
Last send pid : 2691 time : Thu Mar 22 10:56:54 2012
Last receive pid : 2690 time: Thu Mar 22 10:56:55 2012
Last change time : Thu Mar 22 10:56:53 2012
Msg uid : 0
Msg gid : 0

Current number of bytes on queue: 0
Number of messages in queue : 0
Max number of bytes on queue : 16384
Last send pid : 2691 time : Thu Mar 22 10:56:54 2012
Last receive pid : 2690 time: Thu Mar 22 10:56:55 2012
Last change time : Thu Mar 22 10:56:53 2012
Msg uid : 0
Msg gid : 0
jimmy@MyPet:~/code/ipc$ 


非正常测试结果及总结:

1)设置gflags = IPC_CREAT | IPC_EXCL,在第二次运行代码时将提示,msg create error。已存在是不重建不覆盖。

2)不使用sudo权限运行代码生成的可执行文件,所有读写信号都将失败。

3)信号是一种强大完善的系统机制,直接应用于进程通信带来许多方便。

4)异步模式是进程不用加入实时监听代码,减少系统开销。

5)对于信号的读写都需要较高的权限。


4/共享内存

测试代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 

typedef struct {
	char name[4];
	int age;
} people;

int main(int argc, char** argv) {
	people *p_map;
	char temp;
	pid_t pid;
	p_map = (people*)mmap(NULL, sizeof(people), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); // 由于是父子进程通信,因此第五个参数为-1,表示使用特殊文件匿名映射共享内存

	if((pid = fork()) == 0) {
		sleep(1); //等待父进程写入完成
    	printf("Child read: the age is %d\n", (*(p_map)).age); // 子进程读出共享内存里面的值
    	
		(*p_map).age = 21; //改变共享内存中的值
    	munmap(p_map, sizeof(people)); // 解除映射
    	exit(0);
	}
	else if(pid > 0) {
		temp = 'a';
    	memcpy((*(p_map)).name, &temp, 2); // 改变共享内存中的值
    	(*(p_map)).age = 20;
  		
		sleep(2); //等待子进程写入完成
  		printf("Parent read: the age is %d\n", (*p_map).age); // 父进程读出共享内存里面的值
  		munmap(p_map, sizeof(people)); //  接触映射
	}
}

运行结果:

jimmy@MyPet:~/code/ipc$ gcc -o ipc ipc.c 
jimmy@MyPet:~/code/ipc$ ./ipc
Child read: the age is 20
Parent read: the age is 21
jimmy@MyPet:~/code/ipc$

非正常测试结果及总结:

1)去掉创建共享内存中的PROT_READ参数,程序能够正常运行,这是因为没有直接对内存进行读(read)操作。

2)去掉创建共享内存中的PROT_WRITE参数,程序出现Segmentation fault错误,不能改变共享内存的值。

3)亲缘关系通信才能使用注释中提到的-1参数,否则需事先open()一个非匿名。以open()返回值替换-1。

4)由于共享内存只需进行两次操作完成读写(管道需要四次),因此具有速度快的特点。


5/信号量(semaphore)

信号量进程间通信这部分看得云里雾里,没有实现进程间通信,这里给出一些参考代码:

#include 
#include 
#include 
#include 
#include 

#define SEM_PATH "/home/jimmy/mysem"
#define max_tries 3 
int semid;

int main() {
	int flag1,	flag2,	key, i, init_ok, tmperrno;
	struct semid_ds sem_info;
	struct seminfo sem_info2;
	union semun arg;       //union semun: 请参考附录2
	struct sembuf askfor_res, free_res;

	flag1 = IPC_CREAT|IPC_EXCL|00666;
	flag2 = IPC_CREAT|00666;
	key=ftok(SEM_PATH,'a');
	init_ok = 0;
	if((semid = semget(key,1,flag1)) < 0) { // 创建一个只包含一个信号灯的信号灯集
  		tmperrno = errno;
  		perror("semget");
		if(tmperrno == EEXIST) {
		    semid = semget(key,1,flag2);//flag2 只包含了IPC_CREAT标志, semget里的第二个参数必须与原来的信号灯数目一致
		    arg.buf = &sem_info;
    		for(i = 0; isem_otime != 0) { 
						i = max_tries;  
						init_ok = 1;
					}
        			else sleep(1);  
      			}
			}
    		if(!init_ok) { // 创建信号灯的进程对信号灯进行初始化
				arg.val = 1;
    			if(semctl(semid,0,SETVAL,arg) == -1) 
					perror("semctl setval error");
			} 
		}
  		else {
			perror("semget error, process exit");
			exit(1);
		}
	}
	else { //semid>=0, 初始化 
		arg.val	= 1;
		if(semctl(semid, 0, SETVAL, arg) == -1)
		    perror("semctl setval error");
	}
	arg.buf=&sem_info;
	if(semctl(semid, 0, IPC_STAT, arg )== -1) // 获取信号灯信息
		perror("semctl IPC STAT");    
	printf("owner's uid is %d\n", arg.buf->sem_perm.uid);
	printf("owner's gid is %d\n", arg.buf->sem_perm.gid);
	printf("creater's uid is %d\n", arg.buf->sem_perm.cuid);
	printf("creater's gid is %d\n", arg.buf->sem_perm.cgid);
	arg.__buf = &sem_info2;
	if(semctl(semid, 0, IPC_INFO, arg) == -1)
	    perror("semctl IPC_INFO");
	printf("the number of entries in semaphore map is %d \n",  arg.__buf->semmap);
	printf("max number of semaphore identifiers is %d \n",    arg.__buf->semmni);
	printf("mas number of semaphores in system is %d \n",   arg.__buf->semmns);
	printf("the number of undo structures system wide is %d \n",  arg.__buf->semmnu);
	printf("max number of semaphores per semid is %d \n",   arg.__buf->semmsl);
	printf("max number of ops per semop call is %d \n",  arg.__buf->semopm);
	printf("max number of undo entries per process is %d \n",  arg.__buf->semume);
	printf("the sizeof of struct sem_undo is %d \n",  arg.__buf->semusz);
	printf("the maximum semaphore value is %d \n",  arg.__buf->semvmx);
  
	askfor_res.sem_num = 0;
	askfor_res.sem_op = -1;
	askfor_res.sem_flg = SEM_UNDO;    
        if(semop(semid, &askfor_res, 1) == -1) //申请资源 
		perror("semop error");

	sleep(1); // 此处可以添加操作代码 

	free_res.sem_num = 0;
	free_res.sem_op = 1;
	free_res.sem_flg = SEM_UNDO;
	if(semop(semid, &free_res, 1) == -1) // 释放资源
    	if(errno==EIDRM)
    		printf("the semaphore set was removed\n");
}

运行结果:

jimmy@MyPet:~/code/ipc$ gcc -o ipc ipc.c 
jimmy@MyPet:~/code/ipc$ ./ipc
semget: File exists
owner's uid is 1000
owner's gid is 0
creater's uid is 1000
creater's gid is 0
the number of entries in semaphore map is 32000 
max number of semaphore identifiers is 128 
mas number of semaphores in system is 32000 
the number of undo structures system wide is 32000 
max number of semaphores per semid is 250 
max number of ops per semop call is 32 
max number of undo entries per process is 32 
the sizeof of struct sem_undo is 20 
the maximum semaphore value is 32767 
jimmy@MyPet:~/code/ipc$


6/套接口(Socket)

测试代码如下:

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define MAX_DATA_SIZE 4096
#define SERVPORT 1234
#define BACKLOG 20

int main() {
	pid_t pid;
	
	if((pid = fork()) == 0) {
		sleep(1);
		int sockfd, sendBytes,recvBytes;
		char sendBuf[MAX_DATA_SIZE],recvBuf[MAX_DATA_SIZE];
		struct hostent *host;
		struct sockaddr_in servAddr;

		if((host = gethostbyname("127.0.0.1")) == NULL) {
			perror("fail to get host by name");
			exit(1);
		}

		if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
			perror("fail to establish a socket");
			exit(1);
		}
	
		servAddr.sin_family = AF_INET;
		servAddr.sin_port = htons(SERVPORT);
		servAddr.sin_addr = *((struct in_addr *)host -> h_addr);
		bzero(&(servAddr.sin_zero), 8);

		if(connect(sockfd, (struct sockaddr *)&servAddr, sizeof(struct sockaddr_in)) == -1) {
			perror("fail to connect the socket");
			exit(1);
		}

		memset(sendBuf, 0x00, MAX_DATA_SIZE);
		if((recvBytes = recv(sockfd, recvBuf, MAX_DATA_SIZE, 0)) == -1) {
			perror("fail to receive datas");
			exit(1);
		}
		printf("Child:parent says-- %s\n", recvBuf);
		close(sockfd);
	}
	
	else if(pid > 0) {
		struct sockaddr_in serverSockaddr, clientSockaddr;
		int sinSize, recvBytes, sendBytes;
		fd_set readfd;
		fd_set writefd;
		int sockfd, clientfd;
		char sendBuf[MAX_DATA_SIZE], recvBuf[MAX_DATA_SIZE];

		if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
			perror("fail to establish a socket");
			exit(1);
		}

		serverSockaddr.sin_family = AF_INET;
		serverSockaddr.sin_port = htons(SERVPORT);
		serverSockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
		bzero(&(serverSockaddr.sin_zero), 8);
	
		int on = 1; 
		setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
	
		if(bind(sockfd, (struct sockaddr *)&serverSockaddr, sizeof(struct sockaddr))== -1) {
			perror("fail to bind");
			exit(1);
		}
	
		if(listen(sockfd, BACKLOG) == -1) {
			perror("fail to listen");
			exit(1);
		}
	
		if((clientfd = accept(sockfd, (struct sockaddr *)&clientSockaddr, &sinSize)) == -1) {
			perror("fail to accept");
			exit(1);
		}
				
		memcpy(sendBuf, "I'm parent!", 12);
		if((sendBytes = send(clientfd, sendBuf, strlen(sendBuf), 0)) != strlen(sendBuf)) {
			perror("fail to send datas");
			exit(1);
		}
		close(sockfd);
	}

	return 0;
}


运行结果:

jimmy@MyPet:~/code/ipc$ gcc -o ipc ipc.c 
jimmy@MyPet:~/code/ipc$ ./ipc
Child:parent says-- I'm parent!
jimmy@MyPet:~/code/ipc$

非正常测试结果及总结:

关于socket相信是很多人最为熟悉的通信方式,以上代码是对我之前的博文http://blog.csdn.net/jmy5945hh/article/details/7286933稍作修改而得。需要参阅相关注释请转到那个连接。socket通信可能是这么多种通信方式中最为麻烦的,毕竟同机父子进程通信对于socket而言是大材小用了。


代码多为收集整理,博主狗尾续貂而已。

感谢google,感谢http://www.ibm.com/developerworks/。

你可能感兴趣的:(Linux,C语言,linux,semaphore,struct,socket,测试,gcc)