一、进程的执行
子进程被创建好后,就需要去执行它所该执行的功能,根据子进程需要做的事,将其分为两类:
1.子进程所做的事与父进程差不多,两者功能几乎一样 //子承父业
2.子进程所做的事和父进程做的事完全不同,子进程自己完成一项功能 // 自力更生
介绍exec函数族
exec函数族的目的是为了让子程序去执行指定的程序,而不是父进程的副本。
exec
函数族是用于替换当前进程映像(程序)的一组函数。这意味着执行这些函数后,当前进程的代码和数据将被新程序的代码和数据取代,旧的程序不再执行。
exec函数族的成员
exec
函数族的成员有以下几种:
execl
execv
execle
execve
execlp
execvp
1. execl
int execl(const char *path, const char *arg, ..., (char *)NULL);
path
: 要执行的程序的路径。arg, ...
: 传递给程序的参数列表,最后必须是一个NULL
指针。
2. execv
int execv(const char *path, char *const argv[]);
path
: 要执行的程序的路径。argv[]
: 参数列表,最后一个元素必须是NULL
。
3. execle
int execle(const char *path, const char *arg, ..., (char *)NULL, char *const envp[]);
path
: 要执行的程序的路径。arg, ...
: 传递给程序的参数列表,最后必须是一个NULL
指针。envp[]
: 一个指向环境变量数组的指针。
4. execve
int execve(const char *path, char *const argv[], char *const envp[]);
path
: 要执行的程序的路径。argv[]
: 参数列表,最后一个元素必须是NULL
。envp[]
: 环境变量列表。
5. execlp
int execlp(const char *file, const char *arg, ..., (char *)NULL);
file
: 要执行的程序的名称或路径。arg, ...
: 传递给程序的参数列表,最后必须是一个NULL
指针。
execlp
与execl
类似,但它会在PATH
环境变量中搜索可执行文件。
6. execvp
int execvp(const char *file, char *const argv[]);
file
: 要执行的程序的名称或路径。argv[]
: 参数列表,最后一个元素必须是NULL
。
execvp
与execv
类似,但它会在PATH
环境变量中搜索可执行文件。
minishell:通过execvp调用系统的命令来执行。
#include<stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include<string.h>
#include<stdlib.h>
#include <sys/wait.h>
int main(int argc , char *argv[])
{
while(1)
{
printf("myshell>: ");
char buf[1024] = {0};
char *args[5] = {0};
fgets(buf,sizeof(buf),stdin);
buf[strlen(buf) - 1 ] = '\0';
args[0] = strtok(buf," ");
args[1] = args[0];
if(!args[0])
{
continue;
}
if(strcmp(args[0],"quit") == 0 || strcmp(args[0],"exit") == 0)
{
return 0;
}
else
{
int i = 0;
while( i < 3)
{
args[i + 1] = strtok(NULL," ");
++i;
}
pid_t pid = fork();
if(pid == -1)
{
perror("fork error");
}
if(pid > 0)
{
wait(NULL);
continue;
}
else if( pid == 0)
{
if (execvp(args[0],args))
{
perror("exec fail");
return 0;
}
}
}
}
return 0;
}
二、进程的结束
进程的终止可以分为正常终止和异常终止两种情况
正常终止:
-
main 函数中的 return:
- 当
main()
函数中执行return
时,进程会正常终止,并返回指定的退出状态码。实际上,这等同于调用exit()
函数。
- 当
-
exit()
函数:exit()
是一个标准库函数,它会执行 I/O 缓冲区的刷新工作,关闭所有打开的文件,并调用通过atexit()
注册的清理函数。在执行这些清理操作后,进程会正常退出。
-
_exit()
_exit()
,它们会立即终止进程,不执行任何 I/O 缓冲区的刷新,也不调用任何清理函数或处理器注册函数(如通过atexit()
注册的函数)。这两个函数之间的区别很小,通常在大多数系统上是等效的。
-
程序执行完毕:
- 如果一个程序运行到末尾(即
main()
函数的最后一行代码执行完毕),而没有显式的return
或exit()
调用,进程也会正常终止,返回值通常是 0。
- 如果一个程序运行到末尾(即
异常终止:
-
abort()
函数:abort()
是一个标准库函数,用于立即终止进程。它不会调用atexit()
注册的清理函数,也不会刷新 I/O 缓冲区。调用abort()
后,通常会生成一个核心转储文件(core dump)用于调试。
-
信号(
signal
):- 进程可以通过信号机制被外部或内部的信号终止。例如,使用
kill
命令发送SIGKILL
信号可以强制终止进程。其他常见的信号包括SIGTERM
(请求终止进程)、SIGINT
(通常由 Ctrl+C 产生)和SIGSEGV
(段错误)。
- 进程可以通过信号机制被外部或内部的信号终止。例如,使用
atexit
函数
用于在程序正常终止时执行特定的清理操作。它允许你注册一个或多个函数,这些函数会在 exit()
被调用或程序正常结束之前自动执行。atexit
函数通常用于资源的清理工作,比如释放动态分配的内存、关闭打开的文件、清理临时数据等。
int atexit(void (*func)(void));
- 当你调用
atexit()
注册一个函数后,程序在正常终止时会按后进先出的顺序(LIFO)依次调用这些注册的函数。 - 如果你注册了多个函数,那么最后注册的函数会第一个执行,依次往前执行之前注册的函数。
返回值
- 如果函数成功注册,
atexit
返回 0。 - 如果注册失败,返回一个非零值。
wait
函数
用于使父进程等待其子进程结束。这个函数通常在Unix/Linux系统的程序中使用。它的主要作用是防止僵尸进程的产生,确保父进程能获得子进程的退出状态。
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
status
:这是一个指向整数的指针,用于存储子进程的退出状态。如果传递NULL
,则不关心退出状态。- 成功时,返回结束的子进程的进程ID(PID)。
- 如果没有子进程,返回
-1
,并且设置errno
来指示错误原因。