进程

进程基础概念

Linux进程是计算机运行中的一个程序的实例。每个进程都是计算机中一个独立的执行单元,具有自己的内存空间、代码、数据和打开的文件。Linux使用进程作为执行任务的基本单位,可以同时运行多个进程,并采用时间片轮转调度算法来分配CPU时间给每个进程。如果系统中某个进程崩溃,它不会影响到其余的进程。每个进程运行在其各自的虚拟地址空间中,进程之间可以通过由内核控制的机制互相通讯。

程序与进程的区别

程序是一组指令和数据的集合,以文本文件的形式存储在存储介质上。程序本身不具备执行能力,只有在操作系统的帮助下,通过创建进程并加载到内存中,才能变成可执行的实体。

进程是程序的一个执行实例。当程序被加载到内存中,并被操作系统调度执行时,就成为了一个进程。

二者区别:

  • 程序是静态的,以文本文件的形式存在于存储介质中;而进程是程序在内存中的一次执行实例。
  • 程序是代码的逻辑表达,描述了一组指令和数据;而进程是程序在运行时的动态实体,具有执行能力。
  • 程序只是一种资源,不占用系统的 CPU、内存和其他资源;而进程是操作系统分配的资源单位,占用系统的 CPU、内存和其他资源。
  • 同一个程序可以有多个进程实例,在不同的时间或环境中执行;而一个进程只对应一个程序实例。

tast struct数据结构

为了便于管理系统中的进程,每个进程用一个 task_struct 数据结构来表示。Task Struct 队列是用来管理和调度进程的,队列中包含了指向系统中所有 task_struct结构的指针。当创建一个新进程时,内核会为该进程创建一个 task_struct 数据结构,并将其加入至队列中。系统中还有一个当前进程的指针,用来指向当前运行进程的结构。

task_struct 数据结构用于保存系统信息,主要包括:

  • 进程调度信息:调度器需要这些信息以便判定系统中哪个进程最迫切需要运行。
  • 进程状态:进程在执行过程中会根据环境来改变状态。主要包括:运行状态、等待状态、停止状态、僵尸状态。
  • 进程标识符:系统中每个进程都有进程一个标志。每个进程还有一个用户与组标志,它们用来控制进程对文件和设备的访问权限。
  • 文件系统:进程可以自由地打开或者关闭文件,进程的 task_struct 结构中包含一个指向文件描述符的指针。两个指向VFS索引节点的指针,第一个索引节点是进程的根目录,第二个节点是当前的工作目录。两个VFS索引节点都有一个计数字段用来表明指向节点的进程数,当多个进程引用它们时,它的值就增加。
  • 进程关系:Linux系统中所有进程都是相互联系的。除了初始化进程外,所有进程都有一个父进程。新进程不是被创建,而是被复制,或者从以前的进程克隆而来。每个进程对应的task_struct结构中包含有指向其父进程和兄弟进程(具有相同父进程的进程)以及子进程的指针。
  • 时间和定时器:系统需要记录进程的创建时间以及在其生命期间消耗的CPU时间。
  • 进程间通信:Linux支持经典的Unix IPC机制。
  • 虚拟内存:多数进程都有一些虚拟内存,linux核心必须跟踪虚拟内存与系统物理内存的映射关系。
  • 处理器的内容:进程被挂起时,进程的上下文,所有的CPU相关的状态必须保存在它的task_struct 结构中。当调度器重新调度该进程时,所有上下文被重新设定。
  • 绑定的终端

进程状态以及状态切换

进程在生存周期中的其状态时变化的。下面是Linux操作系统的进程的几种常见的状态:

  • 运行状态,此状态下进程正在运行或者是准备运行状态(即就绪状态)。用字母R表示。
  • 等待状态,进程正在等待事件的发生或者等待某种系统资源。等待进程可分为可中断等待和不可中断等待。
    • 可中断等待:可以被信号(signal)中断。用字母S表示。
    • 可中断等待:不受信号干扰,直到硬件状态发生改变。用字母D表示。
  • 停止状态:进程被停止,通常是收到了一个控制信号或者正在被跟踪调试。用字母T表示。
  • 僵尸状态:进程由于某种原因已经终止或结束,但在进程表项中仍有记录。用字母Z表示。

用户模式与内核模式

