Operating System
$Nanjing\ University\rightarrow Yanyan\ Jiang\newline$
Overview
复习
操作系统内核的启动:CPU Reset
→ Firmware
→ Boot loader
→ Kernel _start()
→ …
本次课回答的问题
Q1
: 操作系统启动后到底做了什么?
Q2
: 操作系统如何管理程序 (进程)?
本次课主要内容
操作系统启动后到底做了什么?
从系统启动到第一个进程
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
#include <am.h>
#include <klib.h>
#include <klib-macros.h>
#define MAX_CPU 8
typedef union task {
struct {
const char * name ;
union task * next ;
void ( * entry )( void * );
Context * context ;
};
uint8_t stack [ 8192 ];
} Task ;
Task * currents [ MAX_CPU ];
#define current currents[cpu_current()]
// user-defined tasks
int locked = 0 ;
void lock () { while ( atomic_xchg ( & locked , 1 )); }
void unlock () { atomic_xchg ( & locked , 0 ); }
void func ( void * arg ) {
while ( 1 ) {
lock ();
printf ( "Thread-%s on CPU #%d \n " , arg , cpu_current ());
unlock ();
for ( int volatile i = 0 ; i < 100000 ; i ++ ) ;
}
}
Task tasks [] = {
{ . name = "A" , . entry = func },
{ . name = "B" , . entry = func },
{ . name = "C" , . entry = func },
{ . name = "D" , . entry = func },
{ . name = "E" , . entry = func },
};
// ------------------
Context * on_interrupt ( Event ev , Context * ctx ) {
extern Task tasks [];
if ( ! current ) current = & tasks [ 0 ];
else current -> context = ctx ;
do {
current = current -> next ;
} while (( current - tasks ) % cpu_count () != cpu_current ());
return current -> context ;
}
void mp_entry () {
iset ( true );
yield ();
}
int main () {
cte_init ( on_interrupt );
for ( int i = 0 ; i < LENGTH ( tasks ); i ++ ) {
Task * task = & tasks [ i ];
Area stack = ( Area ) { & task -> context + 1 , task + 1 };
task -> context = kcontext ( stack , task -> entry , ( void * ) task -> name );
task -> next = & tasks [( i + 1 ) % LENGTH ( tasks )];
}
mpe_init ( mp_entry );
}
操作系统会加载 “第一个程序”
RTFSC
(latest Linux Kernel
)
1
2
3
4
5
6
7
if ( ! try_to_run_init_process ( "/sbin/init" ) ||
! try_to_run_init_process ( "/etc/init" ) ||
! try_to_run_init_process ( "/bin/init" ) ||
! try_to_run_init_process ( "/bin/sh" ))
return 0 ;
panic ( "No working init found. Try passing init= option to kernel. "
"See Linux Documentation/admin-guide/init.rst for guidance." );
$\uparrow$如果没有指定启动选项 init=
,按照 “默认列表” 尝试一遍,如果都不行,内核就拒绝启动(panic
的部分)
从此以后,Linux Kernel
就进入后台,成为 “中断/异常处理程序”
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ pstree
init( Ubuntu-22.─┬─SessionLeader───Relay( 55) ───sh───sh───sh───node─┬─node─┬─zsh───pstree
│ │ └─11*[{ node}]
│ ├─node─┬─clangd.main───9*[{ clangd.main}]
│ │ ├─node───6*[{ node}]
│ │ ├─node───7*[{ node}]
│ │ └─11*[{ node}]
│ ├─node───12*[{ node}]
│ └─10*[{ node}]
├─SessionLeader───Relay( 486) ───node───6*[{ node}]
├─SessionLeader───Relay( 495) ───node───6*[{ node}]
├─init───{ init}
└─{ init( Ubuntu-22.}
$\uparrow$上面是wsl
的,和真机启动方式不太一样
找了一台Ubuntu
虚拟机,这回一样了
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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
systemd─┬─ModemManager───2*[{ ModemManager}]
├─NetworkManager───2*[{ NetworkManager}]
├─ToDesk_Service───13*[{ ToDesk_Service}]
├─VGAuthService
├─accounts-daemon───2*[{ accounts-daemon}]
├─acpid
├─anacron
├─avahi-daemon───avahi-daemon
├─bluetoothd
├─colord───2*[{ colord}]
├─cron
├─cups-browsed───2*[{ cups-browsed}]
├─cupsd
├─dbus-daemon
├─dockerd─┬─containerd───7*[{ containerd}]
│ └─8*[{ dockerd}]
├─fwupd───4*[{ fwupd}]
├─gdm3─┬─gdm-session-wor─┬─gdm-wayland-ses─┬─gnome-session-b───2*[{ gnome-session-b}]
│ │ │ └─2*[{ gdm-wayland-ses}]
│ │ └─2*[{ gdm-session-wor}]
│ └─2*[{ gdm3}]
├─geoclue───2*[{ geoclue}]
├─gnome-keyring-d───3*[{ gnome-keyring-d}]
├─irqbalance───{ irqbalance}
├─2*[ kerneloops]
├─networkd-dispat
├─packagekitd───2*[{ packagekitd}]
├─polkitd───2*[{ polkitd}]
├─power-profiles-───2*[{ power-profiles-}]
├─rsyslogd───3*[{ rsyslogd}]
├─rtkit-daemon───2*[{ rtkit-daemon}]
├─snapd───12*[{ snapd}]
├─switcheroo-cont───2*[{ switcheroo-cont}]
├─systemd─┬─( sd-pam)
│ ├─at-spi2-registr───2*[{ at-spi2-registr}]
│ ├─dbus-daemon
│ ├─dconf-service───2*[{ dconf-service}]
│ ├─evolution-addre───5*[{ evolution-addre}]
│ ├─evolution-calen───8*[{ evolution-calen}]
│ ├─evolution-sourc───3*[{ evolution-sourc}]
│ ├─2*[ gjs───4*[{ gjs}]]
│ ├─gnome-session-b─┬─at-spi-bus-laun─┬─dbus-daemon
│ │ │ └─3*[{ at-spi-bus-laun}]
│ │ ├─evolution-alarm───5*[{ evolution-alarm}]
│ │ ├─gsd-disk-utilit───2*[{ gsd-disk-utilit}]
│ │ ├─update-notifier───3*[{ update-notifier}]
│ │ └─3*[{ gnome-session-b}]
│ ├─gnome-session-c───{ gnome-session-c}
│ ├─gnome-shell─┬─Xwayland
│ │ ├─gjs───5*[{ gjs}]
│ │ └─9*[{ gnome-shell}]
│ ├─gnome-shell-cal───5*[{ gnome-shell-cal}]
│ ├─gnome-terminal-─┬─bash───pstree
│ │ └─3*[{ gnome-terminal-}]
│ ├─goa-daemon───3*[{ goa-daemon}]
│ ├─goa-identity-se───2*[{ goa-identity-se}]
│ ├─gsd-a11y-settin───3*[{ gsd-a11y-settin}]
│ ├─gsd-color───3*[{ gsd-color}]
│ ├─gsd-datetime───3*[{ gsd-datetime}]
│ ├─gsd-housekeepin───3*[{ gsd-housekeepin}]
│ ├─gsd-keyboard───3*[{ gsd-keyboard}]
│ ├─gsd-media-keys───3*[{ gsd-media-keys}]
│ ├─gsd-power───3*[{ gsd-power}]
│ ├─gsd-print-notif───2*[{ gsd-print-notif}]
│ ├─gsd-printer───2*[{ gsd-printer}]
│ ├─gsd-rfkill───2*[{ gsd-rfkill}]
│ ├─gsd-screensaver───2*[{ gsd-screensaver}]
│ ├─gsd-sharing───3*[{ gsd-sharing}]
│ ├─gsd-smartcard───3*[{ gsd-smartcard}]
│ ├─gsd-sound───3*[{ gsd-sound}]
│ ├─gsd-wacom───3*[{ gsd-wacom}]
│ ├─gsd-xsettings───3*[{ gsd-xsettings}]
│ ├─gvfs-afc-volume───3*[{ gvfs-afc-volume}]
│ ├─gvfs-goa-volume───2*[{ gvfs-goa-volume}]
│ ├─gvfs-gphoto2-vo───2*[{ gvfs-gphoto2-vo}]
│ ├─gvfs-mtp-volume───2*[{ gvfs-mtp-volume}]
│ ├─gvfs-udisks2-vo───3*[{ gvfs-udisks2-vo}]
│ ├─gvfsd─┬─gvfsd-trash───2*[{ gvfsd-trash}]
│ │ └─2*[{ gvfsd}]
│ ├─gvfsd-fuse───5*[{ gvfsd-fuse}]
│ ├─gvfsd-metadata───2*[{ gvfsd-metadata}]
│ ├─ibus-portal───2*[{ ibus-portal}]
│ ├─ibus-x11───2*[{ ibus-x11}]
│ ├─pipewire───{ pipewire}
│ ├─pipewire-media-───{ pipewire-media-}
│ ├─pulseaudio───3*[{ pulseaudio}]
│ ├─sh───ibus-daemon─┬─ibus-engine-lib───3*[{ ibus-engine-lib}]
│ │ ├─ibus-extension-───3*[{ ibus-extension-}]
│ │ ├─ibus-memconf───2*[{ ibus-memconf}]
│ │ └─2*[{ ibus-daemon}]
│ ├─snap-store───6*[{ snap-store}]
│ ├─snapd-desktop-i───snapd-desktop-i───3*[{ snapd-desktop-i}]
│ ├─tracker-miner-f───5*[{ tracker-miner-f}]
│ ├─vmtoolsd───3*[{ vmtoolsd}]
│ ├─xdg-desktop-por───5*[{ xdg-desktop-por}]
│ ├─2*[ xdg-desktop-por───3*[{ xdg-desktop-por}]]
│ ├─xdg-document-po─┬─fusermount3
│ │ └─5*[{ xdg-document-po}]
│ └─xdg-permission-───2*[{ xdg-permission-}]
├─systemd-journal
├─systemd-logind
├─systemd-oomd
├─systemd-resolve
├─systemd-timesyn───{ systemd-timesyn}
├─systemd-udevd
├─udisksd───4*[{ udisksd}]
├─unattended-upgr───{ unattended-upgr}
├─upowerd───2*[{ upowerd}]
├─vmtoolsd───3*[{ vmtoolsd}]
├─vmware-vmblock-───2*[{ vmware-vmblock-}]
└─wpa_supplicant
操作系统只创建树根上面的进程,剩下所有的进程都是由树根上的进程创建的
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
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ls /sbin/init -l
lrwxrwxrwx 1 root root 20 Sep 10 2022 /sbin/init -> /lib/systemd/systemd
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ls /etc/init.d -l
total 88
-rwxr-xr-x 1 root root 3740 Feb 23 2022 apparmor
-rwxr-xr-x 1 root root 2915 May 10 2022 apport
-rwxr-xr-x 1 root root 1175 Dec 26 2021 binfmt-support
-rwxr-xr-x 1 root root 1232 Nov 23 2021 console-setup.sh
-rwxr-xr-x 1 root root 3062 Mar 18 2021 cron
-rwxr-xr-x 1 root root 3152 Jun 28 2021 dbus
-rwxr-xr-x 1 root root 1748 Feb 21 2022 hwclock.sh
-rwxr-xr-x 1 root root 2638 Oct 30 2021 irqbalance
-rwxr-xr-x 1 root root 1479 Jul 24 2021 keyboard-setup.sh
-rwxr-xr-x 1 root root 2044 Jan 8 2021 kmod
-rwxr-xr-x 1 root root 1386 Feb 23 2022 plymouth
-rwxr-xr-x 1 root root 760 Feb 23 2022 plymouth-log
-rwxr-xr-x 1 root root 959 Feb 25 2022 procps
-rwxr-xr-x 1 root root 4417 Oct 12 2022 rsync
-rwxr-xr-x 1 root root 1222 Feb 18 2021 screen-cleanup
-rwxr-xr-x 1 root root 6871 Mar 8 2022 udev
-rwxr-xr-x 1 root root 2083 Sep 19 2021 ufw
-rwxr-xr-x 1 root root 1391 Feb 19 2021 unattended-upgrades
-rwxr-xr-x 1 root root 1306 Feb 21 2022 uuidd
-rwxr-xr-x 1 root root 2762 Oct 19 2021 x11-common
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ls /bin/sh -l
lrwxrwxrwx 1 root root 4 Feb 11 05:35 /bin/sh -> dash
程序:状态机
C 代码视角:语句
汇编/机器代码视角:指令
与操作系统交互的方式:syscall
定制最小的 Linux
1
2
3
4
5
$ tree .
.
├── bin
│ └── busybox ( 可以在我们的Linux里直接执行)
└── init
加上 vmlinuz
(内核镜像) 就可以在 QEMU
里启动了
可以直接在文件系统中添加静态链接的二进制文件
Makefile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.PHONY : initramfs run clean
$( shell mkdir -p build )
initramfs :
@cd initramfs && find . -print0 | cpio --null -ov --format= newc | gzip -9 \
> ../build/initramfs.cpio.gz
run :
@qemu-system-x86_64 \
-nographic \
-serial mon:stdio \
-m 128 \
-kernel vmlinuz \
-initrd build/initramfs.cpio.gz \
-append "console=ttyS0 quiet acpi=off"
clean :
@rm -rf build
1
2
sudo apt-get install qemu
sudo apt-get install qemu-system
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
sh: can't access tty; job control turned off
/ # QEMU 6.2.0 monitor - type ' help' for more information
( qemu) info registers
RAX = 0001a94000000000 RBX = 0000000000000000 RCX = 0000000000000001 RDX = 0000000000000ca2
RSI = 0000000000000087 RDI = 0000000000000087 RBP = ffffffffa3c03e28 RSP = ffffffffa3c03e08
R8 = ffff88a5c781df80 R9 = 0000000000000200 R10 = 0000000000000000 R11 = 0000000000000000
R12 = 0000000000000000 R13 = ffffffffa3c13780 R14 = 0000000000000000 R15 = 0000000000000000
RIP = ffffffffa30d564e RFL = 00000246 [ ---Z-P-] CPL = 0 II = 0 A20 = 1 SMM = 0 HLT = 1
ES = 0000 0000000000000000 00000000 00000000
CS = 0010 0000000000000000 ffffffff 00af9b00 DPL = 0 CS64 [ -RA]
SS = 0018 0000000000000000 ffffffff 00cf9300 DPL = 0 DS [ -WA]
DS = 0000 0000000000000000 00000000 00000000
FS = 0000 0000000000000000 00000000 00000000
GS = 0000 ffff88a5c7800000 00000000 00000000
LDT = 0000 0000000000000000 00000000 00008200 DPL = 0 LDT
TR = 0040 fffffe0000003000 0000206f 00008900 DPL = 0 TSS64-avl
GDT = fffffe0000001000 0000007f
IDT = fffffe0000000000 00000fff
CR0 = 80050033 CR2 = 0000000000e22c18 CR3 = 0000000003ca8000 CR4 = 000006f0
DR0 = 0000000000000000 DR1 = 0000000000000000 DR2 = 0000000000000000 DR3 = 0000000000000000
DR6 = 00000000ffff0ff0 DR7 = 0000000000000400
EFER = 0000000000000d01
FCW = 037f FSW = 0000 [ ST = 0] FTW = 00 MXCSR = 00001f80
FPR0 = 0000000000000000 0000 FPR1 = 0000000000000000 0000
FPR2 = 0000000000000000 0000 FPR3 = 0000000000000000 0000
FPR4 = 0000000000000000 0000 FPR5 = 0000000000000000 0000
FPR6 = 0000000000000000 0000 FPR7 = 0000000000000000 0000
XMM00 = 0000000000e20450 0000000000000400 XMM01 = 0000000000000000 0000000000000000
XMM02 = 0000000000000000 0000000000000000 XMM03 = 0000000000000000 0000000000000000
XMM04 = ffffffffffffffff ffffffff00000000 XMM05 = 0000000048094038 3028201810080072
XMM06 = 0000000000ff0000 000000ff00000000 XMM07 = 0000000400000004 0000000400000004
XMM08 = 00000000008d93e0 00000000008d93e0 XMM09 = 0000000000000000 0000000000000000
XMM10 = 0000000000000000 0000000000000000 XMM11 = 0000000000000000 0000000000000000
XMM12 = 0000000000000000 0000000000000000 XMM13 = 0000000000000000 0000000000000000
XMM14 = 0000000000000000 0000000000000000 XMM15 = 0000000000000000 0000000000000000
变魔术时间到
1
2
3
4
5
6
7
8
9
c1 = "arch ash base64 cat chattr chgrp chmod chown conspy cp cpio cttyhack date dd df dmesg dnsdomainname dumpkmap echo ed egrep false fatattr fdflush fgrep fsync getopt grep gunzip gzip hostname hush ionice iostat ipcalc kbd_mode kill link linux32 linux64 ln login ls lsattr lzop makemime mkdir mknod mktemp more mount mountpoint mpstat mt mv netstat nice nuke pidof ping ping6 pipe_progress printenv ps pwd reformime resume rev rm rmdir rpm run-parts scriptreplay sed setarch setpriv setserial sh sleep stat stty su sync tar touch true umount uname usleep vi watch zcat"
c2 = "[ [[ awk basename bc beep blkdiscard bunzip2 bzcat bzip2 cal chpst chrt chvt cksum clear cmp comm crontab cryptpw cut dc deallocvt diff dirname dos2unix dpkg dpkg-deb du dumpleases eject env envdir envuidgid expand expr factor fallocate fgconsole find flock fold free ftpget ftpput fuser groups hd head hexdump hexedit hostid id install ipcrm ipcs killall last less logger logname lpq lpr lsof lspci lsscsi lsusb lzcat lzma man md5sum mesg microcom mkfifo mkpasswd nc nl nmeter nohup nproc nsenter nslookup od openvt passwd paste patch pgrep pkill pmap printf pscan"
c3 = "pstree pwdx readlink realpath renice reset resize rpm2cpio runsv runsvdir rx script seq setfattr setkeycodes setsid setuidgid sha1sum sha256sum sha3sum sha512sum showkey shred shuf smemcap softlimit sort split ssl_client strings sum sv svc svok tac tail taskset tcpsvd tee telnet test tftp time timeout top tr traceroute traceroute6 truncate ts tty ttysize udhcpc6 udpsvd unexpand uniq unix2dos unlink unlzma unshare unxz unzip uptime users uudecode uuencode vlock volname w wall wc wget which who whoami whois xargs xxd xz xzcat yes"
for cmd in $c1 $c2 $c3 ; do
/bin/busybox ln -s /bin/busybox /bin/$cmd
done
mkdir -p /proc && mount -t proc none /proc
mkdir -p /sys && mount -t sysfs none /sys
export PS1 = '(linux) '
创建链接,创建目录,挂载了一些内核的文件系统,将操作系统一些内部状态暴露给应用程序 -> 变成linux
命令也可以用
例子:NOILinux-lite
小结:应用程序视角的操作系统
Linux
操作系统启动流程
CPU Reset
→ Firmware
→ Loader
→ Kernel _start()
→ 第一个程序 /bin/init
→ 程序 (状态机) 执行 + 系统调用
操作系统为 (所有) 程序提供 API
进程 (状态机) 管理
fork, execve, exit
- 状态机的创建/改变/删除 ← 今天的主题
存储(内存) (地址空间) 管理
文件 (数据对象) 管理
open, close, read, write
- 文件访问管理
mkdir, link, unlink
- 目录管理
fork()
操作系统:状态机的管理者
状态机管理:创建状态机
如果要创建状态机,我们应该提供什么样的 API
?
UNIX
的答案: fork
int fork();
立即复制状态机 (完整的内存)
新创建进程返回 0
执行 fork
的进程返回子进程的进程号
Fork Bomb
代码解析: Fork Bomb
1
2
3
4
5
6
7
8
9
:(){ :| :& } ; : # 刚才的一行版本
:() { # 格式化一下
: | : &
} ; :
fork() { # bash: 允许冒号作为标识符……
fork | fork &
} ; fork
这次你们记住 Fork 了!
1
2
3
4
5
6
7
8
9
10
systemd-+-accounts-daemon---2*[{ accounts-daemon}]
| -agetty
| -atd
| -automount---2*[{ automount}]
| -avahi-daemon---avahi-daemon
| -cron
| -dbus-daemon
| -irqbalance---{ irqbalance}
| -lxcfs---7*[{ lxcfs}]
...
理解 fork: 习题 (1)
1
2
3
4
5
6
7
8
9
#include <unistd.h>
#include <stdio.h>
int main () {
pid_t pid1 = fork ();
pid_t pid2 = fork ();
pid_t pid3 = fork ();
printf ( "Hello World from (%d, %d, %d) \n " , pid1 , pid2 , pid3 );
}
Including the initial parent process, how many processes are created by the program shown in Figure 3.31?
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>
int main () {
/* fork a child process */
fork ();
/* fork another child process */
fork ();
/* and fork another */
fork ();
return 0 ;
}
$$
1+3+3+1=8
$$
理解 fork: 习题 (2)
问以下程序的输出结果
一个更好的版本: fork-printf.c
用状态机的视角再试一次
试一试:./a.out
v.s. ./a.out | cat
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
int main ( int argc , char * argv []) {
int n = 2 ;
for ( int i = 0 ; i < n ; i ++ ) {
fork ();
printf ( "Hello \n " );
}
for ( int i = 0 ; i < n ; i ++ ) {
wait ( NULL );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./fork-printf
Hello
Hello
Hello
Hello
Hello
Hello
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./fork-printf | wc -l
8
1
2
3
4
5
6
7
8
9
10
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./fork-printf | cat
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
1
2
3
4
5
6
7
8
#include <stdio.h>
int main () {
printf ( "Hello" );
int * p ;
p = NULL ;
* p = 1 ;
}
1
2
3
4
5
6
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ gcc demo.c -o demo
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./demo
zsh: segmentation fault ./demo
1
2
3
4
5
6
7
8
#include <stdio.h>
int main () {
printf ( "Hello \n " );
int * p ;
p = NULL ;
* p = 1 ;
}
1
2
3
4
5
6
7
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ gcc demo.c -o demo
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./demo
Hello
zsh: segmentation fault ./demo
main.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main () {
printf ( "a" );
fflush ( stdout );
pid_t pid = fork ();
if ( pid < 0 ) {
printf ( "Error! \n " );
} else if ( pid == 0 ) {
execl ( "child" , "" , NULL );
exit ( 0 );
} else {
wait ( NULL );
printf ( "c" );
}
return 0 ;
}
child.c
1
2
3
4
5
6
7
#include <stdio.h>
#include <unistd.h>
int main () {
printf ( "b" );
return 0 ;
}
在调用fork()
前先printf("a")
,在fork()
后的子进程里printf("b")
,这样结果按理来说是打印"ab"
,但是运行出来反而是"ba"
。 这个问题的原因是c
语言的输出缓冲,在调用printf()
函数时,数据先被存放在缓冲区中,待缓冲区满了或者遇到了换行符"\n"
时才会输出到屏幕上。 printf("a")
语句输出字符"a"
,但由于缓冲区未满,字符"a"
并没有被立即输出。当程序调用fork()
函数创建子进程时,子进程也继承了父进程的缓冲区,这时缓冲区中的数据包括字符"a"也被复制到了子进程的缓冲区中。 接着,在子进程中,printf("b")
语句输出字符"b"
,由于子进程的缓冲区已经满了,因此字符"a"
和字符"b"
都被输出到屏幕上,此时的输出结果是"ba"
。
解决的方法是在printf("a")
语句后加上fflush(stdout)
语句,强制将缓冲区的数据输出到屏幕上,这样输出的结果就是"ab"
。
1
2
3
4
5
6
7
8
9
10
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./fork-printf | cat
Hello
Hello
Hello
Hello
Hello
Hello
Hello
Hello
树状图
另一种解决方案:setbuf(stdout,NULL);
强行将标准输出流设置为不缓冲
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <sys/wait.h>
#include <unistd.h>
int main ( int argc , char * argv []) {
setbuf ( stdout , NULL );
int n = 2 ;
for ( int i = 0 ; i < n ; i ++ ) {
fork ();
printf ( "Hello \n " );
}
for ( int i = 0 ; i < n ; i ++ ) {
wait ( NULL );
}
}
1
2
3
4
5
6
7
8
9
10
11
12
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./fork-printf | cat
Hello
Hello
Hello
Hello
Hello
Hello
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./fork-printf | wc -l
6
理解 fork: 习题 (3)
execve()
状态机管理:替换状态机
光有 fork
还不够,怎么 “执行别的程序”?
1
int execve ( const char * filename , char * const argv , char * const envp );
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <stdio.h>
int main () {
char * const argv [] = {
"/bin/bash" , "-c" , "env" , NULL ,
};
char * const envp [] = {
"HELLO=WORLD" , NULL ,
};
execve ( argv [ 0 ], argv , envp );
printf ( "Hello, World! \n " );
}
1
2
3
4
5
6
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./execve-demo
PWD = /mnt/d/work for vscode/chapter11
HELLO = WORLD
SHLVL = 0
_ = /usr/bin/env
printf
没有被执行(原来的状态机没有了)
参照
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ bash -c ls
demo demo.c execve-demo execve-demo.c fork-demo fork-demo.c fork-printf fork-printf.c l.zip linux-minimal thread-os.c
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ bash -c env
SHELL = /usr/bin/zsh
USER_ZDOTDIR = /home/jungle
COLORTERM = truecolor
WSL2_GUI_APPS_ENABLED = 1
TERM_PROGRAM_VERSION = 1.78.0
WSL_DISTRO_NAME = Ubuntu-22.04
LESS_TERMCAP_se =
LESS_TERMCAP_so =
NAME = LAPTOP-A7S3TAA4
PWD = /mnt/d/work for vscode/chapter11
LOGNAME = jungle
_ = /usr/bin/env
...
...
...
环境变量
“应用程序执行的环境”
使用env
命令查看
PATH
: 可执行文件搜索路径
PWD
: 当前路径
HOME
: home 目录
DISPLAY
: 图形输出
PS1
: shell 的提示符
export
告诉 shell 在创建子进程时设置环境变量
小技巧:export ARCH=x86_64-qemu
或 export ARCH=native
上学期的 AM_HOME
终于破案了(这是南大的,邮专上学期还在写数据结构)
1
2
3
4
5
6
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ export HELLO = WORLD
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ env | grep HELLO
HELLO = WORLD
第一个调用总是execve()
1
2
3
4
5
6
7
8
9
...
mprotect( 0x7f373bcba000, 8192, PROT_READ) = 0
prlimit64( 0, RLIMIT_STACK, NULL, { rlim_cur = 8192*1024, rlim_max = RLIM64_INFINITY}) = 0
munmap( 0x7f373bc74000, 46367) = 0
execve( "/bin/bash" , [ "/bin/bash" , "-c" , "env" ] , 0x7fffa5c787c0 /* 1 var */) = 0
brk( NULL) = 0x559dfe4ca000
arch_prctl( 0x3001 /* ARCH_??? */, 0x7ffe8ef996f0) = -1 EINVAL ( Invalid argument)
mmap( NULL, 8192, PROT_READ| PROT_WRITE, MAP_PRIVATE| MAP_ANONYMOUS, -1, 0) = 0x7f476f6f0000
...
1
2
3
4
5
6
7
8
9
10
...
write( 1, "PWD=/mnt/d/work for vscode/chapt" ..., 37PWD = /mnt/d/work for vscode/chapter11
) = 37
write( 1, "HELLO=WORLD\n" , 12HELLO = WORLD
) = 12
write( 1, "SHLVL=0\n" , 8SHLVL = 0
) = 8
write( 1, "_=/usr/bin/env\n" , 15_ = /usr/bin/env
) = 15
...
环境变量:PATH
1
2
3
4
[ pid 28369] execve( "/usr/local/sbin/as" , [ "as" , "--64" , ...
[ pid 28369] execve( "/usr/local/bin/as" , [ "as" , "--64" , ...
[ pid 28369] execve( "/usr/sbin/as" , [ "as" , "--64" , ...
[ pid 28369] execve( "/usr/bin/as" , [ "as" , "--64" , ...
1
2
3
$ PATH = "" /usr/bin/gcc a.c
gcc: error trying to exec 'as' : execvp: No such file or directory
$ PATH = "/usr/bin/" gcc a.c
fork
是状态机的复制,execve
是状态机的重置,环境变量就是重置状态机的参数
_exit()
状态机管理:终止状态机
有了 fork
, execve
我们就能自由执行任何程序了,最后只缺一个销毁状态机的函数!
1
void _exit ( int status );
销毁当前状态机,并允许有一个返回值
子进程终止会通知父进程 (后续课程解释)
这个简单……
结束程序执行的三种方法
不妨试一试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#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 ;
if ( strcmp ( argv [ 1 ], "exit" ) == 0 )
exit ( 0 );
if ( strcmp ( argv [ 1 ], "_exit" ) == 0 )
_exit ( 0 );
if ( strcmp ( argv [ 1 ], "__exit" ) == 0 )
syscall ( SYS_exit , 0 );
}
1
2
3
4
5
6
7
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./exit-demo
Goodbye, Cruel OS World!
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./exit-demo exit
Goodbye, Cruel OS World!
使用static
编译,省去链接过程,便于查看strace
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ strace ./exit-demo
execve( "./exit-demo" , [ "./exit-demo" ] , 0x7ffdaa2866a0 /* 42 vars */) = 0
arch_prctl( 0x3001 /* ARCH_??? */, 0x7ffd390434e0) = -1 EINVAL ( Invalid argument)
brk( NULL) = 0x9b6000
brk( 0x9b6dc0) = 0x9b6dc0
arch_prctl( ARCH_SET_FS, 0x9b63c0) = 0
set_tid_address( 0x9b6690) = 8090
set_robust_list( 0x9b66a0, 24) = 0
rseq( 0x9b6d60, 0x20, 0, 0x53053053) = 0
uname({ sysname = "Linux" , nodename = "LAPTOP-A7S3TAA4" , ...}) = 0
prlimit64( 0, RLIMIT_STACK, NULL, { rlim_cur = 8192*1024, rlim_max = RLIM64_INFINITY}) = 0
readlink( "/proc/self/exe" , "/mnt/d/work for vscode/chapter11" ..., 4096) = 42
getrandom( "\xfc\x1b\x9a\x3b\x0c\x0d\xf6\x62" , 8, GRND_NONBLOCK) = 8
brk( 0x9d7dc0) = 0x9d7dc0
brk( 0x9d8000) = 0x9d8000
mprotect( 0x4c1000, 16384, PROT_READ) = 0
newfstatat( 1, "" , { st_mode = S_IFCHR| 0620, st_rdev = makedev( 0x88, 0x7) , ...} , AT_EMPTY_PATH) = 0
write( 1, "Goodbye, Cruel OS World!\n" , 25Goodbye, Cruel OS World!
) = 25
exit_group( 1) = ?
+++ exited with 1 +++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ strace ./exit-demo _exit
execve( "./exit-demo" , [ "./exit-demo" , "_exit" ] , 0x7ffdcbeb2108 /* 42 vars */) = 0
arch_prctl( 0x3001 /* ARCH_??? */, 0x7fff2fcb4900) = -1 EINVAL ( Invalid argument)
brk( NULL) = 0x1346000
brk( 0x1346dc0) = 0x1346dc0
arch_prctl( ARCH_SET_FS, 0x13463c0) = 0
set_tid_address( 0x1346690) = 8124
set_robust_list( 0x13466a0, 24) = 0
rseq( 0x1346d60, 0x20, 0, 0x53053053) = 0
uname({ sysname = "Linux" , nodename = "LAPTOP-A7S3TAA4" , ...}) = 0
prlimit64( 0, RLIMIT_STACK, NULL, { rlim_cur = 8192*1024, rlim_max = RLIM64_INFINITY}) = 0
readlink( "/proc/self/exe" , "/mnt/d/work for vscode/chapter11" ..., 4096) = 42
getrandom( "\x1f\x9d\x12\x25\xe1\x65\xc9\x7b" , 8, GRND_NONBLOCK) = 8
brk( 0x1367dc0) = 0x1367dc0
brk( 0x1368000) = 0x1368000
mprotect( 0x4c1000, 16384, PROT_READ) = 0
exit_group( 0) = ?
+++ exited with 0 +++
1
2
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ ./exit-demo _exit
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
┌──( jungle㉿LAPTOP-A7S3TAA4) -[ /mnt/d/work for vscode/chapter11]
└─$ strace ./exit-demo __exit
execve( "./exit-demo" , [ "./exit-demo" , "__exit" ] , 0x7ffeca752f48 /* 42 vars */) = 0
arch_prctl( 0x3001 /* ARCH_??? */, 0x7ffe37ad6940) = -1 EINVAL ( Invalid argument)
brk( NULL) = 0x15df000
brk( 0x15dfdc0) = 0x15dfdc0
arch_prctl( ARCH_SET_FS, 0x15df3c0) = 0
set_tid_address( 0x15df690) = 8256
set_robust_list( 0x15df6a0, 24) = 0
rseq( 0x15dfd60, 0x20, 0, 0x53053053) = 0
uname({ sysname = "Linux" , nodename = "LAPTOP-A7S3TAA4" , ...}) = 0
prlimit64( 0, RLIMIT_STACK, NULL, { rlim_cur = 8192*1024, rlim_max = RLIM64_INFINITY}) = 0
readlink( "/proc/self/exe" , "/mnt/d/work for vscode/chapter11" ..., 4096) = 42
getrandom( "\xed\x64\xb4\xdb\x93\xb2\x77\xd0" , 8, GRND_NONBLOCK) = 8
brk( 0x1600dc0) = 0x1600dc0
brk( 0x1601000) = 0x1601000
mprotect( 0x4c1000, 16384, PROT_READ) = 0
exit( 0) = ?
+++ exited with 0 +++
exit
只关闭一个线程,_exit
$\rightarrow^{Syscall}$ exit_group
会把整个所有的线程都删掉
linux
默认用的_exit
,安全
总结
本次课回答的问题
Q1 : 操作系统启动后到底做了什么?
Q2 : 操作系统如何管理程序 (进程)?
Take-away messages
进程管理 API
fork, execve, exit: 状态机的复制、重置、销毁
理论上就可以实现 “各种功能” 了!
声明:本文章引用资料与图像均已做标注,如有侵权本人会马上删除