linux进程

进程的一些概念

程序

是指编译好的二进制文件,在磁盘上,占用磁盘内存,不占用系统资源,是一个静态的概念。

进程

被执行之后的程序叫做进程,不占用磁盘空间,需要消耗系统的内存,CPU资源,每个运行的进程的都对应一个属于自己的虚拟地址空间,这是一个动态的概念。

是操作系统分配资源的基本单位,在 /proc/pid目录下你可以看到每一个进程的详细信息,资源情况

虚拟地址空间

大小也由操作系统决定,32位的操作系统虚拟地址空间的大小为 2^32 字节,也就是 4G,64 位的操作系统虚拟地址空间大小为 2^64 字节.当我们运行磁盘上一个可执行程序, 就会得到一个进程,内核会给每一个运行的进程创建一块属于自己的虚拟地址空间,并将应用程序数据装载到虚拟地址空间对应的地址上。具体使用空间大小根据进程来,32位系统超过4G会error,也可能只使用了几kb。

并行与并发

并行与并发,宏观上来看就是cup同时处理多个任务。

并行:针多核cup言,真正的多个任务交由多个同时处理。

并发:单核cup,任务排队执行,同一时刻只有一个任务在处理。内存数据传递耗时远大于cpu处理耗时,避免cpu性能浪费,系统会强制让任务让出 cpu 资源,让cpu处理其他任务,如此反复,多个任务被cpu交替处理,但宏观上感觉它们在同时进行。

单道程序设计

所有进程一个一个排对执行。若 A 阻塞,B 只能等待,即使 CPU 处于空闲状态。(被淘汰)

多道程序设计

在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行。多道程序设计必须有硬件基础作为保证。

时钟中断

多道程序设计模型的理论基础,一种强制让进程让出 cpu 资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒,周期性的硬件行为。 操作系统中的中断处理函数,来负责调度程序执行。

CPU时间片

时间片即CPU分配给各个程序的时间,每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间

如果在时间片结束时进程还在运行,进程会被中断。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换到其他进程。

进程控制块 PCB

每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux

内核的进程控制块是 task_struct 结构体。

主要包括:

进程 id:每一个进程都一个唯一的进程 ID,类型为 pid_t, 本质是一个整形数
进程的状态:进程有不同的状态,状态是一直在变化的,有就绪、运行、挂起、停止等状态。
进程对应的虚拟地址空间的信息。
描述控制终端的信息,进程在哪个终端启动默认就和哪个终端绑定。
当前工作目录:默认情况下,启动进程的目录就是当前的工作目录
umask 掩码:在创建新文件的时候,通过这个掩码屏蔽某些用于对文件的操作权限。
文件描述符表:每个被分配的文件描述符都对应一个已经打开的磁盘文件
和信号相关的信息:在 Linux 中 调用函数 , 键盘快捷键 , 执行shell命令等操作都会产生信号。
阻塞信号集:记录当前进程中阻塞哪些已产生的信号,使其不能被处理
未决信号集:记录在当前进程中产生的哪些信号还没有被处理掉。
用户 id 和组 id:当前进程属于哪个用户,属于哪个用户组
会话(Session)和进程组:多个进程的集合叫进程组,多个进程组的集合叫会话。
进程可以使用的资源上限:可以使用 shell 命令 ulimit -a 查看详细信息。

内存管理单元 MMU

进程在运行过程中,程序内部所有的指令都是通过 CPU 处理完成的,CPU 只进行数据运算并不具备数据存储的能力,其处理的数据都加载自物理内存.

进程中的数据是通过 CPU 中的内存管理单元 MMU(Memory Management Unit)从进程的虚拟地址空间中映射到物理内存的。(回写数据)

主要功能:

1)虚拟内存。有了虚拟内存,可以在处理器上运行比实际物理内存大的应用程序。为了使用虚拟内存,操作系统通常要设置一个交换分区(通常是硬盘),通过将不活跃的内存中的数据放入交换分区,操作系统可以腾出其空间来为其它的程序服务。虚拟内存是通过虚拟地址来实现的。
2)内存保护。根据需要对特定的内存区块的访问进行保护,通过这一功能,我们可以将特定的内存块设置成只读、只写或是可同时读写。