在linux系统中,进程的执行模式划分为用户模式和内核模式。如果当前运行的是应用程序或者内核之外的系统程序,那么对应进程就在用户模式下运行。如果在程序执行过程中出现系统调用或者发生中断事件,就要运行操作系统核心程序,进程模式就切换为内核模式。

按照进程的功能和运行的程序分类,进程可以划分为两大类:一类是系统进程,只运行在内核模式,执行操作系统代码,完成一些管理性的工作,例如内存分配和进程切换,另外一个类是用户进程,通常在用户模式中执行,并通过系统调用或在出现中断、异常时进入内核模式。用户进程既可以在用户模式下运行,也可以在内核模式下运行。

进程调度

CPU资源是有限的,那么在调度进程时,每个进程只允许运行很短的时间,当这个时间用完之后,系统将选择另一个进程来运行,原来的进程必须等待一段时间以继续运行,这段时间称为时间片。 

Linux使用基于优先级的简单调度算法来选择下一个运行进程。当选定新进程后,系统必须将当前进程的状态,处理器中的寄存器以及上下文状态保存到 task_struct 结构中。同时它将重新设置新进程的状态并将系统控制权交给此进程。为了将CPU时间合理的分配给系统中每个可执行进程,调度管理器必须将这些时间信息也保存在 task_struct 中。

进程空间

进程空间是操作系统为每个运行的进程分配的独立内存空间。每个进程都有自己的地址空间,包括代码、数据和堆栈等区域。通过进程空间隔离,操作系统可以保护每个进程的内存访问,使得进程之间互不干扰,提高系统的稳定性和安全性。

各区域含义:

  • 代码段:存放进程运行的可执行代码。
  • 已初始化数据段:分为 ro 和 rw 段,存放进程的全局变量。例如 char* p = "hello","hello"将存放在 ro 段,p存放在 rw 段。
  • bss数据段:存放未初始化静态变量和全局变量,bss段中的数据在程序开始执行前会被系统自动初始化为0或者空值。
  • 堆:存放进程运行时动态分配的内存。
  • 栈:存放进程运行时的函数调用、局部变量等。栈区是自动管理的,每当一个函数被调用,相关的参数和局部变量就会被压入栈中,函数调用结束后栈会自动释放这些内存。

环境变量

环境变量是在操作系统中设置的一组全局变量,它们可以为应用程序提供配置信息、路径设置、临时数据等。每个进程都有它所运行的的一个环境变量,环境变量一般是存放在内存的用户空间的一个环境变量表中,这个环境变量表是在进程生成时,从父进程的环境变量表中拷贝一份。

获取环境变量表

获取环境变量有两种方式:

  • 通过main()函数参数传递
  • 通过全局变量 environ 

通过main()函数参数传递

 1 #include<stdio.h>
 2 
 3 int main(int argc, char** argv, char** env)
 4 {
 5     int i = 0;
 6     while(env[i] != NULL)
 7     {
 8         printf("%s\n", env[i++]);
 9     }
10     return 0;
11 }

通过全局变量 environ 

libc库中定义的全局变量 char** environ 指向环境变量表,environ没有包含在任何头文件中,所以在使用时要用 extern 声明。例如:

 1 #include<stdio.h>
 2 
 3 int main(int argc, char** argv, char** env)
 4 {
 5     extern char** environ;
 6     int i = 0;
 7     while(environ[i] != NULL)
 8     {
 9         printf("%s\n", environ[i++]);
10     }
11     return 0;
12 }

获取环境变量-getenv()函数

getenv()函数是一个C语言函数,用于获取某个环境变量的值。该函数定义如下:

char *getenv(const char *name);

参数说明:

  • name:要获取的环境变量的名称。

返回值:

若找到指定名称的环境变量,并且其值非空,则返回一个指向环境变量值的指针。若未找到指定名称的环境变量,则返回空指针。

设置环境变量-setenv()函数

setenv()函数是一个C语言函数,用于设置环境变量的值。该函数定义如下:

int setenv(const char *name, const char *value, int overwrite);

参数说明:

  • name:要设置的环境变量的名称。
  • value:要设置的环境变量的值。
  • overwrite:指定是否允许覆盖已存在的环境变量。取值为非0时,表示允许覆盖;取值为0时,则不允许覆盖。

返回值:

如果设置环境变量成功,则返回 0。如果设置环境变量失败,则返回 -1,并设置相应的错误码 errno。

