操作系统——进程

        进程是一个程序执行的过程,会去分配内存(mem),固态硬盘空间(ssd)和CPU等等;用来实现同一时刻多任务并发

进程控制块PCB

        这是一个结构体,是操作系统中最重要的数据结构之一。PCB是进程存在的唯一标识,操作系统通过管理PCB来管理进程。

        ​进程ID​:唯一的身份标识。

        ​进程状态​:运行、就绪、阻塞等。

        程序计数器​:指向下一条要执行的指令的地址。

        CPU寄存器​:当进程被切换时,需要保存当前的寄存器状态,以便下次执行时能恢复。

        ​内存管理信息​:如基地址、界限地址等。

        ​记账信息​:使用的CPU时间、时间戳等。

        ​I/O状态信息​:分配给该进程的I/O设备、打开的文件列表等。

进程的内存分布(32位架构最大约3GB)

代码段/文本段(code)

        存放程序本身的机器指令(即编译后的可执行代码),是从可执行文件中直接加载进来的。是只读且可共享的,同一程序的多个进程可以共享同一份代码段,节省内存。

数据段(data)

        包含了全局变量和静态变量(包括全局静态变量和局部静态变量),在程序开始时分配,并一直存在直到程序结束。

内存映射段

        进程虚拟地址空间中的一个区域,操作系统通过mmap()系统调用,将一个文件或者一段匿名内存直接映射到这个区域。

        映射成功后,进程就可以像访问普通内存一样,使用指针来读写文件内容,而无需调用传统的 read()write()等I/O函数。操作系统会在幕后负责将内存中的修改写回磁盘文件。

堆(heap)

        内存占用小于3G,用与在程序运行时动态申请内存(malloc),由程序员手动控制申请和释放,如果忘记释放,就会造成“内存泄漏”。

        ​增长方向​:​向高地址增长。堆的当前边界由一个称为“程序中断点”的指针来标识。

栈(stack)

        linux中默认内存占用8M(无限递归或定义非常大的局部变量会导致“栈溢出”),用于储存函数调用时的局部变量、参数和返回地址。每次函数调用都会在栈上创建一个新的“栈帧”

        由编译器自动管理。函数调用时分配栈帧,函数返回时自动销毁该栈帧。

 内核空间

        在地址空间的最高部分,​存放操作系统内核的代码和数据。​这部分内存受保护,​用户进程无法直接访问。当进程通过系统调用陷入内核态时,才会访问这部分空间。

进程的状态

创建

        进程正在被创建。操作系统正在为其分配PCB(进程控制块)、建立地址空间、加载程序代码等。这是一个初始化的中间状态,尚未准备就绪。

        当操作系统完成创建工作并有足够资源运行它时,状态变为就绪

就绪

        进程已获得了除CPU以外的所有必需资源。它已经加载到内存中,随时可以执行,只是在等待操作系统的调度器选中它。在新进程创建完成,运行中的进程时间片用完,阻塞的进程等待的事件发生了​(如I/O操作完成)等情况下,进程就会处于就绪态。

        当调度器选中它时,状态变为运行

运行

        进程正在CPU上执行其指令。

        在单核CPU系统中,​任何时候最多只有一个进程处于运行状态

        如果下一步进程执行完毕,变为终止;如果进程需要等待某个事件(如用户输入,读取文件),它会主动让出CPU,变为阻塞;如果操作系统音某些原因停止进程占用CPU(通常因为用完了分配的时间片),变为就绪

阻塞(等待)

        进程因等待某个外部事件而主动暂停执行。即使CPU空闲,它也无法运行。如等待用户输入、等待磁盘读写、等待网络传输、等待其他进程发送信号。

        当它所等待的事件发生后,状态变为就绪

