Linux 进程及进程之间的通信机制——管道教程
参考: LInux C编程从初学到精通 电子工业出版社
Linux 进程
Linux 进程简介
Linux是一个多用户多任务的操作系统,多用户是指多个用户可以在同一时间使用同一台计算机系统;多用户是指Linux可以同时执行几个任务,它可以在还未执行完一个任务时又执行另一个任务,操作系统管理着多个用户的请求和多个任务。
Linux系统中所有运行的东西都可以称之为一个进程。每个用户任务、每个系统管理,都可以称之为进程,Linux用分时管理方法使所有的任务共同分享系统资源。
归结起来,进程具有以下4个要素:
- 要有一段程序供该进程运行。
- 进程专用的系统堆栈空间。
- 进程控制块(PCB),在Linux中的具体实现是task\_struct结构。
- 有独立的存储空间。
Linux操作系统包括三种不同类型的进程,每种进程都有自己的特点和属性:
- 交互进程:由一个shell启动的进程。交互进程既可以在前台运行,也可以在后台运行。
- 批处理进程:这种进程和终端没有联系,是一个进程序列
- 监控进程(守护进程):Linux系统启动时启动的进程,并在后台运行。
进程相关的主要函数
fork() 创建一个子进程,若成功,父进程中返回子进程id,子进程中返回0;若出错则返回-1;
#include<sys/types.h>
#include<unistd.h>
pid_t fork(void);
需要注意:
- fork()系统调用的作用是复制一个进程。当一个进程调用它,完成后就会出现两个几乎一摸一样的进程。
- fork()函数被调用一次,但是返回两次。在子进程中返回的是0,在父进程中返回的是子进程id。
getpid() 返回进程的进程id
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
exec函数族
#include<unistd.h>
int execl(const char*pathname, const char *arg, ...);
int execlp(const char*filename, const char *arg, ...);
int execle(const char*pathname, const char*arg, ..., char *const envp[]);
int execv(const char*pathname, char*const argv[]);
int execvp(const char*filename, char*const argv[]);
int execve(const char*pathname, char*const argv[], char*cons envp[]);
6 个函数若成功则无返回值,若出错返回-1;
需要注意:
- 函数名中有字母 ‘l’ 的,其参数个数不定,参数由所调用程序的命令行参数列表组成,最后一个NULL表示结束。
- 函数名中有字母 ‘v’ 的,则是使用一个字符串数组指针argv指向参数列表,这一字符串数组和含有字母 ‘l’ 的函数中的参数列表完全相同,也同样以NULL结束。
- 函数名中有字母 ‘p’ 的,可以自动在环境变量PATH指定的路径中搜索要执行的程序,因此它的第一个参数为filename,表示可执行程序的文件名。而其他函数则需要用户在参数中指定该程序,所以其第一个参数为pathname。
- 函数名中含有 ’e’ 的,比其他函数多含有一个参数envp,这是一个字符串数组指针,用于指定环境变量。调用这样的函数(execle和execve)时,可以由用户自行设定子进程的环境变量,存放在参数的envp所指向的字符串数组中。这个字符串数组也必须由NULL结束。其他函数则是接受当前的环境变量。
简单示例:
#include<stdio.h>
#include<unistd.h>
int main(){
//char *envp[]={"THIS=test",NULL};
//char *argv[]={"echo","this is a test",NULL};
//char *argve[]={"env",NULL};
//execl("/usr/bin/ls","ls","-a",NULL);
//execlp("echo","echo","this is a test",NULL);
//execle("/usr/bin/env","env",NULL,envp);
//execv("/bin/echo",argv);
//execvp("echo",argv);
//execve("/usr/bin/env",argve,envp);
return 0;
}
普遍情况下,如果一个进程想要执行另一个程序,它可以fork一个新进程,然后调用任何一个exec(使用system函数更简单)。
**exit(int) 终止一个进程 **
#include<stdlib.h>
void exit(int status);
在一个进程调用了exit之后,该进程并非马上就消失掉,而是留下一个僵尸进程的数据结构。
wait和waitpid函数:进程等待的系统调用
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
两函数:若成功则返回进程id,若出错则返回-1;
对于waitpid如果设置了WNOHANG选项,返回0表示没有已退出的子进程可收集。
需要注意:
- 系统一旦调用了wait,就立即阻塞自己,由wait自动分析是否当前进程中的某个子进程已经退出,如果让它找到了这样一个已经变成僵尸的子进程,wait就会手机这个子进程的信息,并把它彻底销毁后返回,如果没有找到这样一个子进程,wait就会一直阻塞在这里。
- 从本质上讲waitpid和wait的作用是完全相同的,但waitpid多了两个可由用户控制的参数pid和options,从而为用户变成提供了一种更为灵活的方式。
pid取值及其含义:
options选项:
WNOHANG:即使没有子进程退出,它也会立即返回
WUNTRRACED:极少用到
进程之间的通信
管道
Linux环境下使用pipe函数创建一个匿名管道,其函数原型如下:
#include<unistd.h>
int pipe(int fd[2]);
参数fd[2]是一个长度为2的文件描述符数组,fd[0]是读出端的文件描述符,fd[1]是写入端的文件描述符。
管道的读写
可以使用read和write函数对管道进行读写操作,需要注意的是,管道的两端是固定了任务的,即管道的读写端智能用于读取数据,管道的写入端则只能用于写入数据。如果试图从管道写端读取数据,或者和向管道读端写入数据都将导致错误发生。
父子进程之间通信:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
int fd[2];
char inbuf[256]="this is a test!\n";
char outbuf[256]={0};
pid_t pid;
int len;
if(pipe(fd)<0){
printf("create the pipe failed!\n");
exit(1);
}
if((pid=fork())<0){
printf("fork failed!\n");
exit(1);
}
else if(pid>0){
close(fd[0]);
write(fd[1],inbuf,16);
exit(0);
}
else{
close(fd[1]);
len=read(fd[0],outbuf,256);
if(len<0){
printf("read pipe failed\n");
exit(1);
}
printf("%s",outbuf);
}
return 0;
}
兄弟进程之间通信
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
int main(){
int fd[2];
char inbuf[256]="this is a test!\n";
char outbuf[256]={0};
int len;
pid_t pid;
if(pipe(fd)<0){
printf("create pipe failed!\n");
exit(1);
}
if((pid=fork())<0){
printf("fork failed!\n");
exit(1);
}
else if(pid==0){
close(fd[0]);
write(fd[1],inbuf,16);
exit(0);
}
if((pid=fork())<0){
printf("fork failed!\n");
exit(1);
}
else if(pid==0){
close(fd[1]);
len=read(fd[0],outbuf,256);
printf("%s",outbuf);
}
close(fd[0]);close(fd[1]);
return 0;
}