清除环境变量-unsetenv()函数

unsetenv()函数是一个C语言函数,用于删除指定的环境变量。该函数定义如下:

int unsetenv(const char *name);

参数说明:

  • name:要删除的环境变量的名称。

返回值:

如果成功删除指定名称的环境变量,则返回值为 0。如果删除失败或指定名称的环境变量不存在,则返回值为 -1,并设置相应的错误码 errno。

进程管控

进程ID

  • 进程标识号PID(process ID):用于标识进程。
  • 用户标识号UID(user ID):用于标识正在运行进程的用户。
  • 用户组标识号GID(group ID): 用于标识正在运行进程的用户所属的组ID。
#include<unistd.h>

int getuid();           //获取进程的实际用户ID。
int geteuid();          //获取进程的有效用户ID。

int getgid();           //获取进程的用户所属的实际用户组ID。
int getegid();          //获取进程的用户所属的有效用户组ID。

int getpid();           //获取当前进程的ID。
int getppid();          //获取父进程的ID

实际用户ID(RUID)用于标识用户的真实身份。当用户登录系统时,操作系统会为其分配一个实际用户ID,通常是唯一且不可更改的。

有效用户ID(EUID)用于标识用户在特定上下文中的有效权限。当用户执行一个程序时,操作系统会检查该程序文件的所有者和用户权限,并使用文件的有效用户ID来决定用户在执行该程序时具有的权限。

例子:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 
 4 int main(int argc, char** argv)
 5 {
 6     printf("uid = %d\n", getuid());
 7     printf("euid = %d\n", geteuid());
 8     printf("gid = %d\n", getgid());
 9     printf("egid = %d\n", getegid());
10     return 0;
11 }

终端:

$ gcc test.c        //编译
$ sudo chown root:root a.out      //修改所有者
$ sudo chmod u+s g+s a.out        //添加suid位和sgid位
$ ./a.out           //运行
uid = 1000
euid = 0
gid = 1000
egid = 0

进程创建-fork() 函数

在Linux系统中,用户创建一个进程的唯一方法就是使用系统调用fork()。内核为了完成此次系统调用需要进行以下几步操作:

  1.  为新进程在进程表中分配一个表项 task_struct。
  2. 给子进程一个唯一的进程标识号(PID)。
  3. 复制一个父进程的进程表项的副本给子进程。内核初始化子进程的进程表项时,是从父进程处拷贝的。所以子进程拥有父进程一样的 uid、euid、gid,用于计算优先权的 nice 值、当前目录、当前根、用户文件描述符表等。
  4. 把与父进程相连的文件表和索引节点表的引用数加1,这些文件自动地与该子进程相连。

fork() 函数创建一个新的进程,该进程是原始进程的副本,包括它的代码、数据和资源。子进程和父进程几乎是相同的,只有少量的差异。该函数定义如下:

#include <unistd.h>

pid_t fork(void);

返回值:

fork() 函数比较特别,该函数一次调用但会返回两次。在父进程中,fork返回新创建子进程的进程ID。在子进程中,fork返回0。如果子进程创建失败,则fork函数返回-1。

案例1:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 
 4 int main(int argc, char** argv)
 5 {
 6     printf("main process id = %d\n", getpid());
 7     pid_t pid = fork();
 8     if(pid == -1)
 9     {
10         perror("fork");
11         return -1;
12     }
13     else if(pid == 0)
14     {
15         printf("child process id = %d, parent id = %d\n",
16                 getpid(), getppid());
17     }
18     else
19     {
20         sleep(1);
21         printf("main process id = %d\n", getpid());
22     }
23     return 0;
24 }

输出:

main process id = 5504
child process id = 5505, parent id = 5504
main process id = 5504

上述代码是fork()函数简单的用法,当pid等于0的时候,表明是当前进程派生出来的子进程。如果返回的pid不等于1,表明派生操作成功 并且返回值就是新的子进程PID。在代码 pid=fork() 之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的 text 代码段完全相同。

案例2:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 
 4 int main(int argc, char** argv)
 5 {
 6     printf("main process id = %d\n", getpid());
 7     pid_t pid = fork();
 8     if(pid == -1)
 9     {
10         perror("fork");
11         return -1;
12     }
13     else if(pid == 0)
14     {
15         int i = 0;
16         while(i++ < 10)
17         {
18             printf("child process id = %d, i = %d\n", getpid(), i);
19             sleep(1);
20         }
21     }
22     else
23     {
24         int i = 0;
25         while(i++ < 10)
26         {
27             printf("main process id = %d, i = %d\n", getpid(), i);
28             sleep(1);
29         }
30     }
31     printf("process end, pid = %d\n", getpid());
32     return 0;
33 }

