SystemStartProcess

image-20210730142407367

简述:

开机后系统将Rom文件加载进Ram内存中,loader检查Ram,kernel启动Swapper进程和kthreadd进程(创建内核守护进程)。

NativeFramework中init.cpp运行后启动init进程,init进程解析init.rc文件后,孵化如installd、logd、adbd等用户守护进程、启动servicemanager、surfaceflinger、bootanim等服务、孵化出Zygote虚拟机进程(java进程)。

JavaFramework中Zygote注册ZygoteSocket加载虚拟机、预加载通用类、资源;之后孵化system_server进程,启动如AMS(startService可监听RootPhase)、WMSPKMS、PMS等服务。

App:由Zygote孵化的第一个App——Launcher(桌面),用户点击Launcher上的app图片,通过JNI调用AMS从Zygote进程中fork出新的App。

ps:ServiceManager.addService()启动WMS、PKMS、IMS等服务,SystemServiceManager.startService()启动AMS、PMS

系统启动流程

BootRom->BootLoader->Linux Kernel->Init->Zygote->SystemServer->Launcher(UI)

BootLoader层:主要包括Boot Rom和Boot Loader

Kernel层:主要是Android内核层

Native层:主要是包括init进程以及其fork出 来的用户空间的守护进程、HAL层、开机动画等

JAVA Framework层:主要是AMS、WMS、PMS等Service的初始化

Application层:主要指SystemUI、Launcher的启动


电源键按下

-> Loader & Kernel

BootLoader 加载 rom 到 ram

linux内核进程 - 创建swapper(pid=0) ;内核守护进程kthreadd(pid=2) 与 各类驱动如binder driver

-> native framework

init(pid=1)进程, 解析并运行所有的init.rc相关文件,孵化出installd、adbd等用户守护进程;启动zygote、servicemanager和surfaceflinger服务进程;孵化Zygote进程(java)

  • init进程会孵化出ueventd、logd、healthd、installd、adbd、lmkd等用户守护进程;

  • init进程还启动servicemanagersurfaceflinger、bootanim(开机动画)等重要服务进程

  • init进程孵化出Zygote进程,Zygote进程是Android系统的第一个Java进程(即虚拟机进程)

  • (Native FrameWork层)Media Server进程,是由init进程fork而来,负责启动和管理整个C++ framework,包含AudioFlinger,Camera Service等服务。

-> java framework - Zygote

Zygote (创建虚拟机AndroidRunTime/VM + 预加载类和资源 ,同时所有app进程,包括”第一个app:桌面/Launch”,由zygote fork出,继承zygote拥有的一切)

  • Zygote进程,是由init进程通过解析init.rc文件后fork生成的,Zygote进程主要包含:
    • 加载ZygoteInit类,注册Zygote Socket服务端套接字
    • 加载虚拟机 Dalivk/ART
    • 提前加载类preloadClasses
    • 提前加载资源preloadResouces

-> java framework - system_server

system_server (因从zygote fork而来,故具有AndroidRunTime/VM 与预加载的类与资源 copy on write机制 ,由此开始启动线程承载ActivityManager,WindowManager,PackageManager,PowerManager等服务。管理所有app)

  • System Server进程,是由Zygote进程fork而来,System Server是Zygote孵化的第一个进程,System Server负责启动和管理整个Java framework,包含ActivityManager,WindowManager,PackageManager,PowerManager等服务(通过context.getSystemService(String)可获得)。

systemStart



附录 init启动过程

init.cpp进程解析的init.rc文件

init进程是Linux系统中用户空间的第一个进程,进程号固定为1。Kernel启动后,在用户空间启动init进程,并调用init中的main()方法执行init进程的职责。对于init进程的功能分为4部分:

  • 创建一块共享的内存空间,用于属性服务器;

  • 解析并运行所有的init.rc相关文件(孵化出ueventd、logd、healthd、installd、adbd、lmkd等用户守护进程;启动zygote、servicemanager和surfaceflinger服务进程)

    //注:7.0之前的版本,服务进程的启动都是直接写在init.rc中的,7.0之后init.rc被拆分成多个,比如bootanimation.rc,surfacefligner.rc等

  • 【epoll机制】进入死循环epoll_wait等待消息,直到系统属性变化事件(property_set改变属性值),或者收到子进程的信号SIGCHLD,再或者keychord 键盘输入事件则唤醒

