OS

操作系统课程2024-14

操作系统上的进程 (fork/execve/exit)

Posted by BY morningcat on May 7, 2024

GPT

  • https://github.com/ggerganov/llama.cpp
  • https://github.com/karpathy/llm.c
  • https://jyywiki.cn/OS/2024/labs/M3.md

AI

  • https://microsoft.github.io/AI-For-Beginners/
  • https://github.com/Microsoft/AI-For-Beginners

第一个进程

Firmware 阶段

  • CPU Reset 后,Firmware 代码开始执行
  • 加载操作系统 操作系统初始化阶段
  • 操作系统扫描系统中的硬件、初始化数据结构……
  • 加载第一个进程 (状态机) 操作系统执行阶段
  • 状态机在 CPU 上执行
  • 允许执行 syscall 进入操作系统代码

人工智能就能帮你实现

  • 我希望用 QEMU 在给定的 Linux 内核完成初始化后,直接执行我自己编写的一个 hello 二进制文件。我应该怎么做?
  • 在这个过程中,发散出很多概念 → 知识体系的快速建立

fork()

pstree

#include <stdio.h>  
#include <unistd.h>  
  
int main() {
    pid_t x = fork();
    pid_t y = fork();
    printf("%d %d\n", x, y);
    return 0;
}
#include <stdio.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <sys/wait.h>  
  
int main() {
    printf("main PID is %d\n", getpid());  

    pid_t pid; // 声明一个 pid_t 类型的变量,用于存储进程 ID  
    pid = fork();
  
    if (pid < 0) { 
        // fork() 失败  
        fprintf(stderr, "Fork Failed");  
        return 1;  
    }  
  
    if (pid == 0) { 
        // 子进程  
        printf("I am the child process. My PID is %d and my parent's PID is %d.\n", getpid(), getppid());  
    } else { 
        // 父进程  
        printf("I am the parent process. My PID is %d and my child's PID is %d.\n", getpid(), pid);  
        // 等待子进程结束  
        int status;  
        waitpid(pid, &status, 0);
    }  
  
    return 0;  
}
/*
main PID is 28049
I am the parent process. My PID is 28049 and my child's PID is 28055.
I am the child process. My PID is 28055 and my parent's PID is 28049.
*/

fork bomb

:(){:|:&};:

测验

#include <stdio.h>  
#include <unistd.h>  
  
int main() {
    for (int i = 0; i < 2; i++) {
        fork();
        printf("Hello\n");
    }
}
// ./a.out
// ./a.out | cat
// ./a.out | wc -l

execve (重置状态机)

int execve(const char *filename,
           char * const argv[], char * const envp[]);

将当前进程重置成一个可执行文件描述状态机的初始状态

int pid = fork();
if (pid == -1) {
    perror("fork"); goto fail;
} else if (pid == 0) {
    // Child
    execve(...);
    perror("execve"); goto fail;
} else {
    // Parent
    ...
}

实践

#include <stdio.h>  
#include <unistd.h>  
  
int main() {
    char *const argv[] = {"/bin/bash", "-c", "env", NULL};
    char *const envp[] = {"HELLO=WORLD", NULL};
    execve(argv[0], argv, envp);
    printf("hello\n");
}

strace ./a.out

_exit() 系统调用

void _exit(int status);

立即摧毁状态机,允许有一个返回值

除了 libc 为我们提供的 exit 函数之外,Linux 提供了两个系统调用:exit 和 exit_group,它们可以在 syscalls(2) 的手册中看到。同时,你也可以在这份手册中看到 Linux 肩负的 “历史包袱”。strace 可以查看应用程序是如何 “退出” 的。

Take-away Messages

因为 “程序 = 状态机”,操作系统上进程 (运行的程序) 管理的 API 很自然地就是状态机的管理。在 UNIX/Linux 世界中,以下三个系统调用创建了整个 “进程世界”,不论是我们常用的 IDE 和浏览器,还是编译时在后台调用的 gcc。其中,fork 对当前状态机状态进行完整复制,execve 将当前状态机状态重置为某个可执行文件描述的状态机,exit: 销毁当前状态机。在对这个概念有了绝对正确且绝对严谨的理解后,操作系统也就显得不那么神秘了。