输出:

main process id = 5632
main process id = 5632, i = 1
child process id = 5633, i = 1
main process id = 5632, i = 2
child process id = 5633, i = 2
...
main process id = 5632, i = 10
child process id = 5633, i = 10
process end, pid = 5632
process end, pid = 5633

从输出的结果可以看到,子进程的输出和父进程的输出是交替输出的。当进程进入睡眠模式时,内核会调度新的进程进入运行模式,这样使得两个进程看上去像是同步执行。值得关注的是,process end 在最后输出了2遍,这说明fork()函数之后的代码,两个进程都会执行,这也印证了两个进程的 text 代码段是完全相同的。除了代码段,文件描述符表同样会被复制。

案例3:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<fcntl.h>
 4 #include<sys/stat.h>
 5 
 6 int main(int argc, char** argv)
 7 {
 8     int fd = open("log", O_RDWR);
 9     if(fd == -1)
10     {
11         perror("open");
12         return -1;
13     }
14 
15     pid_t pid = fork();
16     if(pid == -1)
17     {
18         perror("fork");
19         return -1;
20     }
21     else if(pid == 0)
22     {
23         char buf[32] = {0};
24         int nRet = read(fd, buf, 5);
25         buf[nRet] = '\0';
26         printf("read : %s, lseek = %ld\n", buf, lseek(fd, 0, SEEK_CUR));
27     }
28     else
29     {
30         sleep(1);
31         char buf[32] = {0};
32         int nRet = read(fd, buf, 5);
33         buf[nRet] = '\0';
34         printf("read : %s, lseek = %ld\n", buf, lseek(fd, 0, SEEK_CUR));
35     }
36     return 0;
37 }

输出:

$ cat log
0123456789abcedf
$ ./a.out
pid = 6076, read : 01234, lseek = 5
pid = 6075, read : 56789, lseek = 10

进程退出-exit() 函数

exit() 函数用于终止调用的进程。该函数定义如下:

#include <stdlib.h>

void exit(int status);

参数说明:

  • status:指定程序的终止状态值。通常情况下,0代表程序正常终止,非零值代表程序异常终止或执行出现错误。

其他:

  • 在调用 exit() 函数后,程序将立即终止,不再执行之后的代码。
  • exit() 函数会自动执行一些清理操作,比如关闭打开的文件描述符等。
  • 在程序终止时,Linux会将状态码 status 返回给调用者,供调用者检查程序的执行状态。
  • exit()函数会调用各种终止处理程序(exit handlers),这些处理程序可以进行一些额外的清理操作。

案例:

 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 
 4 void my_exit()
 5 {
 6     printf("my_exit\n");
 7 }
 8 
 9 int main(int argc, char** argv)
10 {
11     //注册退出函数
12     if(atexit(my_exit) != 0)
13         printf("can't register my_exit\n");
14 
15     printf("main is done!\n");
16     return 0;
17 }

输出:

main is done!
my_exit

僵尸进程与孤儿进程

僵尸进程(Zombie Process):当一个子进程结束执行后,但其父进程没有及时调用wait()或类似的系统调用来获取子进程的退出状态时,子进程的退出状态和其他一些相关信息将会被内核保留。这时,子进程成为一个僵尸进程。僵尸进程占用系统资源(如进程表项),但没有任何可执行的代码在运行。僵尸进程并不具有实际运行的代码和上下文,它只是一个保留的数据结构,用于存储子进程的退出状态,以便父进程稍后获取。

孤儿进程(Orphan Process):当一个父进程在子进程结束之前就提前终止(或异常终止)时,子进程成为一个孤儿进程。孤儿进程由于父进程的退出或终止而无法获得父进程的处理,它将由 init 进程(进程ID为1的特殊进程)接管,并成为 init 进程的子进程。init 进程将负责回收孤儿进程的资源,使其不再占用系统资源。

等待进程

子进程终止的时候,必须由父进程回收其进程表 task_struct,否则进程将处于僵尸状态直到被回收。如果父进程在子进程终止前已经终止,那么该进程的所有子进程由init进程回收。

