进程调度,主要解决CPU资源分配问题,多进程解决如下问题:
解决生命周期问题(init,systemd);
解决进程程序背景的问题(exec)
解决进程之间通信问题(IPC)
设计进程与进程之间的界限;
1.解决生命周期问题(init, systemd)
fork创建子进程后,可以监控子进程退出原因;父进程死后,子进程默认挂载到init进程;3.4之后的版本,添加了用SUBREPER来挂载孤儿进程;
systemd分两level:
系统级systemd,(init进程对应的软连接),
用户级systemd调用prctl(PR_SET_CHILD_SUBREPER,1)将自己设置为subreper;
Linux为每个用户创建一个systemd。
若父进程先退出,子进程挂靠到对应的systemd根进程。
Systemd源码: vim src/core/main.c
if (!arg_system)
1984 /* Become reaper of our children */
1985 if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0)
1986 log_warning_errno(errno, "Failed to make us a subreaper: %m");
1987
2 解决进程程序背景的问题(exec)
2.1 exec不创建进程,它改变程序执行背景
Windows CreateProcess创建子进程,Linux exec不创建进程,原进程PID不变,只是改变程序执行背景(fork原子进程与父进程是COW机制共享资源比如MM等,exec是全新的);
同一个进程,执行程序一样了;
父子进程都可以改变程序环境。
2.2 vfork没有写时拷贝
fork()依赖于硬件MMU实现,对于没有MMU的系统,用vfork代替,vfork没有写时拷贝;
vfork后,父进程什么时候继续执行?
当父进程MM不再被子进程共享时;
vfork创建子进程后,父进程阻塞等待vfork_done,再继续往下执行;
Linux唤醒vfork_done有两个地方,进程退出和exec
所以,在子进程创建后立马调用exec的场景,用vfork更合适(可以省掉fork的COW);
调用exec之前指向同一个mm_struct,调用exec之后申请新的mm_struct。
3 main函数的生命周期
3.1 main函数即不是程序的入口,也不是出口
main函数进去之前,执行了动态库的构造函数;
main函数退出之后,执行动态库的析构函数
注意,后注册的钩子函数先执行;
__attribute__ ((constructor))
Xxx1()
{
…
}
__attribute__ ((destructor))
Xxx2()
{
…
}
3.2 main函数正常退出方式有:
(1)正常/默认退出return 0; 会调用库函数exit()
(2)调用库函数exit()退出;
库函数exit()会执行flush io、调用atexit()注册的钩子函数,再调用系统调用_exit(n) 退出, (n为return函数返回的退出码);
strace ./a.out
(3)exit_group(1):同TGID全部退出
(4)进程被信号杀掉,会调用_exit(),不会调用钩子函数,也不会执行flush,直接退出;;
(5)调用_exit(n),非正常返回;
4.Lsan检测内存泄漏:
实际上是调用钩子函数__lsan_do_leak_check(),它依赖返回,如果不返回则不调用;
gcc lsan.c -g -fsanitize=leak
但是如果程序不退出,或者用信号杀死,都不会执行__lsan_do_leak_check();
解决办法,实际工程上,可以把内存泄漏检测放在捕捉信号函数上;
gcc -shared -fPIC lsan-helper.c -o lsan-helper.so -lpthread -ldl
#include <sanitizer/lsan_interface.h>
__lsan_do_leak_check()
LD_PRELOAD强制加载动态库
LD_PRELOAD=./lsan-helper.so ./a.out