不落辰

知不可乎骤得,托遗响于悲风

0%

操作系统-进程状态机模型

  • 操作系统就是一个中断处理程序
    • 负责将第一个进程加载完后 第一个进程通过fork execve exit一系列系统调用创建其他的
    • 创建状态机 : fork
    • 替换 : execve
    • 终止 : _exit
      • 请问main退出后的detached thread的行为 ?

  • Linux 操作系统启动流程

    • CPU Reset → Firmware → Loader → Kernel _start() → 第一个程序 /bin/init → 程序 (状态机) 执行 + 系统调用
  • 操作系统为 (所有) 程序提供 API

    • 进程 (状态机) 管理
      • fork, execve, exit - 状态机的创建/改变/删除 ← 今天的主题
    • 存储 (地址空间) 管理
      • mmap - 虚拟地址空间管理
    • 文件 (数据对象) 管理
      • open, close, read, write - 文件访问管理
      • mkdir, link, unlink - 目录管理

创建状态机

  • 如果要创建状态机,我们应该提供什么样的 API?
  • UNIX 的答案: fork
    • 做一份状态机完整的复制 (内存、寄存器现场)

替换状态机

  • UNIX 的答案: execve
    • 将当前运行的状态机重置成成另一个程序的初始状态
  • int execve(const char *filename, char * const argv, char * const envp);
    • 执行名为 filename 的程序
    • 允许对新状态机设置参数 argv (v) 和环境变量 envp (e)
    • 刚好对应了 main() 的参数!

终止状态机

  • 有了 fork, execve 我们就能自由执行任何程序了,最后只缺一个销毁状态机的函数!
  • UNIX 的答案: _exit
    • void _exit(int status)
    • 立即摧毁状态机
      • 销毁当前状态机,并允许有一个返回值
      • 子进程终止会通知父进程 (后续课程解释)
      • 这个简单……
  • 但问题来了:多线程程序怎么办?

exit 的几种写法 (它们是不同)

  • exit(0) - stdlib.h 中声明的 libc 函数

    • 因为是libc的库函数,所以会帮助我们做libc的clean up。比如将libc缓冲区中的数据写出去
      • 会调用 atexit 注册的函数
    • return 也会将没flush的缓冲区清空。main种调用return就是相当于调用了exit(0)
    • 执行 “exit_group” 系统调用终止整个进程 (所有线程)
      • 那么操作系统会把当前进程所有的资源,包括地址空间、io、线程、管道、fd等等等等全部终止使用并且回收
  • _exit(0) - glibc 的 syscall wrapper

    • 是系统调用,不会帮助我们做libc的clean up(因为他根本就不知道libc)。直接就终止了整个进程。
      • 不会调用 atexit注册的函数
    • 执行 “exit_group” 系统调用终止整个进程 (所有线程)
  • syscall(SYS_exit, 0)

    • 执行 “exit” 系统调用终止当前线程
    • 不会调用 atexit注册的函数
  • pthread_exit(0)

    • libc的函数 , 所以会帮助我们做libc的clean up。比如将libc缓冲区中的数据写出去
      • 会调用atexit注册的函数
    • 当有多个thread时,执行”exit“系统调用终止当前thread
    • 当只有一个thread时,执行exit_group终止process
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
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/syscall.h>

void func() {
printf("Goodbye, Cruel OS World!\n");
}

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

if (argc < 2) return EXIT_FAILURE; // exit_group
if (strcmp(argv[1], "exit") == 0)
exit(0); // exit_group
if (strcmp(argv[1], "_exit") == 0)
_exit(0); // exit_group
if (strcmp(argv[1], "__exit") == 0)
syscall(SYS_exit, 0); // exit
}


shc@DESKTOP-TVUERHD:~/Code/try/lock/jyy$ strace ./exit-demo
execve("./exit-demo", ["./exit-demo"], 0x7fffbed4f890 /* 36 vars */) = 0
arch_prctl(0x3001 /* ARCH_??? */, 0x7fffd8ed0c70) = -1 EINVAL (Invalid argument)
brk(NULL) = 0x68c000
brk(0x68d1c0) = 0x68d1c0
arch_prctl(ARCH_SET_FS, 0x68c880) = 0
uname({sysname="Linux", nodename="DESKTOP-TVUERHD", ...}) = 0
readlink("/proc/self/exe", "/home/shc/Code/try/lock/jyy/exit"..., 4096) = 37
brk(0x6ae1c0) = 0x6ae1c0
brk(0x6af000) = 0x6af000
mprotect(0x4bd000, 12288, PROT_READ) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
write(1, "Goodbye, Cruel OS World!\n", 25Goodbye, Cruel OS World!
) = 25
exit_group(1) = ?
+++ exited with 1 +++

