Linux 进程详解

目录

一、进程创建

二、进程API 

1. 进程创建 fork() 

2. 等待 wait()

3.执行 exec() 

3.1 execlp函数

3.2 execl函数

三、其他API 


一、进程创建

上文讲述了进程的概念,现在大家对于进程的定义已经有所了解了,本文主要介绍一下进程的基本信息,例如进程是怎么创建和结束的,怎么用操作系统提供的API去自己创建一个进程?

创建:在平时我们打开一个软件只需要双击图标,软件就会自己运行,或者在shell中输入命令就可以创建一个进程来运行程序,那么程序是如何转化为进程的呢?

Linux 进程详解_第1张图片 上图直观的显示出了这个过程,程序运行时首先操作系统会将程序运行需要的数据存入到内存,存入到进程的地址空间中。此时已经将程序的运行代码存到了进程的地址空间中,还需要分配一些堆栈空间用于程序执行,此时就完成了准备工作,最后就是启动程序运行,OS将CPU的控制权转移给进程,从而程序开始执行。

操作系统为每个进程分配独立的地址空间,确保进程之间的内存空间相互隔离,不会相互干扰或访问彼此的数据。,所以我们可以运行多个程序并且他们互不干扰

二、进程API 

1. 进程创建 fork() 

#include 
#include 
#include 

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        // fork() 出错
        fprintf(stderr, "Fork failed\n");
        return 1;
    } 
    else if (pid == 0) {
        // 子进程
        printf("This is the child process\n");
    }
    else {
        // 父进程
        printf("This is the parent process\n");
    }

    return 0;
}

子进程并不是完全拷贝了父进程。具体来说,虽然它拥有自己的地址空间(即拥有自己的私有内存)、寄存器、程序计数器等,但是它从fork()返回的值是不同的。fork() 调用返回两次,一次在父进程中返回子进程的进程 ID(PID),一次在子进程中返回0。通过这种方式,父进程和子进程可以根据返回值来确定自己的身份。

2. 等待 wait()

上述代码如果你多次运行后会发现运行后产生的结果不是固定的,有的情况下父进程会早于子进程被打印出来,这种不确定性是因为CPU调度程序来决定哪个进程先被执行,感兴趣可以了解一下系统调度算法

wait()是一个系统调用(或函数),用于父进程等待其子进程的结束。它允许父进程暂停执行,直到子进程完成执行为止。

#include 
#include 
#include 
#include 

int main() {
    pid_t pid = fork();

    if (pid < 0) {
        // fork() 出错
        fprintf(stderr, "Fork failed\n");
        return 1;
    } 
    else if (pid == 0) {
        // 子进程
        printf("This is the child process\n");
        sleep(2);  // 模拟子进程执行一段时间
        return 42;
    }
    else {
        // 父进程
        printf("This is the parent process\n");
        int status;
        pid_t child_pid = wait(&status);
        
        if (WIFEXITED(status)) {
            int exit_status = WEXITSTATUS(status);
            printf("Child process %d exited with status %d\n", child_pid, exit_status);
        } 
        else {
            printf("Child process %d did not exit normally\n", child_pid);
        }
    }

    return 0;
}

wait() 函数会阻塞父进程的执行,直到一个子进程结束。在子进程结束后,父进程会继续执行,并从 wait() 调用中返回。

wait() 函数的参数 status 是一个指向整型变量的指针,用于存储子进程的退出状态信息。可以使用宏函数 WIFEXITED(status)、WEXITSTATUS(status) 等来检查和获取子进程的退出状态。

wait() 函数可以等待任意一个子进程结束,也可以使用 waitpid() 函数等待指定的子进程。

3.执行 exec() 

给我可执行程序的名称及需要的参数后,exec()会从可执行程序中加载代码和静态数据,并用它覆写自己的代码段(以及静态数据)堆、栈及其他内存空间也会被重新初始化。然后操作系统就执行该程序,将参数通过argv传递给该进程。因此,它并没有创建新进程,而是直接将当前运行的程序替换为不同的运行程序

exec 系列的函数有多个变体,包括 execl、execv、execle、execve 等,每个变体在使用上略有不同,但它们的基本目标都是相同的:加载并执行一个新的程序。

我们主要介绍其中的两个:

3.1 execlp函数

成功无返回,失败返回-1。 

#include 

int execlp(const char *file, const char *arg0, ..., (char *) NULL);

execlp 函数接受可执行文件的名称(不需要完整路径)和一系列参数,以及一个以 NULL 结束的参数列表。它会在系统的路径中搜索与提供的文件名称匹配的可执行文件,找到后将加载并执行该程序。

3.2 execl函数

 成功无返回,失败返回-1。 

#include 

int execl(const char *path, const char *arg0, ..., (char *) NULL);

 加载一个进程,通过路径和程序名来加载

4.结束进程

  • exit() :在程序的任何地方调用 exit() 函数将导致程序立即退出。一般约定,状态码为 0 表示程序正常退出,非零状态码表示程序异常退出或出错。
  • 通过信号终止进程:kill <进程ID>
  • 强制终止进程: kill -9 <进程ID>

三、其他API 

 除了上面提到的 fork()、wait()和 exec()之外,还有其他许多与进程交互的方
式。比如可以通过 kill()系统调用向进程发送信号,包括要求进程睡眠、终止或其
他有用的指令。实实上,整个信号子系统提供了一套丰富的向进程传递外部事件的途径。

你可能感兴趣的:(linux,服务器,改行学it,开发语言,学习方法)