下面用一个简单的例子说明僵尸进程:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 
 4 int main(int argc, char** argv)
 5 {
 6     pid_t pid = fork();
 7     if(pid == -1)
 8     {
 9         perror("fork");
10         return -1;
11     }
12     else if(pid == 0)
13     {
14         sleep(2);
15         printf("pid = %d, child process is done\n", getpid());
16     }
17     else
18     {
19         printf("main process\n");
20         sleep(60);
21     }
22 }

上述代码子进程休眠2秒后退出进程,父进程不回收子进程的资源,输出如下:

main process
pid = 6972, child process is done

再开一个终端,通过ps命令查看:

$ ps -aux
...
test 6939  0.0  0.1  13956  5120 pts/1 Ss 17:36 0:00 bash
test 6971  0.0  0.0   2496   508 pts/0 S+ 17:39 0:00 ./a.out
test 6972  0.0  0.0      0     0 pts/0 Z+ 17:39 0:00 [a.out] <defunct>
test 6973  0.0  0.0  14780  3520 pts/1 R+ 17:39 0:00 ps -aux

可以看到,进程 6972 当前处于"Z+"僵尸状态。父进程退出,我们再查看子进程时,发现子进程已经被回收了。只是因为当进程退出的时候,该进程所有子进程将被移交给 init 进程,init 进程将回收这个僵尸进程。

wait()函数

wait()函数用于在父进程中等待子进程的状态改变。该函数会阻塞父进程,直到一个子进程退出或被信号中断。一旦子进程状态改变,wait() 函数将返回子进程的进程ID(PID)和子进程的退出状态。函数定义如下:

#include <sys/wait.h>

pid_t wait(int *status);

参数说明:

  • status:指向整型变量的指针,用于存储子进程的退出状态。

返回值:

返回子进程的PID。如果调用失败或当前进程没有子进程,返回值为 -1,并设置相应的错误码。

用于检查子进程退出的宏定义:

WIFEXITED(status)      //通过系统调用_exit或exit()函数退出,该值为真
WIFSIGNALED(status)    //由信号(signal)导致退出时,该值为真

WEXITSTATUS(status)    //如果 WIFEXITED 为真,该宏获取 exit 设置的退出码
WTERMSIG(status)       //如果 WIFSIGNALED 为真,该宏获取具体信号值

案例:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 #include<sys/wait.h>
 4 #include<stdlib.h>
 5 
 6 int main(int argc, char** argv)
 7 {
 8     printf("main process id = %d\n", getpid());
 9 
10     pid_t pid = fork();
11     if(pid == -1)
12     {
13         perror("fork");
14         return -1;
15     }
16     else if(pid == 0)
17     {
18         for(int i = 0; i < 10; i++)
19         {
20             printf("child process %d, id = %d\n", i, getpid());
21             sleep(1);
22         }
23         exit(10);
24     }
25     else
26     {
27         int status;
28         int pid = wait(&status);
29         if(WIFEXITED(status))
30         {
31             printf("child process has exit by exit code : %d\n", WEXITSTATUS(status));
32         }
33         else if(WIFSIGNALED(status))
34         {
35             printf("child process has exit by signal : %d\n", WTERMSIG(status));
36         }
37         sleep(20);
38     }
39 
40     return 0;
41 }

正常等待程序退出:

main process id = 9058
child process 0, id = 9059
child process 1, id = 9059
...
child process 9, id = 9059
child process has exit by exit code : 10

子进程运行期间通过ps命令查看:

$ ps -aux

test 9058  0.0  0.0   2496   512 pts/0    S+   10:02   0:00 ./a.out
test 9059  0.0  0.0   2496    84 pts/0    S+   10:02   0:00 ./a.out

子进程退出后,发现没有僵尸进程:

$ ps -aux

test 9058  0.0  0.0   2496   512 pts/0    S+   10:02   0:00 ./a.out

在终端A中执行该程序:

$ ./a.out

在终端B中发送kill -9 信号:

$ kill -9 9068

终端A中最终输出结果为:

main process id = 9067
child process 0, id = 9068
child process 1, id = 9068
child process 2, id = 9068
child process 3, id = 9068
child process 4, id = 9068
child process has exit by signal : 9

waitpid()函数

