重定向
概念:在 Linux系统中,重定向是将命令的输入或输出从默认的终端设备(如屏幕)重定向到其他位置(如文件)或从其他位置读取数据。常见的重定向操作包括:
- 输出重定向:将命令的输出写入文件,而不是显示在终端上
- 追加重定向:将输出追加到文件末尾,而不是覆盖
- 输入重定向:从文件中读取输入,而不是从终端键盘输入
这些重定向符号分别是 >
(输出重定向)、>>
(追加重定向)和 <
(输入重定向)
dup/dup2函数
int dup(int oldfd);
文件描述符复制
oldfd
:已有文件描述符- 返回值:新文件描述符
int dup2(int oldfd, int newfd);
文件描述符复制、重定向(新文件描述符也指向原文件,即后面指向前面)
比如:dup2(fd1, STDOUT_FILENO);
//将屏幕输入重定向给fd1所指向的文件,文件描述符如下图所示(以fd1 = 3为例):
fcntl 函数实现 dup:
int fcntl(int fd, int cmd, …)
参3由参2具体命令决定
在这里要实现dup功能,参2为F_DUPFD,参3指定复制体的文件描述符(若已被占用,则返回最小可用的文件描述符)
比如:int fcntl(fd1, F_DUPFD, 5);
进程控制
程序和进程的区别:
- 程序是编译好的二进制文件(死的),只占用磁盘空间,不占用系统资源
- 进程是运行起来的程序(活的),占用内存、cpu等系统资源
虚拟内存和物理内存映射关系
ps:以32位系统举例,进程地址空间大小为2的32次方byte,即0~4GB
MMU(Memory Management Unit,内存管理单元)是计算机系统中的一个硬件组件,位于CPU内,负责虚拟内存和物理内存之间的映射。
PCB进程控制块
每个进程在内核都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task struct结构体,内部成员很多,重点掌握以下几个:
- 进程id:系统中每个进程有唯一的id,即PID
- 进程状态:初始、就绪、挂起、运行、停止
- 进程切换时需要保存和恢复的一些CPU寄存器
- 描述虚拟地址空间的信息
- 描述控制终端的信息
- 当前工作目录位置
- umask掩码
- 文件描述符表
- 和信号相关的信息
- 用户id和组id
- 会话和进程组
- 进程可以使用的资源上限
三个进程函数
pid_t fork(void)
:创建子进程,fork
调用的返回值在父进程和子进程中是不同的(父进程返回子进程pid, 子进程返回0 )pid_t getpid(void)
:获取进程idpid_t getppid(void)
:获取父进程id
※循环创建N个子进程模型
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, char *argv[]) {
int i;
pid_t pid;
for (i = 0; i < 5; i++) {
if (fork() == 0) //循环期间 子进程不fork
break;
}
if (5 == i) { //父进程
sleep(5);
printf("I'm parent \n");
} else { //子进程 从break跳出
sleep(i);
printf("I'm %dth child\n", i+1);
}
return 0;
}
ps:fork
之后父进程和子进程不一定谁先执行,这取决于内核所使用的调度算法(因此在上面代码中使用sleep
函数让父进程先睡眠)
gdb调试父子进程
- 设置父进程调试路径:set follow-fork-mode parent (默认)
- 设置子进程调试路径:set follow-fork-mode child
父子进程共享
- 相同:刚fork后的:data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式
- 不同:进程id、fork返回值、各自的父进程、进程创建时间、定时器、未决信号集
- 共享:文件描述符、mmap映射区
父子进程遵循读时共享、写时复制的原则,因此父子进程并不共享全局变量(当然如果都只是读的话也可以理解为共享)
exec函数族
指定进程执行某一程序,exec
函数一旦调用成功就执行新的程序,不会返回,只有调用失败才返回 -1(exec
函数族都是库函数,在man page第三卷,重点掌握以下三个)
int execlp(const char *file, const char *arg, …);
借助PATH环境变量找寻待执行程序(函数名的最后一个字母p指的就是PATH,该函数通常用来调用系统程序,如:ls
、date
、cat
等命令),示例:execlp("ls", "ls", "-l", "-h", NULL);
- 参数1:程序名
- 参数2:argv[0](因此调用函数时会出现两次程序名)
- 参数3:argv[1]
- ······:argv[n]
- 哨兵:
NULL
(表示参数结束)
int execl(const char *path, const char *arg, …);
自己指定待执行程序路径(其它与execlp
函数同理)int execvp(const char *file, const char *argv[]);
提前把变参封装为字符指针数组(其它与execlp
函数同理),示例:char *argv[] = {"ls", "-l", "-h", NULL}; execvp("ls", argv);
回收子进程
子进程的回收一般由父进程来完成,当子进程终止时,会关闭所有文件描述符,释放在用户空间分配的内存,但会将PCB进程控制块残留在内核中,父进程有义务获取这些信息,然后彻底清除掉这个进程
孤儿进程
父进程先于子进程结束,则子进程成为孤儿进程,此时子进程新的父进程为init进程,称为init进程领养孤儿进程
僵尸进程
进程终止,但父进程尚未回收,子进程残留资源(PCB)存放在内核中,变成僵尸进程(理论上来说,每个进程都会经历僵尸态,因为进程死亡和父进程回收不可能是同步的)
ps:kill
命令只能终止活着的进程,因此无法用kill
命令杀死僵尸进程,一般使用kill
命令来杀死僵尸进程的父进程,这时init进程会来领养僵尸进程,发现僵尸进程的身份后,init进程就会将它回收
wait函数
父进程调用wait
函数可以回收子进程终止信息,该函数有三个功能:
- 阻塞等待子进程退出(即一直等着子进程死)
- 回收子进程残留资源
- 获取子进程结束状态(退出原因)
pid_t wait(int *status)
;
- 参数:回收进程的状态(传出参数) ,如果不关心进程的状态,就设置为NULL
- 返回值:
- 成功: 成功回收的子进程pid
- 失败: -1, errno
传出参数int *status
:
- 获取子进程正常终止值:
WIFEXITED(status)
—》为真—》调用WEXITSTATUS(status)
—》得到子进程退出值(即wait if exited和wait exit status) - 获取导致子进程异常终止信号:
WIFSIGNALED(status)
—》为真—》调用WTERMSIG(status)
—》得到 导致子进程异常终止的信号编号(即wait if signaled和wait termination signal)
完整代码:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/wait.h>
int main(void) {
int status;
pid_t pid, wpid;
pid = fork();
if(pid == 0) { //返回值为0,说明是子进程
printf("i am child, my id is %d, going to sleep 10s\n",getpid()); //获取子进程实际pid
sleep(10);
printf("child die\n");
return 5;
} else if(pid > 0) {
wpid = wait(&status);
if(wpid == -1) {
perror("wait error");
exit(1);
}
if(WIFEXITED(status)) { //若为真,说明子进程正常终止
printf("child exit with %d\n", WEXITSTATUS(status));
}
if(WIFSIGNALED(status)) { //若为假,说明子进程异常终止
printf("child kill with signal %d\n", WTERMSIG(status));
}
} else {
perror("fork");
return 1;
}
}
waitpid函数
指定某一个进程进行回收(可以设置非阻塞)
pid_t waitpid(pid_t pid, int *status, int options)
参数:
- pid:指定回收的某一个子进程pid
- >0:指定回收的子进程pid
- -1:表示回收任意子进程(相当于
wait
) - 0:表示回收同进程组的子进程
- <-1:表示回收指定进程组里的任意子进程
- status:(传出参数)回收进程的状态
- options:WNOHANG(即wait no hang)指定回收方式为非阻塞,0表示阻塞(默认)
当这样设置waitpid
函数参数时,就相当于wait
函数:waitpid(-1, &status, 0)
== wait(&status)
返回值:
- >0:成功回收的子进程pid
- 0:函数调用时,参3指定了
WNOHANG
,并且没有结束的子进程 - -1:回收失败,errno
ps:wait
、waitpid
函数一次调用,只能回收一个子进程,如果想回收多个子进程,要用while
循环
环境变量
本质上是操作系统中的一组键值对(name = value),用来存储影响进程行为的关键信息(可用命令env
输出全部环境变量),重点掌握以下几个:
- PATH:可执行文件的搜索路径(可用命令
echo $PATH
输出,以下同理) - SHELL:当前shell
- TERM:当前终端类型
- LANG:语言和locale,决定了字符编码以及时间、货币等信息的显示格式
- HOME:当前用户主目录的路径