Linux中进程间的通信机制主要有:管道FIFO,信号量,消息,共享内存区,套接字。程序员在使用中可以根据不同的需求进行选择。
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。
下面是一个简单的例子.先看两个linux中常用的命令
grep
ls
通过 " | " 可以创建一个管道,把这两个进程链接在一起。
终端运行
ls | grep in
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;
}
主要解释一下几个相关的函数。
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进程间通信(六)---信号量通信之semget()、semctl()、semop()及其基础实验 -
Linux环境进程间通信(一) -