waitpid()函数与wait函数的区别是,waitpid()函数提供更加丰富的参数设定,使用时更加灵活,可以更精确地控制等待的子进程。该函数定义如下:

#include <sys/types.h>
#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *status, int options);

参数说明:

  • pid:指定要等待的子进程。有如下几种取值:
    • pid=1时:等待任何子进程退出,相当于调用wait()。
    • pid=0时:等待进程组ID等于当前进程的进程组ID的子进程退出。
    • pid>0时:等待进程ID等于参数pid的子进程退出。
    • pid < 1时:等待进程组ID等于pid的绝对值的进程组子进程退出。
  • status:指向整数的指针,用于存储子进程的退出状态或其他相关信息。
  • options:控制选项。有如下几种取值:
    • 0:不设置控制选项
    • WNOHANG:非阻塞方式,即如果没有子进程退出,函数立即返回而不阻塞。
    • WUNTRACED:如果子进程进入暂停状态(例如收到SIGSTOP信号),函数将返回。
    • WCONTINUED:如果子进程由暂停状态被唤醒(SIGCONT),函数将返回。

返回值:

返回子进程的PID。如果调用失败或当前进程没有子进程,返回值为 -1,并设置相应的错误码。

用于检查子进程退出的宏定义:

WIFEXITED(status)      //通过系统调用_exit或exit()函数退出,该值为真
WIFSIGNALED(status)    //由信号(signal)导致退出时,该值为真
WIFSTOPPED(status)     //指定了 WUNTRACED 选项且进程被暂停时,该值为真
WIFCONTINUED(status)   //指定了 WCONTINUED 选项且子进程由停止态转为就绪态,该值为真

WEXITSTATUS(status)    //如果 WIFEXITED 为真,该宏获取 exit 设置的退出码
WTERMSIG(status)       //如果 WIFSIGNALED 为真,该宏获取具体信号值
WSTOPSIG(status)       //如果 WIFSTOPPED 为真,该宏获取导致子进程停止的信号

执行其他程序

在派生一个子进程来完成某项工作的时候,经常需要让另外一个程序来完成。Linux提供一系列接口来加载新程序的代码、数据和堆栈,并将程序的入口点设置为新程序的入口地址。它还会设置新程序的命令行参数和环境变量。一旦调用这些接口,该进程的原始代码、数据和状态将被新程序替换。有如下一些接口:

#include <unistd.h>

 int execl(const char* path, const char *arg, ...);
 int execlp(const char* file, const char *arg, ...);
 int execle(const char* path, const char *arg, ..., char * const envp[]);
 int execv(const char* path, char* const argv[]);
 int execvp(const char* file, char * const argv[]);
 int execve(const char* filename, char* const argv[], char* const envp[]);

这几个函数功能都是相同的,只是传递的参数不一致。这些函数都以exec开头,后面字母代表不同含义:

  • l:表明传递给程序的参数是一个参数列表。第一个参数必须是要执行程序,最后一个参数必须是NULL。
  • p:第一个参数可以是相对路径或程序名,如果无法立即找到要执行的程序,那么就在环变量PATH指定的路径中搜索。
  • v:表明传递给程序的参数用一个字符串数组来传递。第一个参数必须是要执行程序,最后一个参数必须是NULL。
  • e:用户可以自己设置程序接收一个设置环境变量的数组。

事实上,只有 execve()函数是真正的系统调用,其它五个函数最终都调用 execve()函数。

案例:

 1 #include<stdio.h>
 2 #include<unistd.h>
 3 
 4 int main(int argc, char** argv)
 5 {
 6     printf("main process id = %d\n", getpid());
 7     execlp("/bin/ls", "ls", "-la", "./", NULL);
 8     printf("main end!\n");
 9     return 0;
10 }

输出:

$ ./a.out 
main process id = 9824
总用量 32
drwxrwxr-x 2 test test 4096  12月   8 14:44 .
drwxrwxr-x 3 test test 4096  12月   7 10:19 ..
-rwxrwxr-x 1 test test 16824 12月   8 14:44 a.out
-rw-rw-r-- 1 test test 449   12月   8 14:44 test.c

会发现没有输出 main end! 。这是因为在执行exec函数后,进程中 原先的代码段被替换为 ls 程序的代码段。

 

 

posted @ 2024-02-18 19:32  西兰花战士  阅读(18)  评论(0编辑  收藏  举报