附录 Zygote启动过程

Zygote是由init进程通过解析init.zygote.rc文件而创建的,zygote所对应的可执行程序app_process,所对应的源文件是App_main.cpp,进程名为zygote。

Zygote进程 , 它由 init进程 启动,启动时会创建一个Davlik虚拟机实例,并把Java运行时库加载到进程中,并注册一些Android核心类的JNI到前面创建的Dalvik虚拟机实例中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd

//service → ATL语言语法,启动一个服务进程;
zygote → 启动的程序名称,这指zygote进程;
/system/bin/app_process → 可执行文件路径( app_main.cpp );
-Xzygote /system/bin → 指定参数传到app_main.cpp中;
--zygote --start-system-server → 传的具体参数值;

简单点说就是:启动了Zygote进程,传递的参数可在 /frameworks/base/cmds/app_process/app_main.cpp 中找到.

//app_main.cpp:

img

对传进来的参数做匹配,zygote、startSystem标志位设置为true,接着定位下哪里用到了zygote这个标记:

img

跟下:**runtime.start()** 定位到 frameworks/base/core/jni/**AndroidRuntime.cpp**,关键代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// ① 初始化jni接口
JniInvocation jni_invocation;
jni_invocation.Init(NULL);

// ② 创建VM虚拟机
JNIEnv* env;
if (startVm(&mJavaVM, &env) != 0) {
return;
}
onVmCreated(env);

// ③ 注册JNI方法
if (startReg(env) < 0) {
ALOGE("Unable to register all android natives\n");
return;
}

// ④ 调用className类的static void main(String args[]) 方法
slashClassName = toSlashClassName(className);
jclass startClass = env->FindClass(slashClassName);
// 找到main函数
jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
"([Ljava/lang/String;)V");
if (startMeth == NULL) {
ALOGE("JavaVM unable to find main() in '%s'\n", className);
/* keep going */
} else {
// 通过 JNI 调用 main 函数,从 C++ 到 Java
env->CallStaticVoidMethod(startClass, startMeth, strArray);
if (env->ExceptionCheck())
threadExitUncaughtException(env);
}

所以这里创建了一个虚拟机,注册JNI方法,然后调用 com.android.internal.os.ZygoteInit 的 **main(),跟下frameworks/base/core/java/com/android/internal/os/ZygoteInit.java**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public static void main(String argv[]) {
try {
...
// ① 注册一个name为zygote的socket,用于和其他进程通信
registerZygoteSocket(socketName);

// ② 预加载所需资源到VM中,如class、resource、OpenGL、公用Library等;
// 所有fork的子进程共享这份空间而无需重新加载,减少了应用程序的启动时间,
// 但也增加了系统的启动时间,Android启动最耗时的部分之一。
preload();

// ③ 初始化gc,只是通知VM进行垃圾回收,具体回收时间、怎么回收,由VM内部算法决定。
// gc()需在fork前完成,这样将来复制的子进程才能有尽可能少的垃圾内存没释放;
gcAndFinalize();

// ④ 启动system_server,即fork一个Zygote子进程
if (startSystemServer) {
startSystemServer(abiList, socketName);
}

// ⑤ 进入循环模式,获取客户端连接并处理
runSelectLoop(abiList);

// ⑥ 关闭和清理zygote socket
closeServerSocket();
} catch (MethodAndArgsCaller caller) {
caller.run();
} catch (RuntimeException ex) {
Log.e(TAG, "Zygote died with exception", ex);
closeServerSocket();
throw ex;
}
}

