Linux进程间通信与同步

半双工管道

管道是一种把两个进程之间的标准输入和标准输出链接起来的机制。
由于管道仅仅是把某个进程的输出和另外一个进程的输入相连接的单项通信的方法,故称之为“半双工”;
由于进程A和进程B都能够访问管道的两个描述符,因此管道创建完毕后要设置在各个进程中的方向,希望数据向那个方向传输。这需要做好规划。

创建管道的函数为
#include <unistd.h>
int pipe(int filedes[2]);

只建立管道看起来没什么用处,要使管道有切实的用处,需要与进程的创建结合起来,利用两个管道在父子进程间通信。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>

int main(void)
{
	int result = -1;
	/* 文件描述符,字符个数 */
	int fd[2], nbytes;
	/* PID值 */
	pid_t pid;
	/* 文件描述符1用于写
	 文件描述符0用于读
	*/
	int * write_fd = &fd[1];
	int * read_fd = &fd[0];
	/* 缓冲区 */
	char buf[1024];
	char  string[] = "hello,linux.";
	

	/* 创建管道 */
	result = pipe(fd);
	if(result == -1)
	{
		printf("创建管道失败\n");
		return -1;
	}


	pid = fork();
	if(-1 == pid)
	{
		printf("fork进程失败\n");
		return -1;
	}

	if(0 == pid)
	{
		close(*read_fd);
		result = write(*write_fd, string, strlen(string));
		if(0 < result)
		{
			printf("write %d bytes.\n", result);
		}
		close(*write_fd);
	}
	else if(fd > 0)
	{
		close(*write_fd);
		read(*read_fd, buf, 1024);
		printf("recv: %s\n", buf);
		close(*read_fd);
	}

	return 0;
}

命名管道

命名管道的工作方式与普通的管道非常相似,但也有明显的一些差别
  • 在文件系统中命名管道是以设备特殊文件的形式存在的
  • 不同的进程可以通过命名管道共享数据
创建命名管道
  • 直接用Shell完成
mkfifo /ipc/namedfifo
  • 使用mkfifo函数
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char * pathname, mode_t mode);

对于命名管道FIFO来说,IO操作与普通的管道IO是一致的,二者之间存在一个主要的差别。
在FIFO中,必须使用一个open函数来显示的建立连接到管道的通道。
一般来说,FIFO总是处于阻塞状态。
  • 当使用O_NONBLOCK 旗标时, 打开FIFO 文件来读取的操作会立刻返回, 但是若还没有其他进程打开FIFO 文件来读取, 则写入的操作会返回ENXIO 错误代码.
  • 没有使用O_NONBLOCK 旗标时, 打开FIFO 来读取的操作会等到其他进程打开FIFO 文件来写入才正常返回. 同样地, 打开FIFO 文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回.
返回值:若成功则返回0, 否则返回-1, 错误原因存于errno 中.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

#define BUF_SIZ (1024)
#define FIFO_NAME "/tmp/fifo"

int main(void)
{

	char buf[BUF_SIZ];/* buffer */
	int fd; /* file descriptor */	
	unlink(FIFO_NAME);

	if(0 == mkfifo(FIFO_NAME, 0777))
	{
		printf("create fifo successfully.\n");
	}
	else
	{
		printf("create fifo failed.\n");
		return -1;
	}

	
	if(fork() > 0)
	{	
		fd = open(FIFO_NAME, O_WRONLY);
		if(fd > 0)
		{
			printf("Process %d open %s successfully.\n",
				getpid(), FIFO_NAME);
			write(fd, "hello,world", strlen("hello,world"));
		}
	}
	else
	{
		fd = open(FIFO_NAME, O_RDONLY);
		if(fd > 0)
		{
			printf("Process %d open %s successfully.\n",
				getpid(), FIFO_NAME);
			read(fd, buf, BUF_SIZ);
			printf("recv:%s\n", buf);
		}
	}

	close(fd);
}

消息队列

消息队列是内核地址空间中的内部链表,通过Linux内核在各个进程之间传递内容。消息顺序的发送到消息队列中,并以几种不同的方式从队列中获取,每个消息队列可以用IPC标识符唯一的标识。内核中的消息队列是通过IPC标识符来区别的,不同的消息队列之间是独立的。每个消息队列中的消息,又构成一个独立的链表。

消息缓冲区结构

常用的结构是msgbuf结构,程序员可以以这个结构为模板定义自己的消息结构。
struct msgbuf
  {
    long int mtype;		/* type of received/sent message */
    char mtext[1];		/* text of the message */
  };

结构msgid_ds

IPC对象分为3类,每一类都有一个内部数据结构,该数据结构是由内核进行维护的。对于消息队列而言,它的内部结构是msq_ds.
struct msqid_ds {
	struct ipc_perm msg_perm;
	struct msg *msg_first;		/* first message on queue,unused  */
	struct msg *msg_last;		/* last message in queue,unused */
	__kernel_time_t msg_stime;	/* last msgsnd time */
	__kernel_time_t msg_rtime;	/* last msgrcv time */
	__kernel_time_t msg_ctime;	/* last change time */
	unsigned long  msg_lcbytes;	/* Reuse junk fields for 32 bit */
	unsigned long  msg_lqbytes;	/* ditto */
	unsigned short msg_cbytes;	/* current number of bytes on queue */
	unsigned short msg_qnum;	/* number of messages in queue */
	unsigned short msg_qbytes;	/* max number of bytes on queue */
	__kernel_ipc_pid_t msg_lspid;	/* pid of last msgsnd */
	__kernel_ipc_pid_t msg_lrpid;	/* last receive pid */
};

