Linux进程间通信

Linux进程间通信

每一个进程之间具有独立性,因此无法直接通信所以才需要操作系统提供进程间通信方式,实现进程间的通信。

操作系统针对不同的通信场景提供了多种不同的通信方式:数据传输–管道/消息队列;数据共享–共享内存;进程控制–信号量。

1.管道

本质:内核中的一块缓冲区,多个进程访问同一个管道就可以实现通信。

种类:匿名管道/命名管道。

匿名管道

内核中的缓冲区没有具体的标识符,因此只能用于具有亲缘关系的进程间通信。

因为只能通过子进程复制父进程的方式获得到管道的操作句柄,因为父进程创建管道的时候操作系统会返回管道的操作句柄。

匿名管道的创建:

int pipe(int pipedfd[2]);//具有两个int型节点数组的首地址,用于接收管道返回操作句柄

pipefd[0]:用于从管道中读取数据。

pipefd[1]:用于从管道中写入数据。

具体代码实现:
int main()
{
	int pipefd[2] = {-1};
	int ret = pipe(pipefd);
	pid_t pid = fork();
	if(pid == 0)
	{
		char buf[1024] = {0};
		read(pipefd[0], buf, 1023);
		printf("%s", buf);
	}
	else
	{
		char* ptr = "hello world\n";
		write(pipefd[1], ptr, strlen(ptr));
	}
	return 0;
}

管道是一个半双工通信,要不然只能读,要不然只能写(单向传输)。

管道的读写特性

1.若管道中没有数据,则调用read读取数据会阻塞。

2.若管道中数据满了,则调用write写入数据会阻塞;管道是一块缓冲区,并非无限制大。

3.若管道的所有读端pipefd[0]被关闭,则继续调用write会产生异常导致进程退出。

4.若管道的所有写端pipefd[1]被关闭,则继续调用read,read读完管道中的数据后不再阻塞,而是返回0。

命令行中管道符的实现:ps -ef | grep ssh
int main()
{
	int pipefd[2] = {-1};
	if(pipe(pipefd) < 0)
	{
		return -1;
	}
	pid_t ps_pid = fork();
	if(ps_pid == 0)
	{
		dup2(pipefd[1], 1);//将标准输出重定向到管道写入端
		execlp("ps", "ps", "-ef", NULL);
		exit(0);
	}
	pid_t grep_pid = fork();
	if(grep_pid == 0)
	{
		close(pipefd[1]);
		dup2(pipefd[0], 0);//将标准输入重定向到管道读取端
		execlp("grep", "grep", "ssh", NULL);
		exit(0);
	}
	close(pipefd[0]);
	close(pipefd[1]);
	waitpid(ps_pid, NULL, 0);
	waitpid(grep_pid, NULL, 0);
	return 0;
}
命名管道

内核中的缓冲区,这块缓冲区具有标识符,可以用于同一主机上的任意进程间通信。

命名管道的创建:

int mkfifo(char* filename, mode_t mode);

filename:管道文件名称;mode:管道文件权限;成功返回0,失败返回-1。

具体代码实现:

读端:read.c

int main()
{
	umask(0);
	int ret = mkfifo("./test.fifo", 0664);
	if(ret < 0 && errno != EEXIST) 
	{
		return -1;
	}
	int fd = open("./test.fifo", O_RDONLY);
	while(1)
	{
		char buf[1024] = {-1};
		int ret = read(fd, buf, 1023);
		if(ret == 0)
		{
			printf("all writr close");
			return -1;
		}
		printf("read buf:%s\n", buf);
	}
	close(fd);
	return 0;
}

写端:write.c

int main()
{
	umask(0);
	int ret = mkfifo("./test.fifo", 0664);
	if(ret < 0 && errno != EEXIST) 
	{
		return -1;
	}
	int fd = open("./test.fifo", O_WRONLY);
	int i = 0;
	while(1)
	{
		char buf[1024] = {-1};
		sprintf(buf, "hello world+%d", i++);
		write(fd, buf, strlen(buf));
		sleep(1);
	}
	close(fd);
	return 0;
}

命名管道的打开特性:

1.若文件以只读打开,则会阻塞,直到文件被以写的方式打开。

2.若文件以只写打开,则会阻塞,直到文件被以读的方式打开。

管道的特性

1.管道是半双通信。

2.管道读写特性。

3.管道生命周期随进程。

4.管道提供字节流传输服务。

5.命名管道额外有打开特性。

6.管道自带同步与互斥。