跟下 **startSystemServer()**:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
private static boolean startSystemServer(String abiList, String socketName)
throws MethodAndArgsCaller, RuntimeException {
int pid;
try {
...
// fork出system_server进程,返回pid,此处pid为0
pid = Zygote.forkSystemServer(
parsedArgs.uid, parsedArgs.gid,
parsedArgs.gids,
parsedArgs.debugFlags,
null,
parsedArgs.permittedCapabilities,
parsedArgs.effectiveCapabilities);
} catch (IllegalArgumentException ex) {
throw new RuntimeException(ex);
}

/* 进入子进程 */
if (pid == 0) {
// Android 5.0上有两个Zygote进程:zygote 和 zygote64
// 对于有两个zygote进程的情况,需等待第二个zygote创建完成;
if (hasSecondZygote(abiList)) {
waitForSecondaryZygote(socketName);
}
// 完成system_server进程的剩余工作
handleSystemServerProcess(parsedArgs);
}
return true;
}

Tips:fork()方法被调用一次,返回两次,区别是:子进程的返回值是0,父进程的返回值是子进程的进程id,可以保证子进程的进程id不可能为0。

zygote_start

  1. 解析init.zygote.rc中的参数,创建AppRuntime并调用AppRuntime.start()方法;

  2. 调用AndroidRuntime的startVM()方法创建虚拟机,再调用startReg()注册JNI函数;

  3. 通过JNI方式调用ZygoteInit.main(),第一次进入Java世界;

  4. registerZygoteSocket()建立socket通道,zygote作为通信的服务端,用于响应客户端请求;

  5. preload()预加载通用类、drawable和color资源、openGL以及共享库以及WebView,用于提高app启动效率;

  6. zygote完毕大部分工作,接下来再通过**startSystemServer()**,fork得力帮手system_server进程,也是上层JavaFramework的运行载体。

    所有APP进程都是由Zygote进程孵化(fork) 而来的,fork时不仅仅会获得Zygote进程中的Dalvik虚拟机实例拷贝,还会与Zygote一起 **共享Java运行时库**。

  7. zygote功成身退,调用runSelectLoop(),随时待命,当接收到请求创建新进程请求时立即唤醒并执行相应工作。

附录 system_server启动的服务

1
2
3
4
5
6
7
8
try {
startBootstrapServices(); // 启动引导服务:AMS、PKMS
startCoreServices(); // 启动核心服务
startOtherServices(); // 启动其他服务:WMS
} catch (Throwable ex) {
Slog.e("System", "************ Failure starting system services", ex);
throw ex;
}
  1. 引导服务(7个):ActivityManagerService、PowerManagerService、LightsService、DisplayManagerService、PackageManagerService、UserManagerService、SensorService;
  2. 核心服务(3个):BatteryService、UsageStatsService、WebViewUpdateService;
  3. 其他服务(70个+):WindowManagerService、AlarmManagerService、VibratorService等。

合计总大约80个系统服务:

ActivityManagerService PackageManagerService WindowManagerService
PowerManagerService BatteryService BatteryStatsService
DreamManagerService DropBoxManagerService SamplingProfilerService
UsageStatsService DiskStatsService DeviceStorageMonitorService
SchedulingPolicyService AlarmManagerService DeviceIdleController
ThermalObserver JobSchedulerService AccessibilityManagerService
DisplayManagerService LightsService GraphicsStatsService
StatusBarManagerService NotificationManagerService WallpaperManagerService
UiModeManagerService AppWidgetService LauncherAppsService
TextServicesManagerService ContentService LockSettingsService
InputMethodManagerService InputManagerService MountService
FingerprintService TvInputManagerService DockObserver
NetworkManagementService NetworkScoreService NetworkStatsService
NetworkPolicyManagerService ConnectivityService BluetoothService
WifiP2pService WifiService WifiScanningService
AudioService MediaRouterService VoiceInteractionManagerService
MediaProjectionManagerService MediaSessionService
DevicePolicyManagerService PrintManagerService BackupManagerService
UserManagerService AccountManagerService TrustManagerService
SensorService LocationManagerService VibratorService
CountryDetectorService GestureLauncherService PersistentDataBlockService
EthernetService WebViewUpdateService ClipboardService
TelephonyRegistry TelecomLoaderService NsdService
UpdateLockService SerialService SearchManagerService
CommonTimeManagementService AssetAtlasService ConsumerIrService
MidiServiceCameraService TwilightService RestrictionsManagerService
MmsServiceBroker RttService UsbService