终止(退出)

        进程已经结束执行(无论是正常退出还是被强制杀死)。操作系统会开始回收其占用的资源(内存、文件句柄、PCB等)。如进程执行完 exit()系统调用、被其他进程发送信号强制终止或出现严重错误和异常。

        PCB中可能仍保留一些退出状态码供父进程查询。这是一个临时的最终状态。

重要状态转换

调度​:​就绪 -> 运行

        这是操作系统的调度器负责的工作,它根据特定的算法(如时间片轮转、优先级)从就绪队列中选择一个进程来执行。

时间片用完​:​运行 -> 就绪

        这是抢占式多任务系统的核心机制。每个进程被分配一个很短的时间片(如几毫秒),用完后就被剥夺CPU,放回就绪队列,以保证所有进程都能公平地得到执行。

等待事件​:​运行 -> 阻塞

        这是进程主动的行为,例如调用 sleep(), read(), wait()等函数。

事件发生​:​阻塞 -> 就绪

        当外部资源就绪(如I/O完成),操作系统会得到通知,随后将对应的进程从阻塞队列移动到就绪队列,等待再次被调度。

进程相关的命令

查看进程状态

ps

        最基础的进程查看命令,参数风格多样

        ps aux:查看所有用户的所有进程详细信息(USER, PID, %CPU, %MEM, COMMAND 等)

        ps -ef --forest:以层级结构显示进程,可以看出父子关系。

        ps -u username:查看特定用户的进程。

top

        实时动态显示系统进程信息。显示信息包括系统负载、CPU/内存使用率、每个进程的资源占用等。按q退出,按k可以杀死进程。

控制进程(发送信号)

kill (-n)

        指定 PID​ 的进程发送信号,请求进程终止,默认信号是15(正常终止)。

常用信号:

        -1:挂起(SIGHUP),让进程重新读取配置文件;

        -2:中断(SIGINT),相当于在终端按ctrl+c;

        -9:强制终止(SIGKILL),进程无法捕获或忽略,直接由系统终止,可能导致数据丢失;

        -15:正常终止(SIGTERM),默认信号;

        -18:继续运行(SIGCONT),与SIGSTOP对应;

        -19:暂停运行(SIGSTOP),相当于在终端按ctrl+z;

示例:

        kill 1234:请求 PID 为 1234 的进程终止。

        kill -9 1234:强制杀死 PID 为 1234 的进程。

        kill -HUP 4567:让 PID 为 4567 的进程(如 Nginx)重新加载配置。

pkill

        根据进程名或其他属性来发送信号

示例:

        pkill firefox:杀死所有名为 firefox的进程。

        pkill -u username:杀死某用户的所有进程。

killall

        与pkill类似,也是根据进程名来操作,如killall chrome

僵尸进程和孤儿进程

        当一个进程终止时,它并不会立刻从系统中完全消失。内核会保留一些信息(主要是进程描述符PCB),直到其父进程读取它的退出状态。

特性

​僵尸进程​

​孤儿进程​

​本质​

​已终止但未被回收的进程

​仍在运行但父进程已死的进程

​产生原因​

父进程未调用 wait()

父进程先于子进程退出

​对系统的影响​

占用进程ID,过多会导致问题

​无害,是正常运行的进程

​系统处理​

无自动处理,依赖父进程

自动被 ​init 进程收养​

​如何清除​

让父进程调用 wait();或杀死父进程

无需干预,会正常结束并被init清理

​在PS中的状态​

Z(Zombie) 或 <defunct>

S/R(Sleeping/Running),状态正常

​可否用kill清除​

​否​(已是死的)

​可以​(是活的进程)

        Init是最终的守护者​:它确保了所有进程最终都能被妥善清理,是防止系统进程资源泄漏的最后一道防线。

