Linux系统编程-进程控制相关操作详解
进程(Process)是计算机科学中一个基本的概念,特别是在操作系统领域中非常重要。它指的是在系统中正在运行的一个程序的实例。每个进程都是系统资源分配的基本单位,是程序执行时的一个实例。以下是关于进程的详细解释:
1.进程的基本概念和特征:
-
程序与进程的区别:
- 程序(Program):是存储在磁盘上的一段可执行的代码,是静态的。
- 进程(Process):是程序在执行过程中的一个实例,是动态的。一个程序可以对应多个进程的实例,每个实例相互独立运行。
-
进程的组成:
- 程序代码:进程所执行的指令集合。
- 当前状态:包括程序计数器、寄存器集合和变量的值。
- 内存空间:进程运行时所占用的内存空间,包括代码段、数据段、堆栈段等。
- 资源:如打开的文件、网络连接等系统资源。
-
进程的特征:
- 程序实例化:进程是一个程序的实例,当程序被加载到内存并开始执行时,就创建了一个进程。
- 资源拥有:每个进程都拥有自己的内存空间、文件描述符、系统指令集、状态等资源。
- 独立性:进程之间是相互独立的,一个进程的错误不会直接影响其他进程(除非它们共享某些资源)。
- 并发执行:多个进程可以同时存在和执行,操作系统通过调度算法来分配CPU时间片给不同的进程。
- 生命周期:进程有创建、就绪、运行、阻塞和终止等状态,这些状态在操作系统的进程管理中有具体的实现。
4. 进程控制块(PCB)
每个进程在操作系统中都有一个相应的进程控制块(PCB),也称为进程描述符。PCB 是操作系统维护的数据结构,用来存储和管理进程的各种信息,包括但不限于:
- 进程状态:例如就绪、运行、阻塞等。
- 进程ID:唯一标识符,用来区分不同的进程。
- 程序计数器(PC):指向当前执行的指令地址。
- 内存管理信息:包括代码段、数据段、堆栈等内存分配信息。
- 调度信息:进程的优先级、调度队列中的位置等。
- 打开文件表:进程打开的文件描述符列表。
- 资源使用情况:如CPU时间、I/O状态等。
- 父子进程关系:父进程ID、子进程ID等。
5. 进程间通信(IPC)
不同进程之间可以通过操作系统提供的进程间通信(IPC)机制进行数据交换和协作,常见的 IPC 包括:
- 管道(Pipe):用于具有亲缘关系的进程间通信,是半双工的。
- 消息队列(Message Queue):允许一个进程向另一个进程发送消息的队列。
- 共享内存(Shared Memory):允许多个进程访问同一块物理内存,是最快的 IPC 方法之一。
- 信号量(Semaphore):用于进程间的同步和互斥。
- 套接字(Socket):用于不同计算机之间或同一计算机的进程间通信。
6. 进程的创建和销毁
- 创建进程:通常通过系统调用(如
fork()
、exec()
等)来创建新的进程。 - 进程终止:进程可以正常退出(调用
exit()
),也可以因为错误或信号而异常终止。操作系统负责回收进程使用的资源,释放其占用的内存等。
2.进程创建
1.fork()
用于创建一个新的进程。新进程是调用进程的副本,但有自己独立的进程ID(PID)。
函数原型:
#include <unistd.h>
pid_t fork(void);
返回值:
在父进程中,fork() 返回新创建的子进程的进程ID,一个大于0的数。
在子进程中,fork() 返回 0。
如果出现错误,则返回 -1。
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main() {pid_t pid; // 用于存储 fork() 返回的进程IDprintf("Before fork()\n");pid = fork(); // 调用 fork() 创建新的进程if (pid < 0) {// 如果 fork() 出现错误fprintf(stderr, "Fork failed\n");return 1;} else if (pid == 0) {// 子进程代码段printf("Child process: PID = %d\n", getpid());printf("Child process: Parent PID = %d\n", getppid());// 在子进程中,可以执行具体的任务,如执行新的程序,或者进行其他操作// 这里演示子进程的简单输出} else {// 父进程代码段printf("Parent process: PID = %d\n", getpid());printf("Parent process: Child PID = %d\n", pid);// 父进程可以继续执行其他任务,或者等待子进程结束(使用 wait() 或 waitpid())}printf("After fork()\n");return 0;
}
实现原理:
1. 调用过程
当程序调用 fork()
时,操作系统会执行以下步骤:
-
复制父进程:
- 操作系统会创建一个新的进程(称为子进程)。子进程是父进程的副本,包括代码段、数据段、堆栈以及文件描述符表等。
-
设置进程状态:
- 子进程开始时处于就绪(Ready)状态,等待调度执行。
-
返回值:
- 在父进程中,
fork()
返回子进程的PID(进程ID)。 - 在子进程中,
fork()
返回0。
- 在父进程中,
2. 内存映像的复制
在调用 fork()
时,操作系统需要复制父进程的内存映像。具体复制的内容包括:
- 代码段:父进程的可执行代码。
- 数据段:包括全局变量和静态变量等。
- 堆栈:包括函数调用的信息和局部变量等。
这种复制是通过操作系统的内存管理机制实现的。在实现过程中,操作系统通过虚拟内存技术来为子进程分配新的物理内存页,并将父进程的对应内存页内容复制到子进程的内存中。这种复制采用了写时复制(Copy-on-Write, COW)技术,即只有在子进程或父进程试图修改内存内容时,才会实际进行内存复制操作,以节省内存和提高效率。
3. 父子进程的区别
虽然子进程是父进程的副本,但它们之间存在一些区别:
- 返回值不同:父进程中
fork()
返回子进程的PID,而子进程中返回0。 - 进程ID不同:父进程和子进程拥有不同的进程ID。
- 父进程的子进程数增加:父进程会增加一个子进程。
4. 注意事项
在使用 fork()
时,需要注意以下几点:
-
文件描述符的复制:子进程会继承父进程的文件描述符,但它们操作这些文件描述符时,可能会引起意外的影响。因此,通常在
fork()
后会调用exec()
系列函数来替换子进程的地址空间,以确保文件描述符处于预期状态。 -
共享内存:父进程和子进程共享相同的内存内容,直到其中一个尝试修改共享的内容时,操作系统才会复制相关的内存页。
面试题:回答两段代码的输出结果是什么
(1)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main(int argc, char* argv[]) {for(int i = 0; i < 3; i++) {fork();printf("%da ",i);}return 0;
}(2)
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int main(int argc, char* argv[]) {for(int i = 0; i < 3; i++) {fork();printf("%da\n",i);}return 0;
}
两段代码的输出结果:
(1)24个a,第一次循环后有两个进程,第二次循环后共有4个进程,第三次循环后共有8个进程,每个进程往printf打印三个a。
(2)14个a,第一次循环后共有2个进程,打印两个a,第二次循环后共有4个进程,打印输出4个a,第三次循环后共有8个进程,打印输出8个a,所有共有14个a。
原因:在使用 fork()
函数创建子进程时,如果父进程有用户态文件缓冲区(例如使用 printf()
输出的缓冲区)中的数据,这些数据也会被复制到子进程的缓冲区中。这会导致在某些情况下出现意外的输出情况或者重复的输出。
详细解释:
-
用户态文件缓冲区:
- 在 C 语言中,
printf()
等输出函数通常会将数据暂存在用户态文件缓冲区中,而不是立即输出到终端。 - 当调用
fork()
创建子进程时,父进程的用户态文件缓冲区中的数据也会被复制到子进程的用户态文件缓冲区中,包括已经积累的输出数据。
- 在 C 语言中,
-
缓冲区复制的影响:
- 如果在调用
fork()
前,父进程的缓冲区中有未输出的数据,比如部分字符串或者字符,这些数据会被完整地复制到子进程的缓冲区中。 - 因此,父子进程各自拥有独立的缓冲区副本,但它们的初始内容可能是相同的,包括未输出的数据部分。
- 如果在调用
-
可能的结果:
- 如果父进程在
fork()
前有未输出的部分数据,那么每个进程在继续执行时会从自己的缓冲区中输出这些数据。这可能导致输出的重复或者顺序上的不一致。
- 如果父进程在
-
影响输出的情况:
- 对于带有换行符的
printf("a\n")
,换行符会触发缓冲区的刷新,这会导致输出立即显示在终端上,避免父子进程之间的输出混合问题。 - 对于不带换行符的
printf("a")
,则会把所有的'a'
累积在缓冲区中,父子进程之间可能会产生交叉的输出。
- 对于带有换行符的
2.查看进程
1.getpid()
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
// 总是成功:返回当前进程的pid
2.getppid()
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
// 总是成功:返回父进程的pid
代码示例:
#include <stdio.h> // 包含标准输入输出库
#include <unistd.h> // 包含 POSIX 系统服务的头文件int main() {// 使用 getpid() 函数获取当前进程的PIDpid_t pid = getpid();pid_t ppid = getppid();// 打印当前进程的PIDprintf("PID of this process: %d\n", pid);// 打印父进程的PIDprintf("Parent PID of this process: %d\n", ppid);return 0;
}
3.进程终止
1.exit()
exit()
是标准 C 库中的函数,它用于正常终止一个程序。它执行以下操作:
- 执行
atexit()
注册的函数(清理函数)。 - 关闭所有标准 I/O 流(如文件、套接字等)。
- 刷新所有缓冲区。
- 最后,调用内核函数
_exit()
终止进程。
void exit(int status);
status 参数指定了进程的退出状态,通常用来表示程序的结束状态或者错误码。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {printf("Before calling exit()\n");// 注册退出时的清理函数atexit(my_exit_handler);printf("Calling exit()\n");exit(0);// 这里的代码不会被执行printf("After calling exit()\n");return 0;
}// 退出时的清理函数
void my_exit_handler() {printf("Inside exit handler\n");
}
2._exit()
_exit()
是一个系统调用,用于立即终止一个进程,不执行任何清理操作,直接返回内核。它不会调用 atexit()
注册的清理函数,也不会刷新标准 I/O 流。
void _exit(int status);
status 参数指定了进程的退出状态。
示例代码:
#include <stdio.h>
#include <unistd.h>int main() {printf("Before calling _exit()\n");_exit(0); // 立即终止进程// 这里的代码不会被执行printf("After calling _exit()\n");return 0;
}
区别总结
-
exit()
:- 标准 C 函数,执行标准的清理操作(如调用
atexit()
注册的函数)。 - 刷新缓冲区,关闭文件描述符等。
- 程序员通常使用它来正常终止进程。
- 标准 C 函数,执行标准的清理操作(如调用
-
_exit()
:- 系统调用,直接返回内核,不执行任何标准的清理操作。
- 程序员通常用于需要立即退出且不需要执行清理操作的情况。
4.进程回收
进程回收是指当一个进程(子进程)结束执行时,操作系统需要进行的一系列动作,包括但不限于:
- 释放进程所占用的内存空间。
- 关闭打开的文件描述符。
- 回收其他系统资源,如信号量、消息队列等。
- 通知父进程该子进程的终止状态,以便父进程可以做进一步的处理。
进程回收是操作系统管理资源的一部分,当一个进程结束时,它可能会持有多种资源,如内存、文件描述符、系统信号量等。如果这些资源不被及时释放,将导致系统资源的浪费,甚至可能引发资源耗尽的问题。
孤儿进程(Orphan Process)
-
定义:
- 孤儿进程是指其父进程先于它结束或者它的父进程被终止而不能正常等待它的结束状态的进程。
- 当一个进程的父进程结束时,操作系统会将这个进程的新父进程设置为 init 进程(进程号为 1 的系统进程)。
-
影响:
- 孤儿进程会被 init 进程接管,并由 init 进程负责收养和管理。
- 对操作系统的影响较小,因为操作系统会确保孤儿进程能够被正确回收,不会造成资源泄露或其他问题。
僵尸进程(Zombie Process)
-
定义:
- 僵尸进程是指一个已经完成执行的进程,但是其父进程还没有调用
wait()
或waitpid()
等系统调用来获取它的终止状态。 - 僵尸进程会在进程表中保留其进程号和一些基本信息,但不再执行任何代码。
- 僵尸进程是指一个已经完成执行的进程,但是其父进程还没有调用
-
影响:
- 僵尸进程占用系统资源(如进程表项),尽管不占用内存空间,但是大量僵尸进程可能导致进程表耗尽,进而影响系统的正常运行。
- 过多的僵尸进程可能会导致系统性能下降,甚至造成系统崩溃。
1.wait()
wait()
函数阻塞调用进程,直到一个子进程结束或者收到一个信号,它会暂停当前进程的执行,直到有子进程退出为止。- 如果调用进程没有子进程或者所有子进程都还在运行,
wait()
会一直阻塞。
函数原型
#include <sys/types.h>
#include <sys/wait.h>pid_t wait(int *status);
参数说明:
status 是一个指向整型的指针,用于存储子进程的退出状态信息。
如果不关心子进程的退出状态,可以传入 NULL。
返回值:
成功时,返回终止的子进程的进程ID。
失败时,返回 -1,并设置 errno 来指示错误的类型。
子进程退出状态获取:
可以通过宏来解析 wait() 返回的 status 变量,获取子进程的退出状态信息。
WIFEXITED(status):如果子进程正常终止,则为真。
WEXITSTATUS(status):获取子进程的退出状态。
注意事项:
如果多个子进程同时结束,wait() 只会返回一个已终止子进程的信息,如果需要获取所有子进程的终止信息,可以循环调用 wait()。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid = fork();if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程执行的代码printf("Child process executing...\n");sleep(2); // 模拟子进程执行任务printf("Child process exiting...\n");exit(EXIT_SUCCESS);} else {// 父进程执行的代码printf("Parent process waiting for child...\n");int status;pid_t child_pid = wait(&status); // 等待子进程结束if (child_pid == -1) {perror("wait failed");exit(EXIT_FAILURE);}if (WIFEXITED(status)) {printf("Child process %d exited with status %d\n", child_pid, WEXITSTATUS(status));} else {printf("Child process %d did not exit normally\n", child_pid);}}return 0;
}
2.waitpid
waitpid()
函数会阻塞调用进程,直到指定的子进程结束或者满足其他的条件。
函数原型:
#include <sys/types.h>
#include <sys/wait.h>pid_t waitpid(pid_t pid, int *status, int options);
参数说明:
pid:指定要等待的子进程的进程ID。
如果 pid > 0,则等待具有指定进程ID的子进程。
如果 pid == -1,则等待任何子进程。
如果 pid == 0,则等待和调用进程在同一个进程组的任何子进程。
如果 pid < -1,则等待属于进程组 -pid 的任何子进程。
status:用于存储子进程的退出状态信息的指针。
options:控制 waitpid() 的行为的选项:
WNOHANG:如果没有符合条件的子进程退出,则立即返回,而不阻塞。
WUNTRACED:除了已经退出的子进程外,也等待进程状态被暂停的子进程。
返回值:
成功时,返回终止的子进程的进程ID。
失败时,返回 -1,并设置 errno 来指示错误的类型。
子进程退出状态获取:
可以通过宏来解析 waitpid() 返回的 status 变量,获取子进程的退出状态。
WIFEXITED(status):如果子进程正常终止,则为真。
WEXITSTATUS(status):获取子进程的退出状态。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>int main() {pid_t pid1, pid2, pid;int status;// 创建第一个子进程pid1 = fork();if (pid1 < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid1 == 0) {// 子进程1执行的代码printf("Child process 1 executing...\n");sleep(2); // 模拟子进程1执行任务printf("Child process 1 exiting...\n");exit(101); // 子进程1退出,退出状态为101}// 创建第二个子进程pid2 = fork();if (pid2 < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid2 == 0) {// 子进程2执行的代码printf("Child process 2 executing...\n");sleep(4); // 模拟子进程2执行任务printf("Child process 2 exiting...\n");exit(202); // 子进程2退出,退出状态为202}// 父进程执行的代码printf("Parent process waiting for child processes...\n");// 等待第一个子进程结束,当options设置为0是意思是一直阻塞的,类似waitpid = waitpid(pid1, &status, 0);if (pid == -1) {perror("waitpid failed");exit(EXIT_FAILURE);}if (WIFEXITED(status)) {printf("Child process %d exited with status %d\n", pid, WEXITSTATUS(status));} else {printf("Child process %d did not exit normally\n", pid);}// 等待第二个子进程结束,使用WNOHANG选项非阻塞printf("Parent process checking child 2...\n");pid = waitpid(pid2, &status, WNOHANG);if (pid == 0) {printf("Child process 2 is still running\n");} else if (pid == -1) {perror("waitpid failed");exit(EXIT_FAILURE);} else {if (WIFEXITED(status)) {printf("Child process %d exited with status %d\n", pid, WEXITSTATUS(status));} else {printf("Child process %d did not exit normally\n", pid);}}// 等待所有子进程结束,使用WUNTRACED选项printf("Parent process waiting for all children...\n");while ((pid = waitpid(-1, &status, WUNTRACED)) > 0) {if (WIFEXITED(status)) {printf("Child process %d exited with status %d\n", pid, WEXITSTATUS(status));} else if (WIFSIGNALED(status)) {printf("Child process %d terminated by signal %d\n", pid, WTERMSIG(status));} else if (WIFSTOPPED(status)) {printf("Child process %d stopped by signal %d\n", pid, WSTOPSIG(status));}}if (pid == -1) {perror("waitpid failed");exit(EXIT_FAILURE);}return 0;
}
5.进程执行
exec()
系列系统调用函数用于在当前进程中执行新的程序。这些函数会用指定的程序替换当前进程的内存空间,从而执行新程序。在Linux系统编程中,exec()
函数族包括多个变种,如 execve()
、execl()
、execv()
等,它们允许以不同的方式传递命令行参数、环境变量和工作目录。
1.execve()
execve()
系统调用函数是 exec()
系列函数中最灵活和通用的一个,它允许在当前进程中执行新程序,并且可以指定命令行参数和环境变量。
#include <unistd.h>int execve(const char *filename, char *const argv[], char *const envp[]);
execve() 函数会用指定的程序替换当前进程的内容,执行新程序。
参数说明:
filename:要执行的新程序的路径。
argv:一个以 NULL 结尾的字符串数组,用于传递给新程序的命令行参数。
envp:一个以 NULL 结尾的字符串数组,用于传递给新程序的环境变量。
返回值:
如果执行成功,execve() 函数不会返回,因为当前进程的内容已经被替换为新程序的内容。
如果执行失败,返回 -1 并设置 errno 来指示错误的类型。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main() {pid_t pid;// 创建子进程pid = fork();if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程执行的代码char *args[] = {"./child", "Hello from child!", NULL};char *envp[] = {"PATH=/bin:/usr/bin", "HOME=/home/user", NULL};printf("Child process executing...\n");// 执行新程序 ./childif (execve("./child", args, envp) == -1) {perror("execve failed");exit(EXIT_FAILURE);}// 如果 execve() 成功,子进程不会继续执行到这里printf("This line will not be executed in child process.\n");} else {// 父进程执行的代码printf("Parent process waiting for child...\n");wait(NULL); // 等待子进程结束printf("Child process finished.\n");}return 0;
}// child.c#include <stdio.h>int main(int argc, char *argv[]) {if (argc != 2) {fprintf(stderr, "Usage: %s <message>\n", argv[0]);return 1;}printf("Child process received message: %s\n", argv[1]);return 0;
}
2.execl()
execl()
系统调用函数用于在当前进程中执行新的程序,它是 exec()
系列函数中的一员,专门用于接受可变长度的参数列表。
函数原型:
#include <unistd.h>
int execl(const char *path, const char *arg0, ... /* (char *) NULL */);
execl() 函数会用指定的程序替换当前进程的内容,执行新程序。
path 是要执行的程序的路径。
arg0, ... 是一个可变长度的参数列表,以 NULL 结尾,用于传递命令行参数给新程序。
参数说明:
path:要执行的新程序的路径。
arg0, ...:命令行参数列表,以及可选的 (char *) NULL 作为结束标志。
返回值:
如果执行成功,execl() 函数不会返回,因为当前进程的内容已经被替换为新程序的内容。
如果执行失败,返回 -1 并设置 errno 来指示错误的类型。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid;// 创建子进程pid = fork();if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程执行的代码printf("Child process executing...\n");// 使用 execl 执行新程序 lsif (execl("/bin/ls", "ls", "-l", NULL) == -1) {perror("execl failed");exit(EXIT_FAILURE);}// 如果 execl() 成功,子进程不会继续执行到这里printf("This line will not be executed in child process.\n");} else {// 父进程执行的代码printf("Parent process waiting for child...\n");wait(NULL); // 等待子进程结束printf("Child process finished.\n");}return 0;
}
3.execlp()
execlp()
系统调用函数与 execl()
函数非常类似,主要区别在于 execlp()
允许不用指定程序的完整路径,而是根据系统的 PATH
环境变量来查找可执行文件。
函数原型:
#include <unistd.h>
int execlp(const char *file, const char *arg0, ... /* (char *) NULL */);
execlp() 函数在当前进程中执行指定的程序 file。
file 是要执行的程序的名称,可以是不带路径的可执行文件名称。
arg0, ... 是一个可变长度的参数列表,用于传递命令行参数给新程序,以 NULL 结尾。
参数说明:
file:要执行的程序的名称,可以是不带路径的可执行文件名。
arg0, ...:命令行参数列表,以及可选的 (char *) NULL 作为结束标志。
返回值:
如果执行成功,execlp() 函数不会返回,因为当前进程的内容已经被替换为新程序的内容。
如果执行失败,返回 -1 并设置 errno 来指示错误的类型。
示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>int main() {pid_t pid;// 创建子进程pid = fork();if (pid < 0) {perror("fork failed");exit(EXIT_FAILURE);} else if (pid == 0) {// 子进程执行的代码printf("Child process executing...\n");// 使用 execlp 执行 ls 命令if (execlp("ls", "ls", "-l", NULL) == -1) {perror("execlp failed");exit(EXIT_FAILURE);}// 如果 execlp() 成功,子进程不会继续执行到这里printf("This line will not be executed in child process.\n");} else {// 父进程执行的代码printf("Parent process waiting for child...\n");wait(NULL); // 等待子进程结束printf("Child process finished.\n");}return 0;
}
4.exec函数族的实现原理
exec()
函数族(包括 execve()
、execl()
、execlp()
等)实现的原理涉及操作系统的进程管理和内存管理机制。这些函数族的共同目标是在当前进程中执行一个新的程序,取代当前进程的内存映像。exec族函数的实现原理图:如:execlp(“ls”, “ls”, “-l”, NULL);
原理概述
-
进程内存结构:
- 操作系统为每个进程分配一块内存空间,用于存储程序的代码、数据和堆栈等信息。这块内存空间包括了程序代码段、数据段、堆、栈等区域。
-
进程执行过程:
- 当调用
exec()
函数族中的某一个函数时,操作系统首先会将新程序的可执行文件加载到当前进程的内存空间中。 - 新程序的可执行文件包含了程序的代码段、数据段以及其他必要的资源信息。
- 当调用
-
内存映像替换:
exec()
函数族中的函数会将当前进程的整个内存映像替换为新程序的内存映像。- 这包括清除当前进程的代码、数据和堆栈等区域,并将新程序的相应部分加载到这些区域。
- 因此,调用
exec()
函数后,原有进程的代码、数据和堆栈等内容都会被新程序的内容取代。
-
文件描述符的处理:
- 在
exec()
执行过程中,文件描述符(如打开的文件、网络连接等)会保留。这意味着,新程序可以继续使用原有进程打开的文件或者其他资源,而无需重新打开。
- 在
-
进程控制流:
- 调用
exec()
函数后,原有进程的执行流会停止,不会继续执行exec()
调用之后的代码。因为当前进程的内存映像已经被完全替换为新程序的内容。 - 如果
exec()
函数调用失败,原有进程会继续执行,不会发生替换操作。
- 调用
-
错误处理:
- 如果
exec()
函数调用失败(返回-1
),通常会设置全局变量errno
来指示具体的错误类型,比如文件不存在、权限不足等。
- 如果
总结:
exec函数是用一个新程序替换了当前进程的代码段、数据段、堆和栈;原有的进程空间没有发生变化,并没有创建新的进程,进程PID没有发生变化。
相关文章:

Linux系统编程-进程控制相关操作详解
进程(Process)是计算机科学中一个基本的概念,特别是在操作系统领域中非常重要。它指的是在系统中正在运行的一个程序的实例。每个进程都是系统资源分配的基本单位,是程序执行时的一个实例。以下是关于进程的详细解释: …...

分布式I/O从站的认知
为什么需要分布式I/O从站? 当PLC与控制机构距离过远时,远距离会带来信号干扰,分布式I/O从站只需要一个网络线缆连接。 ET200分布式I/O从站家族 体积紧凑、功能强大。 ET200SP ET200M ET200S ET200iSP ET200 AL ET200pro ET200 eco PN 通讯协议…...

【python】PyQt5顶层窗口相关操作API原理剖析,企业级应用实战分享
✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…...

流程图编辑框架LogicFlow-vue-ts和js
LogicFlow官网https://site.logic-flow.cn/LogicFlow 是一款流程图编辑框架,提供了一系列流程图交互、编辑所必需的功能和灵活的节点自定义、插件等拓展机制。LogicFlow支持前端研发自定义开发各种逻辑编排场景,如流程图、ER图、BPMN流程等。在工作审批配…...

goaccess分析json格式日志
一.安装使用yum安装,yum install goaccess 二.主要介绍格式问题 1.nginx日志格式如下: log_format main escapejson {"time_local":"$time_local", "remote_addr":"$remote_addr", "r…...
游戏AI的创造思路-技术基础-决策树(1)
决策树,是每个游戏人必须要掌握的游戏AI构建技术,难度小,速度快,结果直观,本篇将对决策树进行小小解读~~~~ 目录 1. 定义 2. 发展历史 3. 决策树的算法公式和函数 3.1. 信息增益(Information Gain&…...

OPenCV实现直方图均衡化----20240711
# 直方图均衡化import cv2 import numpy as np import matplotlib.pyplot as plt# 读取彩色图像 img = cv2.imread("./pictures/Lena.jpg")# 检查图像是否加载成功 if img is None:print("Could not open or find the i...

2023年全国大学生电子信息竞赛E题——自动追踪系统(stm32和openmv+普通舵机)完美解决第四问
当时做的时候,当时看别人开源的23年的题,感觉一头雾水。两个字没思路。确实只有做了才会有思路。我这里清晰的整理出来思路。 1.第一问的复位问题就是写一个函数,如果按键按下,就进入,再按下就退出 当然这个复位是写死…...

【UNI-APP】阿里NLS一句话听写typescript模块
阿里提供的demo代码都是javascript,自己捏个轮子。参考着自己写了一个阿里巴巴一句话听写Nls的typescript模块。VUE3的组合式API形式 startClient:开始听写,注意下一步要尽快开启识别和传数据,否则6秒后会关闭 startRecognition…...

Apache Spark分布式计算框架架构介绍
目录 一、概述 二、Apache Spark架构组件栈 2.1 概述 2.2 架构图 2.3 架构分层组件说明 2.3.1 支持数据源 2.3.2 调度运行模式 2.3.3 Spark Core核心 2.3.3.1 基础设施 2.3.3.2 存储系统 2.3.3.3 调度系统 2.3.3.4 计算引擎 2.3.4 生态组件 2.3.4.1 Spark SQL 2.…...

Visual Studio 2019 (VS2019) 中使用 CMake 配置 OpenCV 库(快捷版)
2024.07.11 测试有效 最近需要用一下 opencv 处理图像,简单配置了一下Cmake下的 opencv 库。 没有编译 opencv ,也不知道他们为什么要自己编译 opencv 。 一、下载并安装 OpenCV 1.前往 OpenCV 官方网站 下载适用于您的系统的 OpenCV 安装包。 2.点击直接…...

BUG解决:postman可以请求成功,但Python requests请求报403
目录 问题背景 问题定位 问题解决 问题背景 使用Python的requests库对接物联数据的接口之前一直正常运行,昨天突然请求不通了,通过进一步验证发现凡是使用代码调用接口就不通,而使用postman就能调通,请求参数啥的都没变。 接口…...

VScode常用快捷键
VScode介绍 VSCode(全称:Visual Studio Code)是一款由微软开发且跨平台的免费源代码编辑器。能够在windows、Linux、IOS等平台上运行,通过安装一些插件可以让这个编辑器变成一个编译器。与Visual Studio相比,它是免费…...

Day1每日编程题日记:数字统计、两个数组的交集、点击消除
前言:该篇用于记录自看。曾回看昨天的做题代码,竟然会觉得陌生,这竟然是我写的,细细读了一下,原来我当时是这么想的。因此我觉得记代码没有实际用处,重点是领悟了思想,这样子代码就在心中&#…...

ENSP实现防火墙区域策略与用户管理
目录 实验拓扑与要求编辑 交换机与防火墙接口的配置 交换机: 创建vlan 接口配置 防火墙配置及接口配置 防火墙IP地址配置 云配置编辑编辑编辑 在浏览器上使用https协议登陆防火墙,并操作 访问网址:https://192.168.100.1:844…...

c#实现23种常见的设计模式--动态更新
c#实现23种常见的设计模式 设计模式通常分为三个主要类别: 创建型模式 结构型模式 行为型模式。 这些模式是用于解决常见的对象导向设计问题的最佳实践。 以下是23种常见的设计模式并且提供c#代码案例: 创建型模式: 1. 单例模式&#…...

昇思25天训练营Day11 - 基于 MindSpore 实现 BERT 对话情绪识别
模型简介 BERT全称是来自变换器的双向编码器表征量(Bidirectional Encoder Representations from Transformers),它是Google于2018年末开发并发布的一种新型语言模型。与BERT模型相似的预训练语言模型例如问答、命名实体识别、自然语言推理、…...

本地开发微信小程序,使用巴比达内网穿透
在微信小程序开发的热潮中,开发者常面临的一个挑战是如何在复杂的网络环境下测试和调试内网环境中的服务。巴比达正为这一难题提供了一条解决方案,极大简化了微信小程序与内网服务器之间通信的流程,加速了开发迭代周期。 以往,开…...

【LeetCode】快乐数
目录 一、题目二、解法完整代码 一、题目 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为: 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变…...

大模型未来发展深度分析
大模型未来发展方向的深度探讨 近年来,人工智能技术的飞速发展,特别是大模型技术的崛起,为全球科技产业带来了前所未有的变革。大模型,以其强大的推理能力、创意生成能力和情绪智能,正在逐步成为推动社会经济发展的核…...

[线性RNN系列] Mamba: S4史诗级升级
前言 iclr24终于可以在openreview上看预印本了 这篇(可能是颠覆之作)文风一眼c re组出品;效果实在太惊艳了,实验相当完善,忍不住写一篇解读分享分享。 TL;DR (overview) Structured State-Sp…...

【鸿蒙学习笔记】元服务
官方文档:元服务规格 目录标题 什么是元服务特征第一个元服务-案例介绍创建项目源码启动模拟器启动entry创建卡片出发元服务 什么是元服务 特征 免安装分包预加载老化和更新机制 第一个元服务-案例介绍 创建项目 源码 Entry Component struct WidgetCard {buil…...

LIS+找规律,CF 582B - Once Again...
一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 582B - Once Again... 二、解题报告 1、思路分析 考虑朴素做法对T *n的数组求LIS 但是T * n可达1e9 思考一下,最优解无非就是几个循环节拼接,我们最差情况下对sqrt(T)个a[]求LIS即…...

数据赋能(145)——开发:数据拆分——实施过程、应用特点
实施过程 数据拆分的实施过程通常涉及以下几个关键步骤: 确定拆分目标和需求: 明确数据拆分的目的和需求,例如是为了减少数据处理的复杂性、提高查询效率还是为了满足特定的业务需求。根据需求确定拆分后的数据结构和拆分规则。选择拆分方法…...

【漏洞复现】Splunk Enterprise for Windows 任意文件读取漏洞 CVE-2024-36991
声明:本文档或演示材料仅用于教育和教学目的。如果任何个人或组织利用本文档中的信息进行非法活动,将与本文档的作者或发布者无关。 一、漏洞描述 Splunk Enterprise 是一款强大的机器数据管理和分析平台,广泛应用于企业中,用于实…...

FastAPI -- 第一弹
Hello World 经典的 Hello World 安装 pip install fastapi pip install "uvicorn[standard]"main.py from typing import Unionfrom fastapi import FastAPIapp FastAPI()app.get("/") def read_root():return {"Hello": "World"}…...

C++入门基础篇(1)
欢迎大家来到海盗猫鸥的博客—— 断更许久,让我们继续好好学习吧! 目录 1.namespace命名空间 命名空间的存在价值: 命名空间的定义: 命名空间的使用: 2.C输入输出函数 使用: 3.缺省参数 4.函数重载…...

基于html开发的在线网址导航在线工具箱源码
基于html开发的在线网址导航在线工具箱源码,将全部文件复制到服务器,入口文件是index.html 如需修改网址,可修改index.html 如需修改关于页面,可修改about里面的index页面 源码下载:https://download.csdn.net/down…...

【密码学】大整数分解问题和离散对数问题
公钥密码体制的主要思想是通过一种非对称性,即正向计算简单,逆向计算复杂的加密算法设计,来解决安全通信。本文介绍两种在密码学领域内最为人所熟知、应用最为广泛的数学难题——大整数分解问题与离散对数问题 一、大整数分解问题 …...

解析 pdfminer layout.py LAParams类及其应用实例
解析 pdfminer layout.py LAParams类及其应用实例 引言类的定义1. line_overlap2. char_margin3. word_margin4. line_margin5. boxes_flow6. detect_vertical7. all_texts 类的初始化参数验证类的表示总结 引言 在这篇文章中,我们将解析一个叫做 LAParams 的类。这…...