结构ipc_perm

内核把IPC对象的许可权限信息放在ipc_perm类型的结构中。
/* Obsolete, used only for backwards compatibility and libc5 compiles */
struct ipc_perm
{
	__kernel_key_t	key;/* 用于区分消息队列 */
	__kernel_uid_t	uid;/* 消息队列用户的ID号 */
	__kernel_gid_t	gid;/* 消息队列用户组的ID号 */
	__kernel_uid_t	cuid;/* 消息队列创建者的UID号 */
	__kernel_gid_t	cgid;
	__kernel_mode_t	mode; /* 权限,用户控制读写,0666,可以对消息进行读写操作 */
	unsigned short	seq;
};

键值构建ftok函数

ftok函数将路径名和项目标示符转变为一个系统V的IPC键值。其原型如下。
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char * path_name, int proj_id);
其中path_name必须是已经存在的目录,而proj_id则是一个8位的值,通常用a、b表示。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/ipc.h>

#define BUF_SZ (10)
struct msgmbuf
{
	int mtype;
	char mtext[BUF_SZ];
};

int main(void)
{

	int ret = 1;
	int msg_flags , msg_id;
	key_t key;
	struct msqid_ds msg_info;
	struct msgmbuf msg_buf;
	int msg_sflags, msg_rflags;
	char * msg_path = ".";
	
	/* 1.使用ftok创建键值 */
	key = ftok(msg_path, 'b');
	if(key != -1)
		printf("Create Key Successfully.\n");
	else
	{
		printf("Create Key Failed.\n");
		return -1;
	}
	
	/* 2.使用msgget获取消息队列标识符*/
	msg_flags = IPC_CREAT;
	msg_id = msgget(key, msg_flags | 0666);
	if(-1 != msg_id)
	{
		printf("msgget succeed.\n");
	}
	else
	{
		printf("msgget failed.\n");
		return -1;
	}
	
	/* 3.使用msgsnd发送消息 */
	msg_flags = IPC_NOWAIT;
	msg_buf.mtype = 10;
	memcpy(msg_buf.mtext, "hello,linux kernel.", strlen("hello,linux kernel."));
	ret = msgsnd(msg_id, &msg_buf, 10, msg_flags);
	if(ret != -1)
	{
		printf("msgsnd succeed.\n");
	}

	/* 4.使用msgrcv接收消息 */
	msg_flags = IPC_NOWAIT;
	ret = msgrcv(msg_id, &msg_buf, 10, 10, msg_flags);
	if(ret != -1)
	{
		printf("msgrcv succeed.\n");
	}

	/* 5.删除队列 */	
	ret = msgctl(msg_id, IPC_RMID, NULL);
	if(ret != -1)
	{
		printf("msgctl succeed.\n");
	}
}

共享内存

共享内存是在多个进程之间共享存储区域的一种进程间通信的方式,它是在多个进程之间对内存段进行映射的方式实现共享内存的,这是最快捷的方式,因为共享内存没有中间过程。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/msg.h>
#include <sys/stat.h>
#include <sys/ipc.h>
#include <sys/sem.h>

static char msg[] = "hello,shared memory.";

int main()
{
	key_t key;
	int  shmid;
	char i, * shms, * shmc;
	struct semid_ds buf;
	int value = 0;
	char buffer[80];
	pid_t p;

	key = ftok(".", 'a');
	shmid = shmget(key, 1024, IPC_CREAT | 0604);

	p = fork();
	if(p > 0)
	{
		shms = (char *)shmat(shmid, 0, 0);
		memcpy(shms, msg, strlen(msg));		
		shmdt(shms);
	}
	else
	{
		sleep(10);
		shmc = (char*)shmat(shmid, 0, 0);
		printf("shared memory content: %s\n",
			shmc);
		shmdt(shmc);
	}
	return 0;

}

使用本地套接字

#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>


static void 
err_sys(const char * errmsg);

int 
main()
{
	int sockfd[2];
	pid_t pid;

	if(socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd) == -1)
	{
		err_sys("sockpair");
	}

	if((pid = fork()) == -1)
	{
		err_sys("fork");
	}
	else if(pid > 0) /* 父进程 */
	{
		char buf[] = "hello,china", s[BUFSIZ];
		ssize_t n;
		close(sockfd[1]);
		write(sockfd[0], buf, sizeof(buf));
		if((n = read(sockfd[0], s, BUFSIZ)) == -1)
			err_sys("read");
		printf("Recv from process %d: %s\n", pid, 
			s);
		close(sockfd[0]);
		exit(1);
	}
	else if(pid == 0) /* 子进程 */
	{
		char s[BUFSIZ];
		ssize_t n;
		close(sockfd[0]);
		if((n = read(sockfd[1], s, BUFSIZ)) == -1)
			err_sys("read");
		write(sockfd[1], s, n);
		close(sockfd[1]);
		exit(1);
	}
	return 0;
}

void 
err_sys(const char * errmsg)
{
	perror(errmsg);
	exit(1);
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值