Linux 进程 – 进程 ID、fork、execv、wait、waitpid C 函数
在本文中,我们将从进程 ID 的一个小解释开始,然后我们将快速跳转到实际方面,我们将讨论一些与进程相关的 C 函数,如 fork()、execv() 和 wait()。
进程 ID
进程 ID 是与进程关联的非负数的进程标识符。这些数字在系统中运行的进程中是唯一的。
进程 ID 的这种唯一性有时会被进程用来创建一些唯一的文件名。当一个进程从系统中终止时,它的进程 ID 可供重用。
但是在使进程 ID 可供重用之前,需要考虑一个特定的延迟。这是因为与现在终止的先前进程相关联的进程 ID 很可能以文件名等形式使用。因此,在重用相同的进程 ID 之前会添加延迟。
进程 ID 1 用于 init 进程。这是系统启动后启动的第一个进程。
init 进程的程序文件可以在 /etc/init 或 /sbin/init 中找到。init 进程是一个用户级进程,但以 root 权限运行,并负责在内核启动后将系统带入一个状态。init进程为了达到某种状态而读取的启动文件是
- /etc/rc*.d
- /etc/init.d
- /etc/inittab
进程 ID 0 属于系统的调度程序。它是一个内核级进程,负责系统内部发生的所有进程调度。
过程控制功能
fork() 函数
由 fork() 创建的新进程称为子进程,而原始进程(从中调用 fork())成为父进程。
函数 fork() 被调用一次(在父进程中)但它返回两次。一旦它在父进程中返回,而第二次它在子进程中返回。请注意,父进程和子进程的执行顺序可能因进程调度算法而异。所以我们看到在进程创建中使用了fork函数。
fork() 的签名是:
pid_t fork(void);
exec 函数族
另一组通常用于创建进程的函数是exec系列函数。这些函数主要用于需要从进程中运行现有二进制文件的情况。
例如,假设我们要在进程中运行“whoami”命令,那么在这种情况下,将使用 exec() 函数或该家族的其他成员。这里值得注意的一点是,通过调用任何 exec 系列函数,当前进程映像将被新进程映像替换。
该系列的一个常见成员是 execv() 函数。它的签名是:
int execv(const char *path, char *const argv[]);
注意:请参阅 exec 的手册页以查看该系列的其他成员。
wait() 和 waitpid() 函数
在某些情况下,当子进程终止或更改状态时,父进程应该知道子进程的状态更改或终止状态。在这种情况下,父进程使用诸如wait()之类的函数,父进程可以使用这些函数查询子进程的状态变化。
wait() 的签名是:
pid_t wait(int *status);
对于一个父进程有多个子进程的情况,有一个函数waitpid()可以被父进程用来查询特定子进程的变化状态。
waitpid() 的签名是:
pid_t waitpid(pid_t pid, int *status, int options);
默认情况下,waitpid() 仅等待终止的子进程,但此行为可通过 options 参数进行修改,如下所述。
pid 的值可以是:
- < -1 : 等待进程组 ID 等于 pid 绝对值的任何子进程。
- -1:等待任何子进程。
- 0:等待进程组ID与调用进程相同的任何子进程。
- > 0 : 等待进程 ID 等于 pid 值的子进程。
options 的值是以下常量中的零个或多个的 OR:
- WNOHANG :如果没有孩子退出,则立即返回。
- WUNTRACED :如果孩子停止了,也返回。即使未指定此选项,也会提供已停止的跟踪子项的状态。
- WCONTINUED :如果停止的孩子已通过传递 SIGCONT 恢复,则也返回。
有关 waitpid() 的更多信息,请查看此函数的手册页。
一个示例程序
这里我们有一个例子,我们使用了上述所有类型的函数。
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
int global; /* In BSS segement, will automatically be assigned '0'*/
int main()
{
pid_t child_pid;
int status;
int local = 0;
/* now create new process */
child_pid = fork();
if (child_pid >= 0) /* fork succeeded */
{
if (child_pid == 0) /* fork() returns 0 for the child process */
{
printf("child process!\n");
// Increment the local and global variables
local++;
global++;
printf("child PID = %d, parent pid = %d\n", getpid(), getppid());
printf("\n child's local = %d, child's global = %d\n",local,global);
char *cmd[] = {"whoami",(char*)0};
return execv("/usr/bin/",cmd); // call whoami command
}
else /* parent process */
{
printf("parent process!\n");
printf("parent PID = %d, child pid = %d\n", getpid(), child_pid);
wait(&status); /* wait for child to exit, and store child's exit status */
printf("Child exit code: %d\n", WEXITSTATUS(status));
//The change in local and global variable in child process should not reflect here in parent process.
printf("\n Parent'z local = %d, parent's global = %d\n",local,global);
printf("Parent says bye!\n");
exit(0); /* parent exits */
}
}
else /* failure */
{
perror("fork");
exit(0);
}
}
在上面的代码中,我尝试创建一个程序:
- 使用 fork() API 创建子进程
- 使用局部和全局变量来证明 fork 创建了父进程的副本,而子进程有自己的变量副本可以处理。
- 使用 execv API 调用“whoami”命令。
- 使用 wait() API 获取父节点中子节点的终止状态。请注意,此 API 保持父级的执行,直到子级终止或更改其状态。
现在,当执行上述程序时,它会产生以下输出:
$ ./fork
parent process!
parent PID = 3184, child pid = 3185
child process!
child PID = 3185, parent pid = 3184
child's local = 1, child's global = 1
himanshu
Child exit code: 0
Parent'z local = 0, parent's global = 0
Parent says bye!
- 点赞
- 收藏
- 关注作者
评论(0)