进程的状态

三态模型和linux中的六种进程状态
在这里插入图片描述

在这里插入图片描述

三态模型

运行态(该时刻进程实际占用CPU)。
就绪态(可运行,但因为其他进程正在运行而暂时停止)。
阻塞态(除非某种外部事件发生,否则进程不能运行)。

linux中的六种进程状态,使用ps命令可以查看系统中所有运行进程的详细信息

               D    uninterruptible sleep (usually IO)
               R    running or runnable (on run queue)
               S    interruptible sleep (waiting for an event to complete)
               T    stopped by job control signal
               t    stopped by debugger during the tracing
               W    paging (not valid since the 2.6.xx kernel)
               X    dead (should never be seen)
               Z    defunct ("zombie") process, terminated but not reaped by its parent

(1)R (TASK_RUNNING),可执行状态。
running or runnable (on run queue),
正在运行的进程或在可运行进程队列(run_queue)中等待运行的进程处于该状态。它实际上包含一般操作系统原理教材中所谓进程三种基本状态中的运行态和就绪两种状态。
当CPU空闲时,进程调度程序只在处于该状态的进程中选择优先级最高的进程运行。Linux中运行态的进程可以进一步细分为3种:内核运行态、用户运行态和就绪态。

(2)S (TASK_INTERRUPTIBLE),可中断的睡眠状态(可中断阻塞状态)。
interruptible sleep (waiting for an event to complete),
处于这个状态的进程因为等待某某事件的发生(比如等待socket连接、等待信号量),而被挂起。这些进程pcb被放入对应事件的等待队列中。当这些事件发生时(由外部中断触发、或由其他进程触发),对应的等待队列中的一个或多个进程将被唤醒。

(3)D (TASK_UNINTERRUPTIBLE),不可中断的睡眠状态(不可中断阻塞状态)。
D:uninterruptible sleep (usually IO).
不可中断指的是进程不响应信号。处于不可中断阻塞状态的进程排成一个不可中断阻塞状态进程队列。该队列中的阻塞进程,不可被其他进程唤醒,只有被使用wake_up()函数明确唤醒时才能转换到可运行的就绪状态。
在某些时候,为避免进程与设备交互的过程被打断,造成设备陷入不可控的状态。进程会进入TASK_UNINTERRUPTIBLE状态总,这种状态存在时间比较短,通过ps命令基本上不可能捕捉到。

(4)T/t (TASK_STOPPED or TASK_TRACED),暂停状态或跟踪状态。
T:stopped by job control signal.
向进程发送一个SIGSTOP信号,它就会因响应该信号而进入TASK_STOPPED状态(除非该进程本身处于TASK_UNINTERRUPTIBLE状态而不响应信号)。

t:stopped by debugger during the tracing
当进程正在被跟踪时,它处于TASK_TRACED这个特殊的状态。“正在被跟踪”指的是进程暂停下来,等待跟踪它的进程对它进行操作。比如在gdb调试中对被跟踪的进程下一个断点,进程在断点处停下来的时候就处于TASK_TRACED状态。而在其他时候,被跟踪的进程还是处于前面提到的那些状态。

(5)Z (TASK_DEAD - EXIT_ZOMBIE),退出状态,(僵死状态,僵尸进程)。表示进程停止但尚未消亡的一种状态。此时进程已经结束运行并释放掉大部分资源,但父进程尚未收回其PCB。在进程退出时,将状态设为TASK_ZOMBIE,然后发送信号给父进程,由父进程再统计其中的一些数据后,释放它的task_struct结构。处于该状态的进程已经终止运行,但是父进程还没有询问其状态。

(6)X TASK_DEAD-EXIT_DEAD(退出状态),处于此状态的进程即将被销毁,EXIT_ DEAD非常短暂,几乎不可能通过ps命令捕捉到。

其他进程小状态