僵尸进程: 执行完毕或终止,但其退出状态没有被父进程读取的进程

        在Linux中,进程终止时,内核会释放其大部分资源(内存、文件描述符等),但会保留一个最小数据集(包括进程ID、退出状态、CPU时间等)保存在进程表项中。这样,父进程以后可以通过 wait()或 waitpid()系统调用来获取这些信息。如果父进程一直没有调用wait(),这个已死的子进程就会一直占据着一个进程表项,成为一个僵尸进程。​

        僵尸进程已经是“死”的,它不占用CPU和内存,但占用着一个进程ID。无法用 kill -9 杀死一个已经死亡的进程。如果系统中存在大量僵尸进程,可能会耗尽可用的进程ID,导致无法创建新进程。在ps或top命令中,其状态显示为z(或者z+表示是前台进程组的一部分),“defunct”也是其标志。

        ​僵尸进程是bug​:它意味着程序逻辑有缺陷(父进程没有履行等待子进程的责任),需要避免。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

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

    if (pid < 0) {
        perror("Fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程
        printf("Child process (PID: %d) is running.\n", getpid());
        printf("Child process is exiting now.\n");
        exit(0); // 子进程正常退出,成为僵尸
    } else {
        // 父进程
        printf("Parent process (PID: %d) created a child (PID: %d).\n", getpid(), pid);
        printf("Parent is going to sleep for 30 seconds and will NOT wait for the child.\n");
        sleep(30); // 在这30秒内,子进程是僵尸状态
        printf("Parent waking up. The program ends, and the zombie is finally reaped.\n");
        // 父进程退出后,init会接管并清理僵尸子进程
    }
    return 0;
}

解决方法:

        编写代码时:父进程必须调用 wait()或waitpid()来等待子进程结束并回收其资源。

        已存在僵尸进程:如果父进程还活着,发送SIGCHLD信号给父进程,使其调用 wait()。如果父进程已经异常,​杀死父进程。父进程死后,它的所有僵尸子进程会被 ​init进程(PID 1)​​ 收养,init会定期调用 wait(),从而彻底清理这些僵尸进程。

孤儿进程:父进程已经终止或退出,但自身仍在运行的子进程

        父进程先于子进程结束(如父进程崩溃或显式退出没有等待子进程)

        当一个进程成为孤儿时,内核会立即将它过继给 init 进程(PID 1)​,成为 init 进程的子进程。Init 进程是系统所有进程的祖先。它会定期调用 wait()系统调用,来清理其下任何终止的子进程(包括这些被过继来的孤儿进程)。因此,​孤儿进程本身不会变成僵尸进程,因为 init 会确保回收它们。所以孤儿进程只是正常运行的进程,不会对系统造成任何危害,最终会正常结束并被 init 清理。

        孤儿进程是特性​:它是系统设计的自然结果,并且系统有完善的机制(init收养)来自动处理,不会造成问题。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

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

    if (pid < 0) {
        perror("Fork failed");
        exit(1);
    } else if (pid == 0) {
        // 子进程
        printf("Child process (PID: %d) started. My parent is (PID: %d).\n", getpid(), getppid());
        printf("Child is going to sleep for 5 seconds...\n");
        sleep(5); // 在此期间,父进程会先退出
        printf("Child woke up. Now my parent is (PID: %d).\n", getppid()); // 此时父进程已是 init (1)
        printf("Child exiting.\n");
    } else {
        // 父进程
        printf("Parent process (PID: %d) created a child (PID: %d).\n", getpid(), pid);
        printf("Parent is exiting NOW, making the child an orphan.\n");
        exit(0); // 父进程立即退出
    }
    return 0;
}

系统调用函数

创建新进程:fork()

        pid_t fork(void);

        用于创建一个新的进程。这个新进程是调用进程(父进程)的一个几乎完全相同的副本。子进程会从父进程那里继承许多属性,包括代码段、数据段、堆、栈的副本;环境变量;已打开的文件描述符(这意味着父子进程可以操作同一个打开的文件);信号处理设置;进程组ID和会话ID。

写时复制:

        在调用fork()的瞬间,​子进程并不会立即复制父进程的全部物理内存,相反,父子进程共享相同的物理内存页。只有当其中任何一个进程尝试修改某块内存时,操作系统才会为子进程复制那块特定的内存页。这极大地提高了效率,避免了不必要的内存拷贝。

