一、什么是进程
进程是计算机中正在运行的程序的实例。它是操作系统分配资源和调度任务的基本单位。每个进程都有自己的地址空间、内存、文件描述符、线程等资源。
进程可以包括一个或多个线程,这些线程共享同一进程的资源,并在同一进程中执行并发任务。
进程之间是相互独立的,彼此之间不能直接访问对方的资源,但可以通过进程间通信来进行数据交换。
操作系统通过分配时间片给各个进程,使得它们在单个处理器或多个处理器上能够并发地运行。
进程的创建、结束和切换是由操作系统负责管理的。
二、创建进程
1、查看进程 id
每个进程都会对应一个唯一 id,这个 id 可以用函数 getpid() 获得,一个进程的父进程可以用 getppid() 获得。如下面代码:
printf("子进程执行:%d,父进程是:%d\n",getpid(),getppid());
终端显示结果如下:
子进程执行:7168,父进程是:7167
2、fork
pid_t pid = fork();
fork 用于创建子进程,返回值为 -1 代表创建出错,返回 0 代表成功创建的子进程 id ,返回非零正数代表父进程 id。
fork 在创建子进程后,后面的代码在父子进程中各执行一次。比如下面代码:
pid_t pid = fork(); //后面的代码在父子进程中各执行一次
printf("此次pid值为:%d\n",pid);
if (pid < 0)
{
printf("进程出错\n");
return 1;
} else if (pid == 0)
{
printf("子进程执行:%d,父进程是:%d\n",getpid(),getppid());
} else
{
printf("父进程执行:%d,子进程是:%d\n",getpid(),pid);
}
终端显示结果如下:
此次pid值为:7168
父进程执行:7167,子进程是:7168
此次pid值为:0
子进程执行:7168,父进程是:7167
3、execve
系统调用 execve 函数,系统会调用一个全新的程序代替旧的程序,不执行下面代码,删除旧程序的所有变量,只保留进程号,父进程 id,进程组 id 等。函数原型如下:
int execve (const char *__path, char *const __argv[],char *const __envp[])
path:可执行程序的路径
argv:传入的参数
格式:程序的名称(执行程序的路径)+ 执行程序需要传入的参数 + NULL
envp:传递的环境变量,如果为绝对路径则不需要环境变量,如果传的是 ping这种命令,则需要环境变量,一般为path路径(echo $PATH)
格式:(PATH路径)+ NULL
return:返回值是-1表示跳转失败,成功不返回且不执行下面代码
如下面代码,目的是跳转到:/home/liuyuhui/c_code/process/ 目录下的 test 可执行文件,在运行后程序执行到这里会直接执行 test 中的代码。
char *args[] = {"/home/liuyuhui/c_code/process/test",name,NULL};
char *envs[] = {NULL};
int re = execve(args[0],args,envs);
if (re == -1)
{
perror("execve");
return 1;
}
4、waitpid
waitpid函数是一个用于等待子进程状态改变的系统调用函数。该函数可用于父进程等待子进程结束以解决如下等问题:
1、孤儿进程:通常子进程如果结束了需要被父进程回收,可是如果父进程先于子进程结束,子进程就会变成孤儿进程,系统会寻找父进程的父进程,直到找到没结束的进程,然后将该进程挂载上去。这样该子进程脱离了终端,用户就无法通过输入设备进行操控;
2、僵尸进程:在每个进程退出的时候,内核会释放所有的资源,包括打开的文件,占用的内存等。但是仍保留一部分信息(进程号PID,退出状态,运行时间等)。直到父进程通过 wait 或 waitpid 来取时才释放。如果父进程没有调用 wait 或者 waitpid 就会产生僵尸进程。僵尸进程会导致进程号一直被占用,但是系统所能使用的进程号是有限的,如果大量产生僵尸进程,将因没有可用的进程号而导致系统无法产生新的进程。
该函数原型如下:
pid_t waitpid(pid_t pid, int *status, int options);
- pid:指定等待的子进程ID。有以下几种取值:
- pid > 0:等待进程ID为pid的子进程。
- pid = -1:等待任意子进程。相当于 wait 函数。
- pid = 0:等待与调用进程属于同一进程组的任意子进程。
- pid < -1:等待进程组ID为pid绝对值的任意子进程。
- status:用于存储子进程的终止状态信息。如果不关心子进程的终止状态,可以将status设为NULL。如果status不为NULL,则可以使用一些宏来解析终止状态:
- WIFEXITED(status):如果子进程正常终止,则返回true。
- WEXITSTATUS(status):如果WIFEXITED为true,则返回子进程的退出状态。
- WIFSIGNALED(status):如果子进程因为信号而终止,则返回true。
- WTERMSIG(status):如果WIFSIGNALED为true,则返回导致子进程终止的信号编号。
- options:用于指定额外的选项。常用的选项有:
- WNOHANG:非阻塞模式,即如果没有子进程终止或状态没有改变,则立即返回0。
- WUNTRACED:除了等待终止的子进程,还等待暂停(stop)的子进程。
waitpid函数会阻塞调用进程,直到满足以下条件之一:
- 某个子进程终止。
- 某个子进程暂停(stop)并且没有被父进程忽略。
当waitpid返回时,返回值为子进程的PID,或者出错时的-1。
三、综合案例
如下代码,创建了两个进程,子进程使用 ping 命令进行五次网络诊断,父进程等待子进程运行完毕后再结束。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc, char const *argv[])
{
int subprocess;
printf("liuyuhui is study in the classroom\n");
pid_t pid = fork();
if (pid == -1)
{
perror("pid");
printf("\n");
return 1;
}else if (pid == 0)
{
//son_process
char *args[] = {"/usr/bin/ping","-c","5","www.atguigu.com",NULL};
char *envs[] = {NULL};
printf("liuyuhui open the internet ten times\n");
int exR = execve(args[0],args,envs);
if (exR == -1)
{
perror("execve");
printf("\n");
return 1;
}
}else{
//father_process
printf("father: %d wait son: %d\n",getpid(),pid);
waitpid(pid,&subprocess,0);
}
printf("liuyuhui login succeed\n");
return 0;
}
如下为终端运行结果:
liuyuhui is study in the classroom
father: 8581 wait son: 8582
liuyuhui open the internet ten times
PING www.atguigu.com.w.kunluncan.com (113.200.122.200) 56(84) bytes of data.
64 bytes from 113.200.122.200: icmp_seq=1 ttl=56 time=100 ms
64 bytes from 113.200.122.200: icmp_seq=2 ttl=56 time=225 ms
64 bytes from 113.200.122.200: icmp_seq=3 ttl=56 time=147 ms
64 bytes from 113.200.122.200: icmp_seq=4 ttl=56 time=168 ms
64 bytes from 113.200.122.200: icmp_seq=5 ttl=56 time=89.4 ms
--- www.atguigu.com.w.kunluncan.com ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4004ms
rtt min/avg/max/mdev = 89.378/145.816/224.748/49.025 ms
liuyuhui login succeed