<:high-priority (not nice to other users),高优先级
N:low-priority (nice to other users),低优先级
L:has pages locked into memory (for real-time and custom IO),被锁入内存
s:is a session leader,进程领导者,其有子进程。
l:is multi-threaded (using CLONE_THREAD, like NPTL pthreads do),多线程(小写 L)
+:is in the foreground process group, 位于前台进程组。
I    Idle kernel thread    空闲 

getenv 函数获取环境变量值

char *getenv(const char *name); 
成功:返回环境变量的值;
失败:NULL (name 不存在)

setenv 函数设置环境变量的值

int setenv(const char *name, const char *value, int overwrite); 
成功:0;
失败:-1
参数 overwrite 取值: 1:覆盖原环境变量.0:不覆盖。该参数常用于设置新环境变量,

unsetenv 函数删除环境变量 name 的定义

int unsetenv(const char *name); 
成功:0;
失败:-1 
注意事项:name 不存在仍返回 0(成功),当 name 命名为"ABC="时则会出错。

fork函数创建子进程

pid_t fork(void); 
失败返回-1;
成功返回:
	父进程返回子进程的 ID(非负) 
	子进程返回 0 
pid_t 类型表示进程 ID,但为了表示-1,它是有符号整型。(0 不是有效进程 ID,init 最小,为 1)
注意:
	不是 fork 函数能返回两个值,而是fork后,fork函数变为两个,父子需【各自】返回一个。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

int main(int argc, char *argv[])
{
    printf("-------只打印一次-------\n");     // 在fork之前打印,父进程执行,只执行一次

    pid_t pid = fork();             // 创建子进程,此时会存在两个进程,fork函数会在两个进程中
返回不同的值
    if (pid == -1) {
        perror("fork error");
        exit(1);

    } else if (pid == 0) {          // 子进程

        printf("---我会在子进程打印\n");

    } else if (pid > 0) {           // 父进程

        printf("---我会在父进程打印,子进程的pid为:%d\n", pid);
    }

    printf("====我会被打印两次======end of file\n");  // 父子进程各自执行一次.

    return 0;
}
[lc@lc133 fork]$ gcc fork.c
[lc@lc133 fork]$ ./a.out
-------只打印一次-------
---我会在父进程打印,子进程的pid为:3968
====我会被打印两次======end of file
---我会在子进程打印
====我会被打印两次======end of file

父子进程的先后:

创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。

父子进程的相同点:

刚fork后。 拷贝完成之后(注意这个时间点),两个地址空间中的用户区数据是相同的,用户区:data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式,文件描述符表。

父子进程不同:

进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集,代码区代码一样,父子进程的执行逻辑不一样,所以地址空间拷贝完成之后,全局数据区 , 栈区 , 堆区 , 动态库加载区(内存映射区) 数据会各自发生变化,由于地址空间是相互独立的,因此不会互相覆盖数据。

父子进程共享:

读时共享、写时复制。———————— 全局变量。

	1. 文件描述符 2. mmap映射区。

获取进程id函数

getpid 函数pid_t getpid(void);获取当前进程 ID
getppid 函数pid_t getppid(void);获取当前进程的父进程 ID区分一个函数是“系统函数”还是“库函数”依据:
是否访问内核数据结构
是否访问外部硬件资源 二者有任一 → 系统函数;二者均无 → 库函数
getuid 函数uid_t getuid(void);获取当前进程实际用户 ID
uid_t geteuid(void);获取当前进程有效用户 ID
getgid 函数gid_t getgid(void);获取当前进程使用用户组 ID
gid_t getegid(void);获取当前进程有效用户组 ID

循环创建子进程

[lc@lc133 fork]$ more fork2.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char *argv[])
{
        int n = 5, i;                           //默认创建5个子进程


        for(i = 0; i < n; i++)  //出口1,父进程专用出口
                if(fork() == 0)    //只会在父进程中循环,子进程返回值==0,会break
                        break;                  //出口2,子进程出口,i不自增

        if(n == i){
                sleep(n);   //sleep为了输出美观
                printf("我是父进程,我的 pid = %d\n", getpid());
        } else {
                sleep(i);
                printf("我是子进程,我是第%d个子进程, 我的pid = %d,父进程的ppid= %d\n", i+1, getpid(),getppid());

        }

        return 0;
}