system_server进程,从源码角度划分为引导服务、核心服务、其他服务3类。 以下这些系统服务的注册过程, 见Android系统服务的注册方式
方式1. ServiceManager.addService():

  • 功能:向ServiceManager注册该服务.
  • 特点:服务往往直接或间接继承于Binder服务;
  • 举例:input, window, package;

方式2. SystemServiceManager.startService:

  • 功能:
    • 创建服务对象;
    • 执行该服务的onStart()方法;该方法会执行上面的SM.addService();
    • 根据启动到不同的阶段会回调onBootPhase()方法;
    • 另外,还有多用户模式下用户状态的改变也会有回调方法;例如onStartUser();
  • 特点:服务往往自身或内部类继承于SystemService;
  • 举例:power, activity;

两种方式真正注册服务的过程都会调用到ServiceManager.addService()方法. 对于方式2多了一个服务对象创建以及 根据不同启动阶段采用不同的动作的过程。可以理解为方式2比方式1的功能更丰富。

附录 BootPhase

系统开机启动过程, 当执行到system_server进程时, 将启动过程划分了几个阶段, 定义在SystemService.java文件

1
2
3
4
5
6
public static final int PHASE_WAIT_FOR_DEFAULT_DISPLAY = 100; 
public static final int PHASE_LOCK_SETTINGS_READY = 480;
public static final int PHASE_SYSTEM_SERVICES_READY = 500;
public static final int PHASE_ACTIVITY_MANAGER_READY = 550;
public static final int PHASE_THIRD_PARTY_APPS_CAN_START = 600;
public static final int PHASE_BOOT_COMPLETED = 1000;

这些阶段跟系统服务大致的顺序图,如下:

system_server服务启动流程

PHASE_BOOT_COMPLETED=1000,该阶段是发生在Boot完成和home应用启动完毕, 对于系统服务更倾向于监听该阶段,而非监听广播ACTION_BOOT_COMPLETED

附录 Android系统架构

图片

第一步:手机开机后,引导芯片启动,引导芯片开始从固化在ROM里的预设代码执行,加载引导程序到到RAM,bootloader检查RAM,初始化硬件参数等功能

第二步:硬件等参数初始化完成后,进入到Kernel层,Kernel层主要加载一些硬件设备驱动,初始化进程管理等操作。在Kernel中首先启动swapper进程(pid=0),用于初始化进程管理、内管管理、加载Driver等操作,再启动kthread进程(pid=2),这些linux系统的内核进程,kthread是所有内核进程的鼻祖;

第三步:Kernel层加载完毕后,硬件设备驱动与HAL层进行交互。初始化进程管理等操作会启动INIT进程 ,这些在Native层中

第四步:init进程(pid=1,init进程是所有进程的鼻祖,第一个启动)启动后,会启动adbd,logd等用户守护进程,并且会启动servicemanager(binder服务管家)等重要服务,同时孵化出zygote进程,这里属于C++ Framework,代码为C++程序;

第五步:zygote 进程是由 **init进程解析init.rc文件后fork生成,它会加载虚拟机,启动System Server(zygote孵化的第一个进程)**;System Server负责启动和管理整个Java Framework,包含ActivityManager,WindowManager,PackageManager,PowerManager等服务;

第六步:zygote同时会启动相关的APP进程,它启动的第一个APP进程为Launcher(UI) ,然后启动Email,SMS等进程,所有的APP进程都有zygote fork生成。

关键概念厘清

swapper进程是唯一一个不由其他进程fork/clone出来的进程,它是直接由内核创建,pid为0;

swapper进程fork出两个关键进程:

  • Init进程(pid=1),一切用户空间进程鼻祖

    img

  • kthreadd进程(pid=2),一切内核空间进程鼻祖

子进程的创建

Init进程采用被动创建的方式来创建子进程:哪些子进程需要创建,那就使用脚本语言.rc来配置相应的信息,Init进程会在LoadBootScripts方法中把所有的配置好信息都收集起来,当init进程启动后会根据配置信息来创建子进程。