同步:通过调节判断实现临界资源操作的合理性。

互斥:通过唯一访问实现临界资源操作的安全性。

2.共享内存

在物理内存上开辟一块内存空间,多个进程可以将同一块物理内存空间映射到自己的虚拟地址空间,通过自己的虚拟地址直接访问这块空间,通过这种方式实现数据共享。共享内存是最快的进程间通信方式。

共享内存的操作流程
int shmget(key_t key, size_t size, int shmflg);//创建共享内存

key:内核中共享内存的标识符。

size:共享内存大小。

shmflg:IPC_CREAT–存在则打开,不存在则创建 | IPC_EXCL与IPC_CREAT同时使用,若存在则报错,不存在创建 | mode

返回值:成功返回一个非负整数,失败返回-1。

void* shmat(int shmid, const void* shmaddr, int shmflg);//映射共享内存

shmid:shmget返回的共享内存操作句柄。

shmaddr:共享内存映射在虚拟地址空间中的首地址–通常置NULL。

shmflg:映射成功之后对共享内存可以进行的操作。SHM_RDONLY用于只读/0-默认可读可写。

返回值:返回共享内存映射在虚拟地址空间中的首地址—通过这个首地址进行后续的内存操作。失败返回(void*)-1。

int shmdt(const void* shmaddr)//接触映射关系

shmaddr:映射在虚拟地址空间中的首地址。

返回值:成功返回0,失败返回-1。

int shmctl(int shmid, int cmd, struct shmid_ds* buf);//删除共享内存

shmid:共享内存操作句柄。

cmd:对共享内存想要进行的操作。IPC_RMID-删除共享内存。

buf:用于获取/设置共享内存信息的结构,不使用则置NULL。

返回值:成功返回0,失败返回-1。

具体代码实现

读端:read.c

#define IPC_KEY 0x12345678
int main()
{
	int shm_id = shmget(IPC_KEY, 32, IPC_CREAT|0664);	
	void* shm_start = shmat(shm_id, NULL, 0);
	while(1)
	{
		printf("[%s]\n", shm_start);
		sleep(1);
	}
	shmdt(shm_start);
	shmctl(shm_id, IPC_RMID, NULL);
	return 0;
}

写端:write.c

#define IPC_KEY 0x12345678
int main()
{
	int shm_id = shmget(IPC_KEY, 32, IPC_CREAT|0664);	
	void* shm_start = shmat(shm_id, NULL, 0);
	int i = 0;
	while(1)
	{
		sprintf(shm_start, "%s+%d", "hello", i++);
		sleep(1);
	}
	shmdt(shm_start);
	shmctl(shm_id, IPC_RMID, NULL);
	return 0
}

共享内存删除的时候并不会立即被删除,只是将其状态置为销毁状态,移除标识(不让共享内存继续被其它进程映射链接),然后等到当前共享内存的链接数为0时,才会删除共享内存。

共享内存并没有自带同步与互斥,多个进程进行访问的时候存在安全问题。

共享内存的特性

1.最快的进程间通信方式。

2.生命周期随内核。

3.消息队列

内核中的一个优先级队列,多个进程通过访问同一个队列,进行添加节点或者获取节点实现通信。

创建步骤
int msgget(key_t key, int msgflg);//在内核中创建一个优先级队列
int msgsnd(int msqid, const void* msgp, size_t msgsz, int msgflg);//发送一个消息到消息队列
ssize_t msgcv(int msqid, void* msgp, size_t msgsz, long msgtyp, int msgflg);//从消息队列接收一个消息
int msgctl(int msqid, int cmd, struct msqid_ds* bug);//删除消息队列
消息队列特性

1.自带同步与互斥。

2.生命周期随内核。

4.信号量

适用于实现进程间的同步与互斥的(共享内存本身是不提供同步与互斥的,因此需要使用信号量保护对共享内存的操作)。

本质:一个内核中的计数器+pcb等待队列+使进程等待/唤醒的接口。

同步的实现:通过计数器对资源进行计数;计数大于0表示能获取;计数小于等于0表示不能获取通过等待接口使进程等待加入等待队列;等到有资源的时候唤醒。

互斥的实现:通过保证计数器不会大于1,保证同一时间只有一个进程能够访问资源。

5.进程间通信相关命令
ipcs	//查看进程间通信资源
-m	//查看共享内存
-q	//查看消息队列
-s	//查看信号量
ipcrm	//删除进程间通信资源
-m
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值