[lc@lc133 fork]$ gcc fork2.c
[lc@lc133 fork]$ ./a.out
我是子进程,我是第1个子进程, 我的pid = 21887,父进程的ppid= 21886
我是子进程,我是第2个子进程, 我的pid = 21888,父进程的ppid= 21886
我是子进程,我是第3个子进程, 我的pid = 21889,父进程的ppid= 21886
我是子进程,我是第4个子进程, 我的pid = 21890,父进程的ppid= 21886
我是子进程,我是第5个子进程, 我的pid = 21891,父进程的ppid= 21886
我是父进程,我的 pid = 21886

注释掉sleep会

[lc@lc133 fork]$ ./a.out
我是子进程,我是第1个子进程, 我的pid = 21921,父进程的ppid= 21920
我是父进程,我的 pid = 21920
我是子进程,我是第3个子进程, 我的pid = 21923,父进程的ppid= 21920
我是子进程,我是第2个子进程, 我的pid = 21922,父进程的ppid= 1
[lc@lc133 fork]$ 我是子进程,我是第4个子进程, 我的pid = 21924,父进程的ppid= 1
我是子进程,我是第5个子进程, 我的pid = 21925,父进程的ppid= 1

父进程在子进程结束前结束了,子进程被系统接管。

gdb跟踪调试

使用gdb调试,gdb只能默认跟踪一个进程

默认是set follow-fork-mode parent 跟踪父进程

可以在fork函数之前进行设置,set follow-mode child 跟踪子进程。

exec函数族

通过一个进程启动另一个新进程,旧的进程括代码段,数据段和堆栈等都已经被新的内容取代(也就是说用户区数据基本全部被替换掉了),只留下进程 ID 等一些表面上的信息仍保持原样,换核不换壳。

六种exec开头的函数

       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[]);

事实上,只有 execve是真正的系统调用 ,其它六个函数最终都调用execve,所以 execve在 man 手册第 2节,其它函数在 man手册第 3节。

说明:

1.exec函数调用成功即执行新的程序,成功不返回,只有失败返回-1,所以函数后面的代码只有perror()和exit()
2.一般规律;
l(list) 命令行参数
p(path) 搜索file使用的环境变量
v(vector) 使用命令行参数数组
e(environment) 使用环境变量数组,不使用进程原有的环境变量,设置新加载程序运行的环境变量

execlp与execl

加载一个进程,借助PATH环境变量

int execlp( const char *file, const char *arg, …);

成功无返回,失败返回-1

参数:

	参1: 程序名     //借助 PATH 环境变量找寻待执行程序

	参2: argv0

	参3: argv1

	...: argvN

	哨兵:NULL

int execl(const char *path, const char *arg, …);

与execlp不同。参数1需要携带路径,手动指定待执行程序路径(相对路径或绝对路径)。

创建子进程执行ls -l命令

进程结束自动被系统回收了,(隐式回收)。