xxx.rc

  1. 配置子进程基础信息:这一步主要用来配置子进程的基础信息,比如子进程的名字、可执行文件路径等,init进程就可以立马明白是哪个子进程被创建
  2. 配置触发条件:主要配置子进程何时或者满足什么条件的情况下被创建,因为不同子进程的创建条件都是不一样的,因此init进程可以从这一步得知是在“什么时候”或者“什么条件满足”的时候来创建子进程
  3. 配置前置命令:主要配置子进程在创建之前需要执行一些提前操作或者提前执行的命令,比如有的子进程在创建之前需要提前创建一些目录等操作
  4. 配置创建子进程命令:这一步非常的简单,init进程遇到这个命令,就开始执行创建子进程的操作

用init脚本语言来配置创建子进程的步骤如下:

  1. 首先子进程在以.rc的脚本文件中,使用service关键字来配置子进程相关的信息
  2. 其次 在init.rc文件中使用import关键字引入脚本文件,使用on关键字来配置子进程的触发条件
  3. 触发条件配置完毕后,如若子进程在创建之前需要配置一些前置操作或命令,则基于触发条件下配置这些信息
  4. 最后使用start关键字来配置创建子进程的命令。

Init进程的运行

Init进程进入循环工作模式

图片

  1. 是否有关机或重启的消息,有的话执行关机或重启

  2. 调用ActionManager的ExecuteOneCommand方法,ActionManager会检查是否有触发器,有的话触发对应的Action执行,有些Action会包含创建子进程的start命令,根据start命令后面的servicename,开始创建对应的子进程

  3. 若有control类型的message,则把它交给对应的Service

子进程的销毁

上面谈到了子进程”生“的问题,那现在咱们聊聊子进程”死“的问题。作为Android用户空间所有进程的鼻祖,从生物学的角度来看,我肯定比我的孩子、孙子们要先死掉,但是在Android系统却恰恰相反,我的生命周期尽然是最长的,我的很多子子孙孙都死了很多次了,我还依然活着,因此我创建的子进程万一死掉的话,那它的善后事情需要我来处理(真是白发人送黑发人啊)。

那有人就会问了,你是如何知道你创建的子进程死掉的,这是个好问题,那我就来讲给大家听。

监听子进程死掉

监听子进程死掉非常的简单,主要是用到了Linux的以下知识点:signal机制。它的主要作用是实现进程之间的通信,signal机制是最“吝啬”的、但是是最简单的进程之间的通信方式。为啥要说它是最“吝啬”呢?主要原因是signal对进程之间传递的数据仅且只能只能传递一个int类型的信号,但是像socket等通信方案对传递的数据并没有这样的限制,你说它“吝啬”不。

简单主要体现在:若对哪个信号有兴趣,可以使用sigaction函数注册这个信号,当这个信号发生时,注册的函数就会被调用。这里一直在提信号,监听子进程状态有一个信号是SIGCHLD,父进程可以注册这个SIGCHLD来监听子进程的状态,状态主要包括死掉(死掉包含正常死掉或者异常死掉比如crash)、停止、继续等。

epoll机制

epoll是“多路复用“技术最好的实现方案,“多路复用”看到这种专业性的词是不是一头雾水啊,咱们举个例子:正常咱们进行阻塞类型的IO读操作(比如从一个socket中读取数据),是不是都会创建一个单独的线程来监听是否有数据到达,如果没有数据到达则线程进入阻塞状态,有的话则线程就开始读取数据。那假如有20个甚至更多的阻塞IO读操作,是不是需要创建对应个数的线程。

这些线程如果大部分都没有可读数据的情况下是不是都处于阻塞状态,这难道不是大大得浪费吗?因此“多路复用”技术就出现了,它的设计理念是:启动一个线程,谁有需要监听IO是否有可读数据到达的操作都可以交给这个线程。这里的“多路”指的就是上面例子中创建的多个线程,“复用”指的就是指用一个线程来进行监听操作。

epoll机制在Android中使用非常的广泛,比如Handler的MessageQueue在没有Message的情况下进入阻塞,以及input事件从systemserver进程传递到app进程,甚至vsyn信号从surfaceflinger传递到app进程都用到了epoll机制。