返回值:调用一次,返回两次​

        父进程中,fork()返回新创建子进程的 ​进程ID。在子进程中,fork()返回 ​0。如果创建失败(例如系统资源不足),则在父进程中返回 ​​-1,并设置相应的error。

不同之处

        子进程和父进程的ID不同,fork()返回值不同,子进程的挂起信号集被清零,子进程的累计CPU时间被重置为0。

示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(){

//声明一个变量用于存储fork的返回值,pid_t是一个专门用于表示PID的数据类型,本质通常是一个整数。
    pid_t pid;

//打印当前进程PID(父进程)
    printf("Before fork,PID:%d\n",getpid());

//创建子进程
    pid=fork();

//pid<0,创建失败
    if(pid<0)
    {
        perror("error");
        return 1;
    }

//pid=0,创建成功,此分支只有子进程会进入,打印子进程和父进程的pid
    else if (0==pid)
    {
        printf("Child process PID:%d,Father process PID:%d\n",getpid(),getppid());
    }

//pid>0此分支只有父进程会进入,打印父进程和子进程的pid
    else
    {
        printf("Father process PID:%d,Child process PID:%d\n",getppid(),pid);
    }

//公用代码区域,此处代码父子进程都会执行,分别打印自己的pid
    printf("This line is printed by both processes (PID: %d)\n", getpid());

    return 0;
}

获取进程标识:getpid()/getppid()

        pid_t getpid(void);

        返回当前调用进程的进程ID,每个进程都有一个唯一的正整数作为其PID。

        pid_t getppid(void);

        返回当前调用进程的父进程的进程ID,如果父进程终止了,子进程会被 init 进程(PID = 1)收养,此时 getppid()将返回 1。

示例:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;

    pid = fork();

    if (pid < 0) {
        perror("Fork failed");
        return 1;
    } else if (pid == 0) {
        // 子进程:执行一个耗时计算或外部程序
        printf("Child (PID: %d) is starting work...\n", getpid());
        sleep(2); // 模拟工作耗时
        printf("Child (PID: %d) work done!\n", getpid());
        _exit(0); // 子进程退出
    } else {
        // 父进程:等待子进程结束
        printf("Parent (PID: %d) is waiting for child (PID: %d)...\n", getpid(), pid);
        wait(NULL); // 阻塞,直到一个子进程结束
        printf("Parent: My child has finished.\n");
    }

    return 0;
}

终止调用:exit()与_exit()

         void exit(int status);

        用于正常终止调用它的进程,status是返回给父进程的退出状态码;exit(0)表示正常退出,exit(1)(或任何非零值)表示异常退出

        void _exit(int status);

        立即终止当前进程,同样0代表成功非0代表失败

特性

exit()

_exit()

本质

C标准库函数

Linux/Unix 系统调用

头文件

#include <stdlib.h>

#include <unistd.h>

清理动作

执行所有用户层清理
(atexit, 刷新缓冲区等)

立即终止,​不进行任何用户层清理

缓冲区

刷新所有标准I/O缓冲区

不刷新缓冲区,可能导致数据丢失

主要用途

在大多数情况下正常终止程序

1. ​子进程中终止
2. 处理严重错误后的紧急退出

核心区别​:exit()​会刷新缓冲区,_exit()​不会。

        main()函数中,使用 returnexit()任何其他函数中需要终止整个程序时,使用 exit()在 ​fork()创建的子孙进程中需要终止时,​总是使用 _exit()

