系统进程控制管理命令
1、 系统进程概念
1、程序是一个包含可执行代码文件,它放在磁盘的介质上,当程序被操作系统装载到内存并分配给他一定的资源后,此时可以被称为进程。``
2、程序是静态的概念,进程是动态的概念。代码片
2、并发
并发,在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但,任一个时刻点上仍只有一个进程在运行。
单道程序设计:所有进程一个一个排对执行。若A阻塞,B只能等待,即使CPU处于空闲状态。而在人机交互时阻塞的出现时必然的。所有这种模型在系统资源利用上及其不合理,在计算机发展历史上存在不久,大部分便被淘汰了。
多道程序设计:在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行。多道程序设计必须有硬件基础作为保证。在多道程序设计模型中,多个进程轮流使用CPU (分时复用CPU资源)。而当下常见CPU为纳秒级,1秒可以执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。
3、进程控制块
每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
* 进程的状态,有就绪、运行、挂起、停止等状态。
* 进程切换时需要保存和恢复的一些CPU寄存器。
* 描述虚拟地址空间的信息。
* 描述控制终端的信息。
* 当前工作目录(Current Working Directory)。
* umask掩码。
* 文件描述符表,包含很多指向file结构体的指针。
* 和信号相关的信息。
* 用户id和组id。
* 会话(Session)和进程组。
* 进程可以使用的资源上限(Resource Limit)
进程状态:进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。
4、常见的环境变量
环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征:
① 字符串(本质) ② 有统一的格式:名=值[:值] ③ 值用来描述进程环境信息。
存储形式:与命令行参数类似。char *[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。
使用形式:与命令行参数类似。
加载位置:与命令行参数类似。位于用户区,高于stack的起始位置。
引入环境变量表:须声明环境变量。extern char ** environ;
PATH:可执行文件的搜索路径。ls命令也是一个程序,执行它不需要提供完整的路径名/bin/ls,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名./a.out,这是因为PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值可以包含多个目录,用:号隔开。在Shell中用echo命令可以查看这个环境变量的值:$ echo $PATH
SHELL:当前Shell,它的值通常是/bin/bash。
TERN:当前终端类型,在图形界面终端下它的值通常是xterm,终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。
LANG:语言和locale,决定了字符编码以及时间、货币等信息的显示格式。
HONE:当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。
fork()函数
创建一个子进程。
pid_t fork(void); 失败返回-1;成功返回:
① 父进程返回子进程的ID(非负)
② ②子进程返回 0
pid_t类型表示进程ID,但为了表示-1,它是有符号整型。(0不是有效进程ID,init最小,为1)
注意返回值,不是fork函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。
创建一个进程代码如下:
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
#include<stdlib.h>
#include<sys/wait.h>
int main()
{
pid_t pid;
int count =0;
pid = fork();
if( pid < 0)
{
printf("error");
}
else if(pid==0)
{
printf("I am child. my pid=%d parent pid=%d\n",getpid(),getppid());
exit(1);
}
else if(pid>0)
{
int temp = 0;
int ret;
sleep(1);
printf("I am parent. my pid=%d\n", getpid());
printf(" wait for child to exit ... \r\n");
ret = wait(&temp);
while( ret!=-1 )
{
printf("child pid = %ld exit code=%d \n", ret, temp);
if(errno == ECHILD)
break;
sleep(1);
ret = wait(&temp);
}
printf(" success for wait for child to exit.\n");
}
return 0;
}
一次fork函数调用可以创建一个子进程,那么创建N个子进程,for(i = 0; i < n; i++) { fork() } 即可。
进程共享
父子相同处: 全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式…
父子不同处: 1.进程ID 2.fork返回值 3.父进程ID 4.进程运行时间 5.闹钟(定时器) 6.未决信号集
getpid函数
获取当前进程ID
pid_t getpid(void);
getppid函数
获取当前进程的父进程ID
pid_t getppid(void);
区分一个函数是“系统函数”还是“库函数”依据:
② 是否访问内核数据结构
③ 是否访问外部硬件资源,二者有任一 → 系统函数;二者均无 → 库函数
getuid函数
获取当前进程实际用户ID
uid_t getuid(void);
获取当前进程有效用户ID
uid_t geteuid(void);
getgid函数
获取当前进程使用用户组ID
gid_t getgid(void);
获取当前进程有效用户组ID
gid_t getegid(void);
孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
僵尸进程
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。
僵尸进程是不能使用 kill 命令清除掉的。因为 kill 命令指示用来终止进程的,而僵尸进程已经终止。杀掉僵尸进程的方法就是 kill 掉他的父进程。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/wait.h>
int main(void) {
pid_t ret = fork();
if (ret == -1) {
perror("fork error");
exit(-1);
} else if (ret == 0) {
// 子进程
printf("child process : i'm going to die.\n");
} else {
// 父进程
while (1) {
printf("father process : pid = %d, and my son pid = %d\n", getpid(), ret);
sleep(2);
}
}
return 0;
}
一个进程在终止的时候会关闭所有文件描述符,释放在用户控件分配的内存,但它的 PCB 还保留着,内核在其中保存了一些信息:如果进程是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。
这个进程的父进程可以调用 wait 或 waitpid 获取这些信息,然后彻底清除掉这个进程。
一个进程的退出状态可以在 shell 中用 $? 查看,因为 shell 是它的父进程,当它终止时 shell 调用 wait 或 waitpid 得到它的退出状态同时彻底清除掉这个进程占用的资源。
当进程终止时,操作系统的隐式回收机制:
1.关闭所有文件描述符;
2.释放用户空间分配的内存。内核的 PCB 仍存在。其中保存该进程的退出状态。(正常终止:退出值;异常终止:终止信号)
wait函数
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
pid_t wait(int *status); 成功:清理掉的子进程ID;失败:-1 (没有子进程)
当进程终止时,操作系统的隐式回收机制会:
1.关闭所有文件描述符
2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断
进程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为非0 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为非0 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
*3. WIFSTOPPED(status) 为非0 → 进程处于暂停状态
WSTOPSIG(status) 如上宏为真,使用此宏 → 取得使进程暂停的那个信号的编号。
WIFCONTINUED(status) 为真 → 进程暂停后已经继续运行
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include<sys/wait.h>
int main(void) {
pid_t ret = fork();
if (ret == -1) {
perror("fork error");
exit(-1);
} else if (ret == 0) {
// 子进程
printf("child process : i'm going to die.\n");
exit(101);
} else {
// 父进程
int status = 0;
pid_t a_ret = wait(&status);
if (a_ret == -1) {
perror("wait error");
exit(-2);
}
if (WIFEXITED(status)) {
printf("child exit with %d\n", WEXITSTATUS(status));
}
}
return 0;
}
waitpid函数
作用同wait,但可指定pid进程清理,可以不阻塞。
pid_t waitpid(pid_t pid, int *status, in options);
成功:返回清理掉的子进程ID;
失败:-1(无子进程)
特殊参数和返回情况:
参数pid:
0 回收指定ID的子进程
-1 回收任意子进程(相当于wait)
0 回收和当前调用waitpid一个组的所有子进程
< -1 回收指定进程组内的任意子进程
返回0:参3为WNOHANG,且子进程正在运行
注意:一次wait或waitpid调用只能清理一个子进程,清 理多个子进程应使用循环
如果所有子进程都正在运行,则调用该函数的进程挂起(因为没有子进程结束,调用该函数的进程阻塞)
如果恰有子进程结束,它的终止状态字等待父进程提取,立即得到该终止状态字并返回,返回值为该字进程的进程号
如果该进程没有子进程,立即返回,返回值为-1
5、进程命令
1、ps命令
功能:ps命令是用来显示系统瞬间的进程信息,它可以显示出在用户输入ps命令是系统的进程及进程的相关信息。
参数:
-e 显示所有进程,环境变量
-f 用树型格式显示进程
-u按用户名和启动时间的顺序来显示进程
-l 长格式输出
-w 宽输出
-a 显示终端上地所有进程,包括其他用户地进程
-r 只显示正在运行地进程
-x 显示没有控制终端地进程(可组合应用)
2、Top命令:
功能:动态监视系统任务的工具,输出结果是连续的
参数:
-b:批处理,不接受命令行的输入
-c:显示完整的命令行,但不就只是命令名
-d:N显示两次刷新时间的间隔
-I:忽略失效过程
-s:保密(安全)模式,禁用一些效互命令。
-S:累积模式输出每个进程的总CPU时间
-i:<时间> 设置间隔时间,禁止显示空闲进程或僵尸进程
-u:<用户名> 指定用户名
-p:<进程号> 指定进程
-n:<次数> 循环显示的次数,然后退出
-q:不经任何延迟就刷新
3、 kill命令
功能:该命令用于向某个进程(通过PID标识)传达一个信号,它于ps命令和jobs命令使用。
Kill命令格式:kill-signal PID,常用signal参数
1:SIGHUP,启动被终止的进程
2:SIGINT,相当于输入ctrl+c,中断一个程序的进行
9:SIGKILL,强制中断一个进程的进行
15:SIGTERM,以正常的结束进程方式来终止进程
17:SIGSTOP,相当于输入ctrl+z,暂停一个进程的进程
4、 killall命令:
使用进程名称杀死进程,Linux 系统中的killall命令用于杀死指定名字的进程(kill processes by name)。我们可以使用kill命令杀死指定进程PID的进程,如果要找到我们需要杀死的进程,我们还需要在之前使用ps等命令再配合grep来查找进 程,而killall把这两个过程合二为一,是一个很好用的命令。
用法:killall[参数][进程名]
参数:
-Z :只杀死拥有scontext 的进程
-p:杀死进程所属的组
-e :要求匹配进程名称
-I :忽略小写
-g :杀死进程组而不是进程
-i :交互模式,杀死进程前先询问用户
-l :列出所有的已知信号名称
-q :不输出警告信息(如果不杀死进程,则不输出任何信息)
-r:使用正规表达式匹配要杀死的进程名称
-s :发送指定的信号(用指定的进程号代替默认信号“SIGTERM”)
-u:杀死指定用户的进程
-v: 报告信号是否成功发送
-w :等待进程死亡
--help :显示帮助信息
--version: 显示版本显示
5、nice命令
功能:nice命令允许在默认优先级的基础上进行增大或减小的方式来运行命令, 调度优先级是内核分配给进程的代表执行先后可能的整数(-20-20)
整数值越小,优先级越高。
格式:nice[参数]<command><arguments…>
Command是系统中任意的可执行文件的名称
-n -adjustment 指定程序运行优先级的调整值。
nice命令可以修改进程的优先级,进而调整进程调度。nice值的范围是[-20, 19], -20表示进程的最高优先级,19表示进程的最低优先级。Linux进程的默认nice值为0。使用nice可调整进程的优先级,这样调度器就会依据进程优先级,为其分配CPU资源。
若nice命令未指定优先级的调整值,则以缺省值10来调整程序运行优先级,就是在命令通常运行优先级的基础之上增加10;
6、renice命令
功能:renice命令是与nice关联的一个命令,由re两个字母就知道可以重新调整进程执行的优先级,可以指定群组或者用户名调整优先级等级,并修改隶属于该群组或者用户的所有程序优先级。等级范围为[-20,19]。同样仅系统管理员可以拉高优先级。nice在进程拉起时调整,renice在进程执行时调整,改变一个正在运行的程序的nice值。
格式:
Renice[参数]
-n :指定程序运行优先级的调整值
7、&命令
功能:在后台运行的命令
例如:[root@host root]#cp -r /usr/*test&
将/usr目录下的所有子目录及文件复制到/root/test目录下的工作放到后台运行
8、进程的挂起和结束
挂起:CTRL+Z
结束:ctrl+c
9、进程的恢复
恢复到前台继续运行(fg)fg[n]
恢复到后台继续运行(bg)bg[n]
查看被挂起的进程:(jobs)
Exec函数族
fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
其实有六种以exec开头的函数,统称exec函数:
int execl(const char *path, const char *arg, ...);// l代表的是list:命令行参数列表
int execlp(const char *file, const char *arg, ...); //p代表的是path:搜索file时的使用的path变量
int execle(const char *path, const char *arg, ..., char *const envp[]); e-environment
int execv(const char *path, char *const argv[]);//v代表的是vector:使用命令行参数数组
int execvp(const char *file, char *const argv[]);
int execve(const char *path, char *const argv[], char *const envp[]);//e代表的是environment:使用环境变量数组
execlp函数 l-list p-PATH
加载一个进程,借助PATH环境变量
argv[]是argc个参数,其中第0个参数是程序的全名,以后的参数
int execlp(const char *file, const char *arg, …);
成功:无返回;失败:-1
参数1:要加载的程序的名字。该函数需要配合PATH环境变量来使用,当PATH中所有目录搜索后没有参数1则出错返回。
该函数通常用来调用系统程序。如:ls、date、cp、cat等命令
``````c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t pid;
pid = fork();
if(pid==-1)
{
perror("error");
exit(1);
}
else if(pid>0)
{
sleep(1);
printf("successful!\n");
}
else{
execlp("ls","ls","-l",NULL);
}
return 0;
}
execl函数
加载一个进程,通过 路径+程序名 来加载。
int execl(const char *path, const char *arg, ...); 成功:无返回;失败:-1
对比execlp,如加载"ls"命令带有-l,-F参数
execlp("ls", "ls", "-l", "-F", NULL); 使用程序名在PATH中搜索。
execl("/bin/ls", "ls", "-l", "-F", NULL); 使用参数1给出的绝对路径搜索。
execvp函数
加载一个进程,使用自定义环境变量env
int execvp(const char *file, const char *argv[]);
变参形式: ①… ② argv[] (main函数也是变参函数,
形式上等同于 int main(int argc, char *argv0, …))
变参终止条件:① NULL结尾 ② 固参指定
execvp与execlp参数形式不同,原理一致。
gdb调试
使用gdb调试的时候,gdb只能跟踪一个进程。可以在fork函数调用之前,通过指令设置gdb调试工具跟踪父进程或者是跟踪子进程。默认跟踪父进程。
set follow-fork-mode child 命令设置gdb在fork之后跟踪子进程。
set follow-fork-mode parent 设置跟踪父进程。
注意,一定要在fork函数调用之前设置才有效。
常见IPC
Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
常用的进程间通信方式有:
① 管道 (使用最简单)
② 信号 (开销最小)
③ 共享映射区 (无血缘关系)
④ 本地套接字 (最稳定)
管道基础
管道是Linux进程间的一种通信方式,两个进程可以通过一个共享内存区域来传递信息,并且管道中的数据只能是单向流动的,也就是说只能有固定的写进程和读进程。
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。有如下特质:
1. 其本质是一个伪文件(实为内核缓冲区)
2. 由两个文件描述符引用,一个表示读端,一个表示写端。
3. 规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道可以分为两种类型:匿名管道和命名管道。
其缺点为:
1、读写数据过程中数据自己读不能自己写。
2、数据一旦被读走,便不在管道中存在,不可反复读取(类似水在水管之中的流动)
3、由于管道采用的半双工通信方式。因此数据只能在一个方向流动。
4、只能在有公共祖先的进程之间使用管道
常见的通信方式有,单工通信、半双工通信、全双工通信。
匿名管道
匿名管道只能在父子进程间通讯。
1、 管道内无数据时,读端会发生阻塞直到有数据可读
2、 管道数据满时,写端会发生阻塞,直到读端开始读取数据
3、 如果写端对应的文件描述符被关闭,read函数返回0,但可以将数据读完
4、 如果读端对应的文件描述符被关闭,在执行write函数时会产生SIGPIPE信号,其默认行为会导致当前进程终止
Pipe函数
创建管道
int pipe(int pipefd[2]); 成功:0;失败:-1,设置errno
管道创建成功以后,创建该管道的进程(父进程)同时掌握着管道的读端和写端。
pipe()函数用于在内核中创建一个管道,该管道一端用于读取管道中的数据,另一端用于将数据写入管道。在创建一个管道后,会获得一对文件描述符,用于读取和写入,然后将参数数组fd中的两个值传递给获取到的两个文件描述符,fd[0]指向管道的读端,fd[1]指向写端。
pipe()函数调用成功,返回值为0;否则返回-1,并且设置了适当的错误返回信息。此函数只是创建了管道,要想从管道中读取数据或者向管道中写入数据,需要使用read()和write()函数来完成。当管道通信结束后,需要使用close()函数关闭管道的读写端。
实现父子进程间通讯
1. 父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
2. 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
3. 父进程关闭管道读端,子进程关闭管道写端。父进程可以向管道中写入数据,子进程将管道中的数据读出。由于管道是利用环形队列实现的,数据从写端流入管道,从读端流出,这样就实现了进程间通信。
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
int main() {
int fd[2];
if (pipe(fd) == -1) {
fprintf(stdout, "Can not open pipe.\n");
exit(1);
}
pid_t pid = fork();
if (pid < 0) {
fprintf(stdout, " failure\n");
} else if (pid == 0) { //子进程
close(fd[0]); //子进程关闭读端
char str[] = "message"; //将"message"传递给父进程
write(fd[1], str, strlen(str));
} else { //父进程
close(fd[1]); //父进程关闭写端
char str[100];
read(fd[0], str, 100);
fprintf(stdout, "Parent: read data from pipeline (%s)", str);
}
return 0;
}
命名管道
命名管道本质上是一个管道文件,它基于文件系统来实现进程间的通信,其读写端进程可以不是父子进程的关系,只需要进程有权限访问该管道文件即可。需要注意的是,命名管道中的数据实际上是存储在内存中,管道文件在文件系统中相当于是一个标记。
其命名规则
在命令行界面通过命令mkfifo filename创建命名管道文件,可以指定其文件名
在程序内部调用mkfifo函数(定义于头文件sys/stat.h中),参数filename为管道文件路径,mode为管道文件读写权限:
例如:int mkfifo(const char *filename, mode_t mode);
命名管道的读写机制和匿名管道相似。
只不过在使用前我们需要调用open函数来打开管道文件,通过其返回的文件描述符来读写管道文件。
Mkfifo函数命名管道
使用mkfifo()函数创建的命名管道文件与前面介绍的管道通信相似,只是它们创建方式不同。访问命名管道文件与访问文件系统中的其他文件一样,都是需要首先打开文件,然后对文件进行读写数据。如果在命名管道文件中读取数据时,并没有其他进程向命名管道文件中写入数据,则会出现进程阻塞状态;如果在写入数据的同时,没有进程从命名管道中读取数据,也会出现进程阻塞状态。
用mkfifo()函数创建一个命名管道,命名管道文件路径
调用open()函数打开该命名管道文件,以读写的方式打开。
调用write()函数向文件写入信息"hello world", 同时调用read()函数读取该文件,输出到终端。
调用close()函数关闭打开的命名管道文件
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#define FIFO "/root/process/hello"
int main()
{
int fd;
int pid;
char r_msg[BUFSIZ];
if((pid = mkfifo(FIFO,0777))==-1) /*创建命名管道*/
{
perror("failed!");
return 1;
}
else
printf("success!\n");
fd = open(FIFO, O_RDWR); /*打开命名管道*/
if(fd == -1)
{
perror("cannot open the FIFO");
return 1;
}
if(write(fd,"hello world", 12) == -1) /*写入消息*/
{
perror("write data error!");
return 1;
}
else
printf("write data success!\n");
if(read(fd, r_msg, BUFSIZ) == -1) /*读取消息*/
{
perror("read error!");
return 1;
}
else
printf("the receive data is: %s\n",r_msg);
close(fd); /*关闭文件*/
return 0;
}
管道的读写
使用管道需要注意以下4种特殊情况(假设都是阻塞I/O操作,没有设置O_NONBLOCK标志):
- 如果所有指向管道写端的文件描述符都关闭了(管道写端引用计数为0),而仍然有进程从管道的读端读数据,那么管道中剩余的数据都被读取后,再次read会返回0,就像读到文件末尾一样。
- 如果有指向管道写端的文件描述符没关闭(管道写端引用计数大于0),而持有管道写端的进程也没有向管道中写数据,这时有进程从管道读端读数据,那么管道中剩余的数据都被读取后,再次read会阻塞,直到管道中有数据可读了才读取数据并返回。
- 如果所有指向管道读端的文件描述符都关闭了(管道读端引用计数为0),这时有进程向管道的写端write,那么该进程会收到信号SIGPIPE,通常会导致进程异常终止。当然也可以对SIGPIPE信号实施捕捉,不终止进程。具体方法信号章节详细介绍。
- 如果有指向管道读端的文件描述符没关闭(管道读端引用计数大于0),而持有管道读端的进程也没有从管道中读数据,这时有进程向管道写端写数据,那么在管道被写满时再次write会阻塞,直到管道中有空位置了才写入数据并返回。
总结:
① 读管道: 1. 管道中有数据,read返回实际读到的字节数。
2. 管道中无数据:
(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
② 写管道: 1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
2. 管道读端没有全部关闭:
(1) 管道已满,write阻塞。
(2) 管道未满,write将数据写入,并返回实际写入的字节数。
284

被折叠的 条评论
为什么被折叠?