好了,有了上面的知识,那我就来介绍下我监听子进程死掉的思路:

  1. 首先我先使用sigaction函数来注册SIGCHLD信号,这样就可以监听到子进程的状态了
  2. 其次使用signalfd函数为SIGCHLD信号生成一个fd(文件描述符)
  3. 再次使用epoll来见监听上一步生成的fd是否有可读数据
  4. 如监听到fd上有可读数据,则证明子进程的状态发生了变化,还需要使用waitpid函数来获取是哪个子进程死掉了

如上4步就可以监听到子进程死掉了,下面是具体的代码,有兴趣的同学可以看下。

system/core/init/init.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
static void InstallSignalFdHandler(Epoll* epoll) {
//初始化sigaction,SIG_DFL:代表使用默认的信号处理行为。
const struct sigaction act { .sa_handler = SIG_DFL, .sa_flags = SA_NOCLDSTOP };
//注册SIGCHLD信号
sigaction(SIGCHLD, &act, nullptr);

//声明mask信号集
sigset_t mask;
//初始化并清空一个信号集,使其不包含任何信号
sigemptyset(&mask);
//把SIGCHLD信号加入mask信号集中
sigaddset(&mask, SIGCHLD);

省略代码......

//SIG_BLOCK:代表将mask添加到当前的信号屏蔽集中
if (sigprocmask(SIG_BLOCK, &mask, nullptr) == -1) {
PLOG(FATAL) << "failed to block signals";
}

// Register a handler to unblock signals in the child processes.
//在子进程创建成功后,恢复SIGCHLD为非屏蔽
const int result = pthread_atfork(nullptr, nullptr, &UnblockSignals);
if (result != 0) {
LOG(FATAL) << "Failed to register a fork handler: " << strerror(result);
}

//调用signalfd函数为mask生成一个fd
signal_fd = signalfd(-1, &mask, SFD_CLOEXEC);
if (signal_fd == -1) {
PLOG(FATAL) << "failed to create signalfd";
}

constexpr int flags = EPOLLIN | EPOLLPRI;
//使用epoll来监听signal_fd上的数据
if (auto result = epoll->RegisterHandler(signal_fd, HandleSignalFd, flags); !result.ok()) {
LOG(FATAL) << result.error();
}
}

//如果fd上有数据就会调用这个方法
static void HandleSignalFd() {

//读取到siginfo信息
signalfd_siginfo siginfo;
ssize_t bytes_read = TEMP_FAILURE_RETRY(read(signal_fd, &siginfo, sizeof(siginfo)));
if (bytes_read != sizeof(siginfo)) {
PLOG(ERROR) << "Failed to read siginfo from signal_fd";
return;
}

//判断当前的ssi_signo
switch (siginfo.ssi_signo) {
case SIGCHLD:
//只看SIGCHLD
ReapAnyOutstandingChildren();
break;

省略无关代码......
}
}

system/core/init/sigchld_handler.cpp

void ReapAnyOutstandingChildren() {
while (ReapOneProcess() != 0) {
}
}

static pid_t ReapOneProcess() {
siginfo_t siginfo = {};
//调用waitpid方法来获取死掉的子进程的信息
if (TEMP_FAILURE_RETRY(waitid(P_ALL, 0, &siginfo, WEXITED | WNOHANG | WNOWAIT)) != 0) {
PLOG(ERROR) << "waitid failed";
return 0;
}

const pid_t pid = siginfo.si_pid;
if (pid == 0) {
DCHECK_EQ(siginfo.si_signo, 0);
return 0;
}

省略无关代码......
}

如上面监听到子进程死掉的时候会通过waitpid方法获取到子进程的pid,还记得在创建子进程的时候,子进程的配置信息都会放在Service类中,根据pid可以找到对应的Service,Service持有了创建子进程的时候持有的各种socket等资源,这些资源会被清除掉,并且会通知kernel层杀掉子进程,进而在kernel层清除掉子进程占据的各种资源。

Author

white crow

Posted on

2021-06-30

Updated on

2024-03-25

Licensed under