用法1:在子进程中终止,避免感染父进程的I/O缓冲区

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    printf("This message will be lost"); // 注意:末尾没有换号\n,数据在缓冲区

    pid_t pid = fork();
    if (pid == 0) {
        // 子进程
        // exit(0);    // 如果使用这个,输出会被刷新,消息能打印出来
        _exit(0);      // 使用这个,不刷新缓冲区,消息丢失
    } else {
        // 父进程
        wait(NULL);
        exit(0);       // 父进程退出时会刷新缓冲区
    }
}

        如果子进程使用 _exit(0),那么printf的消息因为还在缓冲区里没有被刷新到屏幕,就会随着进程的终止而丢失。这就是为什么在子进程中通常推荐使用 _exit()——为了避免重复刷新父进程已经持有的缓冲区。

用法2:处理严重错误后紧急退出

        当程序检测到一种严重的、不可恢复的内部状态错误(比如内存结构被破坏)时,可能需要立即终止以避免执行更多的代码,从而造成更大的破坏(如写入错误数据到重要文件)。在这种情况下,跳过所有清理过程是更安全的选择。

清理回调函数:atexit()

        int atexit(void (*function)(void));

        注册一个函数,当程序通过exit()正常终止时,该函数会被自动调用,主要用于程序结束前的资源释放和状态保存等清理工作。

        function是一个函数指针,指向一个没有参数、没有返回值的函数。

        返回值​:注册成功返回 0;失败返回非零值(例如注册的函数数量已达到系统限制)。

        当程序调用exit或者由main函数执行return时,所有用atexit注册的退出函数,将会由注册时顺序倒序被调用;

#include <stdio.h>
#include <stdlib.h>

void cleanup1() {
    printf("Performing cleanup 1...\n");
}

void cleanup2() {
    printf("Performing cleanup 2...\n");
}

void cleanup3() {
    printf("Performing cleanup 3...\n");
}

int main() {
    // 注册退出处理函数
    atexit(cleanup1);
    atexit(cleanup2);
    atexit(cleanup3); // 最后注册,最先执行

    printf("Main function is starting...\n");
    printf("Main function is ending...\n");

    return 0; // return 触发 exit,从而执行 atexit 函数
}



//输出结果:
//    Main function is starting...
//    Main function is ending...
//    Performing cleanup 3...
//    Performing cleanup 2...
//    Performing cleanup 1...

获取子进程状态和回收资源:wait()/waitpid()

特性

wait()

waitpid()

等待目标

任意子进程

可指定特定子进程或进程组

阻塞行为

总是阻塞

可设置为非阻塞 (WNOHANG)

灵活性

简单但功能有限

更精细的控制(如只等待暂停的子进程)

常见用途

简单场景,只需等待一个子进程

需要选择性等待或非阻塞轮询

#include<sys/types.h>

#include<sys/wait.h>

        pid_t wait(int *status);

        阻塞调用进程,直到它的任意一个子进程终止或收到信号。如果已经有子进程终止(成为僵尸进程),则wait()会立即返回。

        @status:一个指向整数的指针,用于存储子进程的退出状态信息。如果不需要这些信息,可以传入NULL。status是一个位掩码,需要用特定的宏来提取信息:

作用

示例

WIFEXITED(status)

