大数跨境
0
0

操作系统进程的三个函数

操作系统进程的三个函数 摩尔线程
2024-07-04
0
导读:你好,我是Alan,今天我们来简单介绍一下操作系统关于进程的三个元素,分别是fork,execve, exit
你好,我是Alan,今天我们来简单介绍一下操作系统关于进程的三个元素,分别是fork,execve, exit。

初始化

在我们写完程序之后,经过编译之后,我们有一个指令叫做系统调用指令,这个指令帮助我们将程序交给操作系统执行,然后操作系统联合相关硬件资源交给CPU执行。比如这个程序可能是发送网络数据包,那么操作系统就会将这个网络数据包给发送出去。

用简单的代码可以表示上面这个过程:

def StateMachine():    b = sys_read()      if b == 0:        sys_write('I got a zero')    else:        sys_write('I got a one')
def main():    sys_spawn(StateMachine)

有了上面的认知我们知道操作系统会给我们去调度资源,执行操作,

那么操作系统启动的第一个进程是什么呢? 

Linux对进程采用了一种层次系统,每个进程都依赖于一个父进程。内核启动 init 程序作为第一 个进程,该进程负责进一步的系统初始化操作,并显示登录提示符或图形登录界面(现在使用比较广泛)。因此init是进程树的根,所有进程都直接或间接起源自该进程。
fork函数
有了这个初始化进程之后,我们可以将这个进程给复制,于是fork函数就出现了,fork的语义是复制一份对应的进程上下文,创建一个子进程,我们看下面这段代码:
#include <unistd.h>#include <stdio.h>
int main() { pid_t pid; pid = fork(); if (pid == -1) {       printf("failed");       return 0; } else if (pid == 0) {       printf("process son %d\n", getpid());       sleep(5);       exit(0); } else {       printf("process facther %d\n", getpid());       waitpid(pid, NULL0); }     return 0;}

关于fork函数的返回值也是非常的有意思,主要有下面这三种:

1. 返回值负数:如果 fork() 函数返回一个负值,这表示创建子进程失败了。最常见的原因是系统资源限制,例如可用的内存不足以创建新的进程。在这种情况下,你应该检查错误码 errno 来获取更详细的错误信息。

2. 返回值为0:如果 fork() 函数返回0,这表示它在子进程中被调用。也就是说,执行 fork() 之后的代码将只在子进程中运行。父进程不会执行这个返回值之后的代码。

3. 返回值为正数:如果 fork() 函数返回一个正值,这表示它在父进程中被调用。返回的值是新创建的子进程的进程ID(PID)。父进程可以使用这个PID来对子进程进行进一步的操作,比如使用 `wait()` 或 `waitpid()` 函数等待子进程结束。

但是记这种API比较抽象,我们可以通过一个比较生动的例子来理解这个过程,我们可以将每一个段程序看作是一个状态机或者说是一个整体,每次执行fork的话都会将所有的状态机的信息都给复制一份。

此时我们需要考虑一个问题,那就是如果什么东西都需要复制一份的话,那么在性能上不会造成非常大的损耗么?在linux内核中并不会复制全部的内容而只会复制一份对应的虚拟页表, 这样就建立了关于虚拟地址和物理地址之间的联系,这样技术叫做cow,这个有机会再说😄。

exec函数

有了fork函数之后我门就可以一生二,二生三,三生万物了,接下来我们需要考虑的就是在相应的进程中执行对应的程序, 那么就需要使用一个叫做execve的系统调用。

exec将一个新程序加载到当前进程的内存中并执行, 旧程序的内存页将刷出,其内容将替换 为新的数据。Linux提供的execve系统调用可以用于这个目的。

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

从状态机器角度来理解,将当前运行进程的重置成为一个可执行文件描述状态机的初始化状态

比如当我们编译一个文件的时候,比如gcc -c xxx,那么一定会有一个系统调用帮助我们将这个参数xxx给传入到对应的进程中去。

于是我们可以说exec允许对新的状态机设置参数argv和环境变量envp

进程的退出必须使用exit系统调用终止,这让内核有机会将该进程使用的资源给释放会系统, 在我们的操作系统中存在四种exit退出的方法。

但是这四种方法存在一些比较细微的一些区别, 比如说exit(0)这个是可以被我们的C编译器给识别的,其余两个是进行系统调用,可能无法被识别,这样就会导致不会出发atexit(func), 具体的情况可以通过下面这个代码去实验。

#include <stdlib.h>#include <stdio.h>#include <string.h>#include <unistd.h>#include <sys/syscall.h>
void func() { printf("Goodbye, Cruel OS World!\n");}
int main(int argc, char *argv[]) { // This is a convenient mechanism for atexit(func);
if (argc < 2) { // We can reference the return code $? in bash. // Our online judge also uses this return code. return EXIT_FAILURE; }
if (strcmp(argv[1], "exit") == 0) { // This is a libc exit. exit(0); }
if (strcmp(argv[1], "_exit") == 0) { // This is an immediate operating system exit. // This _exit() is provided by libc. _exit(0); }
if (strcmp(argv[1], "__exit") == 0) { // This is an even more "operating system" exit. syscall(SYS_exit, 0); }}

【声明】内容源于网络
0
0
摩尔线程
摩尔线程以全功能 GPU 为核心,致力于向全球提供计算加速的基础设施和一站式解决方案,为各行各业的数智化转型提供强大的AI计算支持。
内容 301
粉丝 0
摩尔线程 摩尔线程以全功能 GPU 为核心,致力于向全球提供计算加速的基础设施和一站式解决方案,为各行各业的数智化转型提供强大的AI计算支持。
总阅读137
粉丝0
内容301