[lc@lc133 exec]$ cat exec.c
#include <stdio.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
        pid_t pid;
        pid = fork();
        if(pid==0){
                printf("我是子进程,我的pid是:%d\n",getpid());
                execlp("ls","ls","-l",NULL);
                perror("execlp error");
        }
        if(pid > 0){
                sleep(2);
                printf("我是父进程,我的pid是:%d\n",getpid());
        }
        return 0;
}
[lc@lc133 exec]$ gcc exec.c
[lc@lc133 exec]$ cd /
[lc@lc133 /]$  ~/test/exec/a.out
我是子进程,我的pid是:4477
总用量 32
lrwxrwxrwx.   1 root root     7 1月  16 2022 bin -> usr/bin
dr-xr-xr-x.   5 root root  4096 11月 18 17:14 boot
drwxr-xr-x   19 root root  3380 12月 26 08:08 dev
drwxr-xr-x. 155 root root 12288 12月 26 08:08 etc
drwxr-xr-x.   3 root root    16 1月  16 2022 home
lrwxrwxrwx.   1 root root     7 1月  16 2022 lib -> usr/lib
lrwxrwxrwx.   1 root root     9 1月  16 2022 lib64 -> usr/lib64
drwxr-xr-x.   2 root root     6 4月  11 2018 media
drwxr-xr-x.   3 root root    18 3月  15 2022 mnt
drwxr-xr-x.   5 root root    45 3月  15 2022 opt
dr-xr-xr-x  331 root root     0 12月 26 08:08 proc
dr-xr-x---.  19 root root  4096 12月  7 13:59 root
drwxr-xr-x   45 root root  1320 12月 26 08:51 run
lrwxrwxrwx.   1 root root     8 1月  16 2022 sbin -> usr/sbin
drwxr-xr-x.   2 root root     6 4月  11 2018 srv
dr-xr-xr-x   13 root root     0 12月 26 08:08 sys
drwxrwxrwt.  20 root root  4096 12月 26 09:36 tmp
drwxr-xr-x.  13 root root   155 1月  16 2022 usr
drwxr-xr-x.  22 root root  4096 4月   8 2022 var
我是父进程,我的pid是:4476

当进程终止时,操作系统的隐式回收机制会:1.关闭所有文件描述符2.释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值;异常终止→终止信号)。

子进程的回收

孤儿进程

父进程先于子进终止,子进程沦为“孤儿进程”,会被 init 进程领养。

僵尸进程:

子进程终止,父进程尚未对子进程进行回收,在此期间,子进程为“僵尸进程”。  kill 对其无效。
可以杀死僵尸进程的父进程,僵尸进程成为"孤儿进程",过继给1号进程init,init始终会负责清理僵尸进程。

wait函数

回收子进程退出资源, 阻塞回收任意一个。

​ pid_t wait(int *status);

返回值:成功: 回收进程的pid

	失败: -1, errno

函数作用1:	阻塞等待子进程退出

函数作用2:	清理子进程残留在内核的 pcb 资源

函数作用3:	通过传出参数,得到子进程结束状态

使用宏函数进一步判断进程终止的具体原因。

	获取子进程正常终止值:

		WIFEXITED(status) --》 为真 --》调用 WEXITSTATUS(status) --》 得到 子进程 退出值。

	获取导致子进程异常终止信号:

		WIFSIGNALED(status) --》 为真 --》调用 WTERMSIG(status) --》 得到 导致子进程异常终止的信号编号。
		
如果不关心子进程退出状态,只回收子进程可以直接wait(NULL),给wait()函数传入NULL。

父进程wait回收子进程

 [lc@lc133 exec]$ cat exec.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
        pid_t pid,pid2;
        int status;
        pid = fork();
        if(pid==0){
                printf("我是子进程,我的pid是:%d\n",getpid());
                execlp("/home/lc/test/zuse/a.out","a.out",NULL); //子进程打开一个阻塞程序,从输入读取内容
                perror("execlp error");
                exit(1);
        }
        if(pid > 0){
                pid2 = wait(&status);
                if(pid2 == -1){
                        perror("wait error");
                        exit(1);
                }else{
                        printf("我是父进程,我的pid是:%d,我回收的进程pid是:%d\n",getpid(),pid2);
                        if(WIFEXITED(status)){printf("子进程正常退出,状态码是:%d\n",WEXITSTATUS(status));}
                        if(WIFSIGNALED(status)){printf("子进程被信号终止,信号是:%d\n " ,WTERMSIG(status));}


                }
        }
        return 0;
}
[lc@lc133 exec]$ gcc exec.c
[lc@lc133 exec]$ ./a.out
我是子进程,我的pid是:7532
pppp
pppp
我是父进程,我的pid是:7531,我回收的进程pid是:7532
子进程正常退出,状态码是:0
[lc@lc133 exec]$ ./a.out
我是子进程,我的pid是:7534
我是父进程,我的pid是:7533,我回收的进程pid是:7534
子进程被信号终止,信号是:9

waitpid函数

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