子进程是否正常退出​(通过 exit()return

if (WIFEXITED(status))

WEXITSTATUS(status)

获取子进程的退出状态码​(exit()的参数)

int code = WEXITSTATUS(status);

WIFSIGNALED(status)

子进程是否因未捕获的信号而终止

if (WIFSIGNALED(status))

WTERMSIG(status)

获取导致子进程终止的信号编号​

int sig = WTERMSIG(status);

WIFSTOPPED(status)

子进程是否被暂停​(如 SIGSTOP

(较少使用)

WSTOPSIG(status)

获取导致子进程暂停的信号编号​

(较少使用)

返回值:成功返回被终止子进程的PID;失败返回-1。

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

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

    if (pid < 0) {
        perror("Fork failed");
        exit(EXIT_FAILURE);
    } else if (pid == 0) {
        // 子进程
        printf("Child (PID: %d) is running.\n", getpid());
        sleep(2);
        printf("Child is exiting with code 42.\n");
        exit(42); // 子进程退出,状态码为 42
    } else {
        // 父进程
        printf("Parent (PID: %d) is waiting...\n", getpid());
        int status;
        pid_t child_pid = wait(&status); // 阻塞等待

        if (child_pid == -1) {
            perror("Wait failed");
            exit(EXIT_FAILURE);
        }

        if (WIFEXITED(status)) {
            printf("Child %d exited normally with code: %d\n", 
                   child_pid, WEXITSTATUS(status));
        } else if (WIFSIGNALED(status)) {
            printf("Child %d was killed by signal: %d\n", 
                   child_pid, WTERMSIG(status));
        }
    }
    return 0;
}

输出:

Parent (PID: 1234) is waiting...
Child (PID: 1235) is running.
Child is exiting with code 42.
Child 1235 exited normally with code: 42

#include<sys/types.h>

#include<sys/wait.h>

        pid_t waitpid(pid_t pid,int *status,int options);

        等待指定的子进程结束,或符合特定条件的子进程。

@pid

        >0:等待进程ID等于 pid特定子进程

        = -1:等待任意子进程,等同于wait(&status)。

   = 0:等待与调用进程同进程组的任何子进程。

        < -1:等待进程组ID等于 |pid|的任何子进程

@status:同wait();

@options:控制行为的选项

        0:阻塞等待,直到目标子进程终止

        WNOHANG:非阻塞模式。如果没有子进程退出,立即返回 0

        WUNTRACED:额外返回已停止的子进程状态(如被SIGSTOP暂停)。

        WCONTINUED:额外返回已继续的子进程状态(如被SIGCONT恢复)。

返回值​:成功返回状态已改变的子进程PID(如果设置了 WNOHANG且没有子进程退出,返回 0);失败返回 -1(如无子进程)

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>

int main() {
    pid_t child1 = fork();
    if (child1 == 0) {
        // 子进程1
        sleep(2);
        exit(10);
    }

    pid_t child2 = fork();
    if (child2 == 0) {
        // 子进程2
        sleep(4);
        exit(20);
    }

    // 父进程:非阻塞地轮询子进程状态
    int status;
    while (1) {
        pid_t pid = waitpid(-1, &status, WNOHANG); // 不阻塞

        if (pid == -1) {
            printf("No more children.\n");
            break;
        } else if (pid == 0) {
            printf("No child exited yet. Parent is working...\n");
            sleep(1);
        } else {
            if (WIFEXITED(status)) {
                printf("Child %d exited with code: %d\n", 
                       pid, WEXITSTATUS(status));
            }
        }
    }

    return 0;
}

exec族

        exec是一组用于替换当前进程映像的系统调用,它们将当前进程的代码段、数据段、堆和栈替换为一个新的程序。exec不会创建新进程(PID 不变),而是让当前进程“变身”为另一个程序。

        不创建新进程​:fork()创建子进程,而 exec替换当前进程的代码和数据。

        不改变 PID​:进程的 PID、父进程 PID、文件描述符等保持不变。

        不返回​:如果 exec成功,原程序的代码被覆盖,exec之后的代码不会执行。只有失败时才会返回 -1

函数

参数传递方式

是否搜索 PATH

是否自定义环境变量

示例

execl

列表​(可变参数)

❌ 需完整路径

❌ 使用当前环境

execl("/bin/ls", "ls", "-l", NULL)

execv

数组​(char *argv[]

❌ 需完整路径

❌ 使用当前环境

execv("/bin/ls", args)

execlp

列表

✅ 自动搜索 PATH

❌ 使用当前环境

execlp("ls", "ls", "-l", NULL)

execvp

数组

✅ 自动搜索 PATH

❌ 使用当前环境

execvp("ls", args)

execle

列表

❌ 需完整路径

✅ 自定义环境变量

execle("/bin/ls", "ls", "-l", NULL, env)

execvpe

数组

✅ 自动搜索 PATH

✅ 自定义环境变量

execvpe("ls", args, env)

        *execvpe不是标准 POSIX 函数,但 Linux 支持。

execl(参数列表 + 完整路径

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

        参数以列表形式传递,最后一个参数必须是 NULL需指定完整路径​,不会自动搜索 PATH

例:把ls指令替换为'ls -l'指令

#include <unistd.h>
#include <stdio.h>

int main() {
    printf("Before exec\n");
    execl("/bin/ls", "ls", "-l", NULL); // 替换为 `ls -l`
    perror("exec failed"); // 只有失败才会执行
    return 1;
}

execv(参数数组 + 完整路径)​

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

        参数以数组形式传递(需要提前定义一个char *const argv[  ]),数组最后一个元素必须是 NULL,需指定完整路径,不会自动搜索 PATH

例:把ls指令替换为'ls -l'指令

#include <unistd.h>
#include <stdio.h>

int main() {
    char *args[] = {"ls", "-l", NULL};
    printf("Before exec\n");
    execv("/bin/ls", args); // 替换为 `ls -l`
    perror("exec failed");
    return 1;
}

execlp(参数列表+搜索PATH)

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

        参数以列表形式传递,自动搜索 PATH环境变量,只需提供程序名(如 ls)。

例:把ls指令替换为'ls -l'指令

#include <unistd.h>
#include <stdio.h>

int main() {
    printf("Before exec\n");
    execlp("ls", "ls", "-l", NULL); // 自动在 PATH 中找 `ls`
    perror("exec failed");
    return 1;
}

### 操作系统进程与线程的概念 #### 进程 (Process) 进程操作系统结构的基础,表示程序的一次执行过程。每个进程都有独立的代码和数据空间(指令、栈、堆),具有分配资源的功能单位。进程由文本区(即代码)、数据区以及用户堆栈组成,并且拥有自己的地址空间[^1]。 ```c // 创建新进程的例子 pid_t pid; pid = fork(); // Unix/Linux创建子进程的方式 if (pid < 0){ printf("error in fork!"); } else if(pid == 0){ printf("I am child process"); } else { wait(NULL); // 父进程等待子进程结束 } ``` #### 线程 (Thread) 线程有时被称为轻量级进程(Lightweight Process),同一进程内的多个线程共享该进程中大部分的数据和状态信息,如文件描述符、信号处理设置等;但是它们各自有自己的调用栈(call stack) 和寄存器集合(registers set)[^2]。 ```java class MyThread extends Thread{ public void run(){ System.out.println("This is a thread."); } } public class Main { public static void main(String[] args) { new MyThread().start(); } } ``` ### 进程与线程的主要区别 - **定义不同** - 进程是一个具有一定独立功能的程序关于某个数据集上的一次运行活动,而线程则是进程的一个实体,是CPU调度和分派的基本单位[^3]。 - **开销方面** - 启动一个新的进程所需的时间较长,因为这涉及到加载新的环境并初始化所有的上下文。相比之下,启动一个新线程则要快得多,因为它只需要复制父线程的一些属性即可完成创建工作[^4]。 - **资源共享** - 不同进程之间无法直接访问对方的内存区域,如果需要交换数据,则必须通过特定机制实现跨进程通讯(IPC) 。然而,在同一个进程下的各个线程间可以直接读写彼此所在的全局变量或静态方法中的局部变量,因此更容易进行协作工作。 - **通信方式** - 对于多进程而言,通常采用消息队列(message queue), 套接字(socket pair) 或者管道(pipe)等方式来进行相互通信;而对于属于同一进程的不同线程来说,由于它们都处于相同的地址空间内,故可通过共享存储器(shared memory segment)来高效传递信息。 - **调度粒度** - 在现代操作系统里,实际被调度的对象往往是线程而非整个进程——尽管后者确实包含了前者所需的全部资源和支持条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值