Linux系统编程 Part5 (dup2重定向、进程)

重定向

概念:在 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) :获取进程id
  • pid_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第三卷,重点掌握以下三个)

  1. int execlp(const char *file, const char *arg, …); 借助PATH环境变量找寻待执行程序(函数名的最后一个字母p指的就是PATH,该函数通常用来调用系统程序,如:lsdatecat等命令),示例:execlp("ls", "ls", "-l", "-h", NULL);
    • 参数1:程序名
    • 参数2:argv[0](因此调用函数时会出现两次程序名)
    • 参数3:argv[1]
    • ······:argv[n]
    • 哨兵:NULL(表示参数结束)
  2. int execl(const char *path, const char *arg, …); 自己指定待执行程序路径(其它与execlp函数同理)
  3. 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:waitwaitpid函数一次调用,只能回收一个子进程,如果想回收多个子进程,要用while循环


环境变量

本质上是操作系统中的一组键值对(name = value),用来存储影响进程行为的关键信息(可用命令env输出全部环境变量),重点掌握以下几个:

  • PATH:可执行文件的搜索路径(可用命令echo $PATH输出,以下同理)
  • SHELL:当前shell
  • TERM:当前终端类型
  • LANG:语言和locale,决定了字符编码以及时间、货币等信息的显示格式
  • HOME:当前用户主目录的路径


不准投币喔 👆

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