Linux操作系统分析(十) - 进程通信之管道与信号量

Linux操作系统分析(10) - 进程通信之管道与信号量

提要

       Linux中进程间的通信机制主要有:管道FIFO,信号量,消息,共享内存区,套接字。程序员在使用中可以根据不同的需求进行选择。


管道

      管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

Linux操作系统分析(十) -  进程通信之管道与信号量

      下面是一个简单的例子.先看两个linux中常用的命令

grep

功能说明:查找文件里符合条件的字符串。

语  法:grep [-abcEFGhHilLnqrsvVwxy][-A<显示列数>][-B<显示列数>][-C<显示列数>][-d<进行动作>][-e<范本样式>][-f<范本文件>][--help][范本样式][文件或目录...]

补充说明:grep 指令用于查找内容包含指定的范本样式的文件,如果发现某文件的内容符合所指定的范本样式,预设grep指令会把含有范本样式的那一列显示出来。若不指定任何文件名称,或是所给予的文件名为“-”,则grep指令会从标准输入设备读取数据。


ls

功能说明:列出目标目录中所有的子目录和文件。

语        法:ls [选项] [目录名]


通过 " | " 可以创建一个管道,把这两个进程链接在一起。

终端运行

ls | grep in

目录下文件名中带in的文件或文件夹会显示出来:

Linux操作系统分析(十) -  进程通信之管道与信号量

在linux的内核中实际上执行的操作如下:

1.调用pipe() 系统调用,假设pipe()返回文件描述符3(管道的读通道)和4(管道的写通道)。

2.两次调用fork() 系统调用。

3.两次调用close() 系统调用来释放文件描述符3和4.


使用管道

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
int pipe_default[2];

int main()
{
	pid_t pid;
	char buffer[32];

	memset(buffer, 0, 32);
	if(pipe(pipe_default) < 0)
	{
		printf("Faild to create pipe.\n");
		return 0;
	}
	//Child process
	if(0 == (pid =fork()))
	{
		close(pipe_default[1]);
		if(read(pipe_default[0], buffer, 32) > 0)
		{
			printf("Receive data from server, %s!\n", buffer);
		}
		close(pipe_default[0]);
	}else     //Parent process
	{
		close(pipe_default[0]);
		if(-1 != write(pipe_default[1], "Fuck", strlen("Fuck")))
		{
			printf("Send data to client,Fuck!\n");
		}
		close(pipe_default[1]);
		waitpid(pid,NULL,0);
	}
		return 1;


}


Linux操作系统分析(十) -  进程通信之管道与信号量


主要解释一下几个相关的函数。

int pipe(int filedes[2]);
filedes[0]用于读出数据,读取时必须关闭写入端,即close(filedes[1]);
filedes[1]用于写入数据,写入时必须关闭读取端,即close(filedes[0])


void *memset(void *s, char ch, size_t n);
将s中前n个字节 (typedef unsigned int size_t)用 ch 替换并返回 s 。


pid_t waitpid(pid_t pid,int * status,int options);

暂时停止目前进程的执行,直到有信号来到或子进程结束。


read和write分别向管道中传送数据。


信号量

       信号量在创建时需要设置一个初始值,表示同时可以有几个任务可以访问该信号量保护的共享资源,初始值为1就变成互斥锁(Mutex),即同时只能有一个任务可以访问信号量保护的共享资源。

  一个任务要想访问共享资源,首先必须得到信号量,获取信号量的操作将把信号量的值减1,若当前信号量的值为负数,表明无法获得信号量,该任务必须挂起在该信号量的等待队列等待该信号量可用;若当前信号量的值为非负数,表示可以获得信号量,因而可以立刻访问被该信号量保护的共享资源。

  当任务访问完被信号量保护的共享资源后,必须释放信号量,释放信号量通过把信号量的值加1实现,如果信号量的值为非正数,表明有任务等待当前信号量,因此它也唤醒所有等待该信号量的任务。

        PV原子操作的具体定义如下:(好好理解,很重要的啊)
● P操作:如果有可用的资源(信号量值>0),则此操作所在的进程占用一个资源(此时信号量值减1,进入临界区代码);如果没有可用的资源(信号量值=0),则此操作所在的进程被阻塞直到系统将资源分配给该进程(进入等待队列,一直等到资源轮到该进程)。
● V操作:如果在该信号量的等待队列中有进程在等待资源,则唤醒一个阻塞进程;如果没有进程等待它,则释放一个资源(即信号量值加1)。

一个实现PV操作的例子。

      

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

#define DELAY_TIME 5

union semun
{
	int val;
	struct semid_ds *buf;
	unsigned short *array;
};

int initsem(int sem_id, int init_value);
int del_sem(int sem_id);
int sem_p(int sem_id);
int sem_v(int sem_id);

int main(void)
{
	pid_t result;
	int sem_id;
	sem_id = semget(ftok(".",'a'),1,0666|IPC_CREAT);
	init_sem(sem_id, 0);
	result = fork();
	if(result == -1)
	{
		perror("fork error!\n");
	}
	else if(result == 0)
	{
		printf("Child process at id:%d, I run first.\n",getpid());
		sleep(DELAY_TIME);
		sem_v(sem_id);
	}
	else
	{
		sem_p(sem_id);
		printf("Parent process at id:%d\n",getpid());
		sem_v(sem_id);
		del_sem(sem_id);
	}
	return 0;
}

int init_sem(int sem_id, int init_value)
{
	union semun sem_union;
	sem_union.val = init_value;
	if(semctl(sem_id, 0, SETVAL, sem_union) == 1)
	{
		perror("Init semaphore!");
		return -1;
	}
	return 0;
}

int del_sem(int sem_id)
{
	union semun sem_union;
	if(semctl(sem_id, 0, IPC_RMID, sem_union) == 1)
	{
		perror("Delete semaphore!");
		return -1;
	}
}

int sem_p(int sem_id)
{
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = -1;
	sem_b.sem_flg = SEM_UNDO;

	if(semop(sem_id, &sem_b, 1) == -1)
	{
		perror("P operation.");
		return -1;
	}
	return 0;
}

int sem_v(int sem_id)
{
	struct sembuf sem_b;
	sem_b.sem_num = 0;
	sem_b.sem_op = 1;
	sem_b.sem_flg = SEM_UNDO;

	if(semop(sem_id, &sem_b, 1) == -1)
	{
		perror("P operation.");
		return -1;
	}
	return 0;
}

Linux操作系统分析(十) -  进程通信之管道与信号量


        在程序中,由于使用了信号量,确保了每次执行子进程运行在父进程之前。



参考

Linux进程间通信(六)---信号量通信之semget()、semctl()、semop()及其基础实验 - http://blog.csdn.net/mybelief321/article/details/9086151

Linux环境进程间通信(一) - http://www.ibm.com/developerworks/cn/linux/l-ipc/part1/