shc@DESKTOP-TVUERHD:~/Code/try/lock/jyy$ strace ./exit-demo exit
execve("./exit-demo", ["./exit-demo", "exit"], 0x7fff0898d428 /* 36 vars */) = 0
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffe206995b0) = -1 EINVAL (Invalid argument)
brk(NULL) = 0xa47000
brk(0xa481c0) = 0xa481c0
arch_prctl(ARCH_SET_FS, 0xa47880) = 0
uname({sysname="Linux", nodename="DESKTOP-TVUERHD", ...}) = 0
readlink("/proc/self/exe", "/home/shc/Code/try/lock/jyy/exit"..., 4096) = 37
brk(0xa691c0) = 0xa691c0
brk(0xa6a000) = 0xa6a000
mprotect(0x4bd000, 12288, PROT_READ) = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x4), ...}) = 0
write(1, "Goodbye, Cruel OS World!\n", 25Goodbye, Cruel OS World!
) = 25
exit_group(0) = ?
+++ exited with 0 +++

shc@DESKTOP-TVUERHD:~/Code/try/lock/jyy$ strace ./exit-demo _exit
execve("./exit-demo", ["./exit-demo", "_exit"], 0x7ffd7e64a328 /* 36 vars */) = 0
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd0457b610) = -1 EINVAL (Invalid argument)
brk(NULL) = 0xcb3000
brk(0xcb41c0) = 0xcb41c0
arch_prctl(ARCH_SET_FS, 0xcb3880) = 0
uname({sysname="Linux", nodename="DESKTOP-TVUERHD", ...}) = 0
readlink("/proc/self/exe", "/home/shc/Code/try/lock/jyy/exit"..., 4096) = 37
brk(0xcd51c0) = 0xcd51c0
brk(0xcd6000) = 0xcd6000
mprotect(0x4bd000, 12288, PROT_READ) = 0
exit_group(0) = ?
+++ exited with 0 +++

shc@DESKTOP-TVUERHD:~/Code/try/lock/jyy$ strace ./exit-demo __exit
execve("./exit-demo", ["./exit-demo", "__exit"], 0x7ffcb7669508 /* 36 vars */) = 0
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffd2fb8ad30) = -1 EINVAL (Invalid argument)
brk(NULL) = 0xf1b000
brk(0xf1c1c0) = 0xf1c1c0
arch_prctl(ARCH_SET_FS, 0xf1b880) = 0
uname({sysname="Linux", nodename="DESKTOP-TVUERHD", ...}) = 0
readlink("/proc/self/exe", "/home/shc/Code/try/lock/jyy/exit"..., 4096) = 37
brk(0xf3d1c0) = 0xf3d1c0
brk(0xf3e000) = 0xf3e000
mprotect(0x4bd000, 12288, PROT_READ) = 0
exit(0) = ?
+++ exited with 0 +++

问题

  • (1)请问main退出后的detached thread的行为 ?解答如下

  • 关键在于 main 如何退出。

    • exit(0) / return 0 : 整个process被终止
      • mutli-thread程序中,任意thread调用exit(0)(最后会到系统调用exit_group),整个进程被终止,所有thread(包括detached thread)都被销毁。(进程的地址空间都没了,线程当然活不了)
      • main中调用return 0就相当于调用了exit(0)。
    • pthread_exit : 只是终止当前main thread。
      • 只退出当前thread。(最后会调用到系统调用exit)
      • detached thread 仍运行
  • (2)编程时是否应该保证在main线程结束前join thread?或者说保证一个detached的thread在main exit之前就结束?

    • 解答:普通thread在main线程结束前,一定要join thread,保证业务执行的完整性(像C++,你没有设置子线程detach,也不join它,main函数运行完,还有子线程没执行完,它都给你直接报core dump了,你直接都能看出来); detach thread不需要,一般detach thread设计的目的,就是在后台做一些非关键性任务,是否正常结束并没有要求,也不会出什么错误,如果要求任务的执行必须是完整的,不能设计成detach thread

atexit科普 : atexit (func)用来设置一个程序正常结束前调用的函数. 当程序通过调用exit ()或从main中return 时, func 会先被调用, 然后再结束程序.