在Linux中有好几种通信机制:如:管道、有名管道、信号量、消息队列、套接字、共享内存等等。今天主要看了管道和有名管道两个,所以下面就记录一下今天所学内容。
前面我们已经知道Linux中各进程之间都有独立的地址空间,即隔离机制,所以一个进程要访问另一个进程的数据几乎是没戏的,所以Linux就制定了一些通信机制,特别是在大型的应用系统中,往往需要多个进程相互协作共同完成一个任务,就需要使用Linux中的编程技术。
管道:它是其中之一。它是一个之存在与内存的特殊文件,并且数据只能单向流动,而且只能用于具有亲缘关系的进程间(即父子进程,兄弟进程),无亲缘关系的进程是不能使用的。如果要使用管道进行全双工通信,那么就要建立两个管道,通过管道通信的两个进程,一个进程向管道写数据,而另一个进程从管道读数据。
管道创建的原型函数为:
int pipe(int fd[2]);
其中fd[2]为用于创建的管道的两端,其中fd[0]为读端,fd[1]为写端,这两端任务是固定的,不能混乱,如果混乱,将会导致错误出现。
管道一旦创建就作为一般的文件来使用,所以对一般文件的操作函数如read(),write()等都适用与管道。
下面我们具体的看一个例子父子进程之间通过管道的通信过程:
点击(此处)折叠或打开
#include
#include
#include
#include
#include
void read_from_pipe(int fd)
{
char buf[100];
read(fd,buf,100);
printf("read from pipe :%s\n",buf);
}
void write_to_pipe(int fd)
{
char *buf="hello pipe!\n";
write(fd,buf,strlen(buf)+1);
}
int main()
{
pid_t pid;
int fd[2];
int stat;
if(pipe(fd)){
printf("pipe failed!\n");
exit(0);
}
pid=fork();
switch(pid){
case -1:
printf("frok failed\n");
exit(0);
case 0:
close(fd[1]);
read_from_pipe(fd[0]);
exit(0);
default:
close(fd[0]);
write_to_pipe(fd[1]);
wait(&stat);
exit(0);
}
return 0;
}
当然上面的例子只是单双工通信方式,如果你想要全双工通信那么就建立两个管道进行全双工通信。这里不再赘述,问题是如果父进程创建一个子进程之后,而子进程不知好歹的去执行了另外一个函数去了,这时候就不能再共享文件描述符了,那该怎么办呢?
别急,凡是总是会有办法的,办法就是可以将子进程中的文件描述符重定向到标准输入,当新程序执行的时候从标准输入获取数据时实际上就是从父进程中获取输入数据,那么dup和dup2函数提供了复制文件描述符的功能,两个函数
均在头文件unistd.h中:原型如下:
int dup(int oldfd);
int dup(int oldfd,int newfd);
这两个函数调用成功时返回一个oldfd文件描述符的副本,失败返回-1,所不同的是dup函数返回的文件描述符是当前可用文件描述符中的最小数值,而fup2函数则可以利用参数newfd制定欲返回的文件描述符。如果参数newfd指定的文件描述符已经打开,系统先将其关闭,然后将oldfd指定的文件描述赋值到该参数,如果newfd等于oldfd,则dup2返回newfd,而不关闭它。
下面我们具体看一个例子:父进程创建子进程后子进程去执行了另一个函数,通过管道给即将执行的程序传递命令行参数。
点击(此处)折叠或打开
#include
#include
#include
#include
#include
#include
int main(int argc,char **argv,char **environ)
{
int fd[2];
pid_t pid;
int stat_val;
if(argc<2){
printf("wrong parameters\n");
exit(0);
}
if(pipe(fd)){
printf("pipe failed\n");
exit(1);
}
pid=fork();
switch(pid){
case -1:
printf("fork failed\n");
exit(1);
case 0:
close(0);
dup(fd[0]);
execve("ctrlprocess",(void *)argv,environ);
exit(0);
default:
close(fd[0]);
write(fd[1],argv[1],strlen(argv[1]));
break;
}
wait(&stat_val);
exit(0);
}
点击(此处)折叠或打开
#include
#include
#include
int main(int argc,char *argv[])
{
int n;
char buf[1024];
while(1){
if((n=read(STDIN_FILENO,buf,1024))>0){
buf[n]='\0';
printf("ctrlprocess receives :%s\n",buf);
if(!strcmp(buf,"exit"))
exit(0);
if(!strcmp(buf,"getpid")){
printf("my pid is %d\n",getpid());
sleep(3);
exit(0);
}
}
}
}编译后首先运行prowrite(运行后处于阻塞状态),打开另一个终端运行程序procread.
开始时把代码中的读管道函数写成这样read(stdin,buf,1024);结果运行的时候什么也出不来,后来网上查了查结果是这种写法:read(STDIN_FILENO,buf,1024);
下面我们看一下有名管道:上面的管道我们很容易看得出它的局限性,那么有名管道就会解决那种问题,即他可以使任意两个进程之间通信。有名管道是一个存在于硬盘上的文件。
有两个函数可以创建有名管道:
int mknod(const char *path,mode_t mod,dev_t dev);
int mkfifo(const char *path,mode_t mode);
有名管道和管道的使用方法相同,只是使用有名管道时必须使用open()函数将其打开。
值得注意的是:调用open()打开有名管道的进程可能会被阻塞,但如果同时用读写方式(O_RDWR)打开,则一定不会阻塞,如果以只读方式打开,则一定会阻塞直到有写的进程打开管道,同样以写的方式打开的时候也会被阻塞,知道有读的进程打开管道,下面我们通过一个具体的例子更深一步的了解有名管道的使用方法:
点击(此处)折叠或打开
#include
#include
#include
#include
#include
#include
#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024
int main()
{
int fd;
char buf[BUF_SIZE]="hello procwrite,i come frome process named procread";
// umask(0);
if(mkfifo(FIFO_NAME,S_IFIFO|0666)==-1){
perror("mkfifo error!");
exit(0);
}
if((fd=open(FIFO_NAME,O_WRONLY))==-1){
perror("open fifo error!");
exit(0);
}
write(fd,buf,strlen(buf)+1);
close(fd);
exit(0);
}
点击(此处)折叠或打开
#include
#include
#include
#include
#include
#include
#define FIFO_NAME "myfifo"
#define BUF_SIZE 1024
int main(void)
{
int fd;
char buf[BUF_SIZE];
// umask(0);
fd=open(FIFO_NAME,O_RDONLY);
read(fd,buf,BUF_SIZE);
printf("read content:%s\n",buf);
exit(0);
}
刚开始由于好奇,当进程退出之后我就看见目录下面生成了一个文件夹myfifo,奇怪的是与其他文件夹的颜色是不同的,我就试图察看它的内容,结果里面是空的,我当时就纳了闷了,明明是一个文件夹,明明写进去东西的,而且另一个进程也从中读出了内容阿,难道是内容被读走了以后就不存在在有名管道中了,但是它不也是一个文件么,写进去的内容怎么会不见呢,难道不是一般的文件?于是上网查了查?结果如下:
有名管道是有名有形的,为了使用这种管道Linux中设立了一个专门的特殊文件系统--管道文件,它存在于文件系统中,任何进程可以在任何时候通过有名管道的路径和文件来访问管道,但是在磁盘上的只是一个节点,而文件的数据则只存在于内存缓冲页面中与普通管道一样。哦,原来是这样,看来它还确实跟普通的文件不一样呢!