进程的基础概念
在Linux系统中,管理进程使用树型管理方式
每个进程都需要与其它某个进程建立父子关系,对应的进程叫做父进程
Linux系统会为每个进程分配ID,这个ID作为当前进程的唯一标识,当进程结束,则会被收回
进程的ID获取:
函数头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
pid_t getpid(void);
函数返回值
returns the process ID (PID) of the calling process. (返回调用进程的进程ID (PID))
父进程的ID获取:
函数头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
pid_t getppid(void);
函数返回值
the process ID of the parent of the calling process. This will be either the ID of the process that created this process using fork(), or if that process has already terminated, the ID of the process to which this process has been reparented.(返回调用进程的父进程ID。这将是使用fork()创建该进程的进程的ID,或者如果该进程已经终止,则是该进程被重新父化的进程的ID)
进程的创建
进程可以创建新的进程,这些新创建的进程被称为子进程,而创建它们的进程则被称为父进程
创建子进程:
通常通过 fork()
系统调用创建子进程。
函数头文件
#include <sys/types.h>
#include <unistd.h>
函数原型
pid_t fork(void);
函数返回值On success, the PID of the child process is returned in the parent, and 0 is returned in the child. On failure, -1 is returned in the parent, no child process is created, and errno is set appropriately.(如果成功,则在父进程中返回子进程的PID,并在子进程中返回0。如果失败,在父进程中返回-1,不创建子进程,并适当设置errno。)
示例代码:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid = fork(); // 创建子进程
if (pid == -1) {
// 子进程创建失败
perror("fork failed");
return 1;
} else if (pid == 0) {
// 子进程
printf("I am the child process with PID: %d\n", getpid());
printf("fork PID:%d\n",pid);
} else {
printf("I am the parent process with PID: %d, and my child has PID: %d\n", getpid(), pid);
}
return 0;
代码解读:
fork()
被调用来创建一个新的子进程。- 如果
fork()
返回-1
,表示创建子进程失败,程序会打印错误信息并退出。 - 如果
fork()
返回0
,表示当前是在子进程中执行,它会打印子进程的 PID 和父进程的 PID。 - 如果
fork()
返回其他值,表示当前是在父进程中执行,它会打印父进程的 PID 和子进程的 PID。
父进程与子进程的关联:
父子进程并发执行,互不干扰,子进程从 fork() 函数之后开始执行
父子进程的执行顺序由操作系统算法决定的,不是由程序本身决定
子进程是父进程的副本,但它是一个独立的实体,拥有自己的内存空间和系统资源
子进程会拷贝父进程地址空间的内容,包括缓冲区、文件描述符等
创建多个进程:
在创建多个进程时,最主要的原则是由父进程统一创建,统一管理,不能进行递归创建
如果递归创建进程,会导致进程树变得非常复杂,难以理解和管理。每个递归创建的进程都可能创建自己的子进程,这会迅速增加系统中的进程数量,并且难以追踪每个进程的来源和它们之间的关系。
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
//创建子进程
pid_t pid = fork();
if(pid == -1)
{
perror("fork");
return -1;
}
else if(pid==0)
{
printf("child1 process <%d> is running.....\n",getpid());
printf("%d\n",pid);
//sleep(2);
printf("child1 process <%d> ready to exit.\n",getpid());
//exit(EXIT_SUCCESS);
}
else{
//创建子进程
pid = fork();
if(pid == -1)
{
perror("fork");
return -1;
}
else if (pid == 0){
printf("child2 process <%d> is running.....\n",getpid());
//sleep(4);
printf("child2 process <%d> ready to exit.\n",getpid());
//exit(EXIT_SUCCESS);
}
printf("parent process task.\n");
}
printf("parent and child task.\n");
return 0;
}
进程的退出
在进程结束时,需要释放分配给进程的地址空间以及内核中产生的各种数据结构
资源的释放需要通过 exit 函数或者 _exit 函数来完成
在程序结束时,会自动调用 exit 函数
exit()函数
函数头文件
#include <stdlib.h>
函数原型
void exit(int status);
函数描述
exit()函数导致正常的进程终止,并将status的最低有效字节(即status & 0xFF)返回给父进程
函数参数
#define EXIT_FAILURE 1 /* Failing exit status. */
#define EXIT_SUCCESS 0 /* Successful exit status. */
_exit()函数
函数头文件
#include <unistd.h>
函数原型
void _exit(int status);
函数描述
立即”终止调用进程。属于该进程的任何打开的文件描述符都被关闭。进程的任何子进程都由init(1)继承。进程的父进程被发送一个SIGCHLD信号。
exit()基于_exit()函数实现,属于库函数,
执行标准的进程退出序列,包括调用退出处理函数、刷新 I/O 流等。_exit()
属于系统调用,直接终止进程,并释放空间以及销毁内核中的各种数据结构。
进程的等待
在 Linux 系统中,父进程通常需要等待一个或多个子进程结束,以便回收它们使用的资源,并且获取它们的退出状态
在子进程运行结束后(调用exit或_exit),进程进入僵死状态,并释放资源,子进程在内核中的数据结构依然保留
父进程调用wait() 或waitpid() 函数等待子进程退出后,释放子进程遗留的资源 (task_struct)
wait()函数
函数头文件
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t wait(int *wstatus);
函数功能
让函数调用者进程进入到睡眠状态,等待子进程进入僵死状态后,释放相关资源并返回
函数返回值
wait(): on success, returns the process ID of the terminated child; on error, -1 is return(如果成功,返回终止子进程的进程ID;如果出错,则返回-1。)
函数参数: ![](https://i-blog.csdnimg.cn/direct/79896bc8fd584c959e914bbce270a076.jpeg)
代码示例:
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
exit(42); // 退出并返回状态码 42
} else {
// 父进程
int status;
wait(&status); // 等待子进程结束
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
}
}
return 0;
}
waitpid()函数
函数头文件
#include <sys/types.h>
#include <sys/wait.h>
函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
函数参数:
int *wstatus与wait()函数一致
int options:如下图
示例代码:
#include <sys/wait.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main() {
pid_t pid = fork();
if (pid == 0) {
// 子进程
exit(42); // 退出并返回状态码 42
} else {
// 父进程
int status;
waitpid(pid, &status, 0); // 等待特定子进程结束
if (WIFEXITED(status)) {
printf("Child exited with status %d\n", WEXITSTATUS(status));
}
}
return 0;
}
总结:
waitpid使用阻塞的方式等待任意子进程退出
waitpid(-1,&status,0);
waitpid使用非阻塞的方式等待子进程退出
while((cpid=waitpid(-1,&status,WNOHANG))==0);
如果不关心状态值,子进程退出状态值的指针为NULL
wait(NULL);
waitpid(-1,NULL,0);
进程的替换
在 Linux 系统中,进程的替换通常指的是一个已经存在的进程通过加载一个新的可执行程序来替换其当前的执行内容。这通常是通过 exec()
系列函数实现的,这些函数在进程的地址空间中替换当前的程序,而不是创建一个新进程。
当一个进程调用 exec()
函数时,它将被一个新的程序替换,但是进程的 PID 保持不变。这意味着进程的身份(如打开的文件描述符、用户 ID、进程组等)不会改变,只是其执行的代码和数据被新程序替换了。
exec()
系列函数:
int execl(const char *pathname, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *pathname, const char *arg, .../*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
参数说明:
path:可执行文件的路径名
file:可执行文件名,可以通过PATH环境变量指定的路径
arg:参数列表,以NULL结尾
argv[]:参数数组
envp[]:环境变量数组
函数返回值:
The exec() functions return only if an error has occurred. The return value is -1, and errno is set to indicate the error(exec()函数只在发生错误时返回。返回值为-1,errno被设置为表示错误)
示例代码:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main() {
// 打印一条消息
printf("Before execlp\n");
// 替换当前进程为 /bin/ls 命令
// execlp 会替换当前进程的映像,但不会创建新进程
// "/bin/ls" 是要执行的命令
// "ls" 是传递给新程序的 argv[0]
// NULL 表示参数列表的结束
execlp("/bin/ls", "ls", "-l", NULL);
// 如果 execlp 返回,说明发生了错误
perror("execlp failed");
return EXIT_FAILURE;
}
代码解读:
- 使用
printf()
函数打印一条消息,表明程序即将尝试执行替换。 - 调用
execlp()
函数尝试替换当前进程映像为/bin/ls
命令。这里"-l"
是传递给ls
命令的参数,请求详细列表。NULL
表示参数列表的结束。 - 如果
execlp()
成功执行,它不会返回。如果它返回,说明执行失败,这时会打印错误信息并返回失败状态。
结语:
无论你是初学者还是有经验的开发者,我希望我的博客能对你的学习之路有所帮助。如果你觉得这篇文章有用,不妨点击收藏,或者留下你的评论分享你的见解和经验,也欢迎你对我博客的内容提出建议和问题。每一次的点赞、评论、分享和关注都是对我的最大支持,也是对我持续分享和创作的动力。