指定某一个进程进行回收。可以设置非阻塞。阻塞方式waitpid(-1, &status, 0) == wait(&status);

参数:
	pid:指定回收某一个子进程pid

		> 0: 待回收的子进程pid

		-1:任意子进程

		0:同组的子进程。

	status:(传出) 回收进程的状态。

	options:WNOHANG 指定回收方式为,非阻塞。

返回值:

	> 0 : 表成功回收的子进程 pid

	0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。

	-1: 失败。errno

wait、waitpid	一次调用,回收一个子进程。

回收多个子进程需要while 循环

循环回收子进程

[lc@lc133 wait]$ cat wait.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <string.h>

int main(int argc, char *argv[])
{


        int i=0;
        pid_t pid, wpid;

        for(i;i<5;i++){
                pid = fork();
                if(pid == 0)
                        break;
        }
        if(i == 5){         //最后的父进程会执行
                sleep(5);
                printf("我是父进程,我的id是:%d\n",getpid());
                while(1)     //循环
                {
                        wpid = waitpid(0,NULL,WNOHANG);    //不关心自进程的终止方式,非阻塞方式回收
                        //if(wpid == -1){ perror("waitpid error");exit(1);}
                        if(wpid == -1){ break;}            //没有子进程回收,会break
                        //else if(wpid == 0){printf("子进程并未结束\n");}
                        else if (wpid > 0){printf("子进程:%d被回收\n",wpid);}   

                }}
        else {        //每个自进程都会执行
                sleep(i);
                printf("我是子进程,id:%d\n",getpid());

        }
}
[lc@lc133 wait]$ gcc wait.c
[lc@lc133 wait]$ ./a.out
我是子进程,id:26437
我是子进程,id:26438
我是子进程,id:26439
我是子进程,id:26440
我是子进程,id:26441
我是父进程,我的id是:26436
子进程:26437被回收
子进程:26438被回收
子进程:26439被回收
子进程:26440被回收
子进程:26441被回收

将回收子进程的信息保存起来

[lc@lc133 wait]$ cat wait.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <pthread.h>
#include <string.h>
#include <time.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{

        int fd;
        int i=0;
        pid_t pid, wpid;

        for(i;i<5;i++){
                pid = fork();
                if(pid == 0)
                        break;
        }
        if(i == 5){
                sleep(5);
                printf("我是父进程,我的id是:%d\n",getpid());

                fd = open("./log.txt",O_RDWR|O_CREAT,0664);    //打开log文件,没有就创建文件
                if(fd == -1){perror("open error");exit(1);}
                lseek(fd, 0, SEEK_END);         				//移动文件位置指针到文件尾部
                dup2(fd,STDOUT_FILENO);							//重定向文件描述符
                printf("+++++++++++++++++++++++++++++++++++++++++\n");
                time_t now_time;
                time(&now_time);								
                printf("时间:%s\n",ctime(&now_time));				//输出当前时间

                while(1)
                {
                        wpid = waitpid(0,NULL,WNOHANG);
                        //if(wpid == -1){ perror("waitpid error");exit(1);}
                        if(wpid == -1){ break;}
                        //else if(wpid == 0){printf("子进程并未结束\n");}
                        else if (wpid > 0){printf("子进程:%d被回收\n",wpid);}

                }
                close(fd);											//关闭文件
        }

        else {
                sleep(i);
                printf("我是子进程,id:%d\n",getpid());

        }
}
[lc@lc133 wait]$ gcc wait.c
[lc@lc133 wait]$ ./a.out
[lc@lc133 wait]$ cat log.txt
+++++++++++++++++++++++++++++++++++++++++
时间:Tue Dec 27 14:18:30 2022

子进程:27078被回收
子进程:27079被回收
子进程:27080被回收
子进程:27081被回收
子进程:27082被回收
+++++++++++++++++++++++++++++++++++++++++
时间:Tue Dec 27 14:18:40 2022

子进程:27084被回收
子进程:27085被回收
子进程:27086被回收
子进程:27087被回收
子进程:27088被回收

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值