目录
基本概念
fork() creates a new process by duplicating the calling process. The new process is referred to as the child process. The calling process is referred to as the parent process.
fork()是一个系统调用,不是一个函数。详细信息可以,man fork。
Linux进程闭知必会
- 每个进程都有一个唯一的标识(PID)
- 每个进程都是由另一个进程创建而来(即:父进程)
问题
- 问题一:第一个进程是什么?init进程
- 问题二:如何创建进程?execve(...)
Linux进程树
- 整个Linux系统的所有进程构成一个树状结构
- 树根由内核自动创建,即:IDLE(PID -> 0)
- 系统中的第一个进程是 init / systemd (PID -> 1)
- 0号进程创建1号进程,1号进程负责完成内核部分初始化工作 (内核模式)
- 1号进程加载执行初始化程序,演变为用户态1号进程 (用户模式)
wj@ubuntu:~/DTThread/03$ pstree
systemd─┬─ModemManager───2*[{ModemManager}]
├─NetworkManager───2*[{NetworkManager}]
├─VGAuthService
├─accounts-daemon───2*[{accounts-daemon}]
├─acpid
├─at-spi-bus-laun─┬─dbus-daemon
│ └─3*[{at-spi-bus-laun}]
├─at-spi2-registr───2*[{at-spi2-registr}]
├─avahi-daemon───avahi-daemon
├─bluetoothd
├─colord───2*[{colord}]
├─containerd───10*[{containerd}]
├─cron
├─cups-browsed───2*[{cups-browsed}]
├─cupsd───dbus
├─4*[dbus-daemon]
├─dbus-launch
├─dconf-service───2*[{dconf-service}]
├─dockerd───13*[{dockerd}]
├─evolution-addre───5*[{evolution-addre}]
├─evolution-calen───8*[{evolution-calen}]
├─evolution-sourc───3*[{evolution-sourc}]
├─fcitx
├─fcitx───2*[{fcitx}]
├─2*[fcitx-dbus-watc]
├─gdm3─┬─gdm-session-wor─┬─gdm-x-session─┬─Xorg───{Xorg}
│ │ │ ├─gnome-session-b─┬─fcitx
│ │ │ │ ├─ssh-agent
│ │ │ │ └─2*[{gnome-session-b}]
│ │ │ └─2*[{gdm-x-session}]
│ │ └─2*[{gdm-session-wor}]
│ └─2*[{gdm3}]
├─gnome-keyring-d───3*[{gnome-keyring-d}]
├─goa-daemon───3*[{goa-daemon}]
├─goa-identity-se───2*[{goa-identity-se}]
├─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-metadata───2*[{gvfsd-metadata}]
├─irqbalance───{irqbalance}
├─2*[kerneloops]
├─networkd-dispat
├─polkitd───2*[{polkitd}]
├─rsyslogd───3*[{rsyslogd}]
├─rtkit-daemon───2*[{rtkit-daemon}]
├─snapd───13*[{snapd}]
├─sogoupinyin-ser───8*[{sogoupinyin-ser}]
├─sogoupinyin-wat───4*[{sogoupinyin-wat}]
├─ssh-agent
├─switcheroo-cont───2*[{switcheroo-cont}]
├─systemd─┬─(sd-pam)
│ ├─at-spi-bus-laun─┬─dbus-daemon
│ │ └─3*[{at-spi-bus-laun}]
│ ├─at-spi2-registr───2*[{at-spi2-registr}]
│ ├─chrome─┬─2*[cat]
│ │ ├─chrome───chrome───18*[{chrome}]
│ │ ├─chrome─┬─chrome─┬─chrome───7*[{chrome}]
│ │ │ │ ├─2*[chrome───14*[{chrome}]]
│ │ │ │ └─chrome───8*[{chrome}]
│ │ │ └─nacl_helper
│ │ ├─chrome───12*[{chrome}]
│ │ └─35*[{chrome}]
│ ├─2*[chrome_crashpad───2*[{chrome_crashpad}]]
│ ├─chrome_crashpad───{chrome_crashpad}
│ ├─code─┬─code───code───13*[{code}]
│ │ ├─code───code───code───16*[{code}]
│ │ ├─code───5*[{code}]
│ │ ├─code─┬─bash
│ │ │ └─14*[{code}]
│ │ ├─code───13*[{code}]
│ │ ├─code───15*[{code}]
│ │ ├─code─┬─cpptools───17*[{cpptools}]
│ │ │ └─13*[{code}]
│ │ └─29*[{code}]
│ ├─2*[cpptools-srv───15*[{cpptools-srv}]]
│ ├─dbus-daemon
│ ├─dconf-service───2*[{dconf-service}]
│ ├─evolution-addre───5*[{evolution-addre}]
│ ├─evolution-calen───8*[{evolution-calen}]
│ ├─evolution-sourc───3*[{evolution-sourc}]
│ ├─gjs───10*[{gjs}]
│ ├─gnome-session-b─┬─evolution-alarm───5*[{evolution-alarm}]
│ │ ├─gsd-disk-utilit───2*[{gsd-disk-utilit}]
│ │ ├─indicator-messa───3*[{indicator-messa}]
│ │ └─3*[{gnome-session-b}]
│ ├─gnome-session-c───{gnome-session-c}
│ ├─gnome-shell─┬─ibus-daemon─┬─ibus-engine-lib───3*[{ibus-engine-lib}]
│ │ │ ├─ibus-extension-───3*[{ibus-extension-}]
│ │ │ ├─ibus-memconf───2*[{ibus-memconf}]
│ │ │ └─2*[{ibus-daemon}]
│ │ └─11*[{gnome-shell}]
│ ├─gnome-shell-cal───5*[{gnome-shell-cal}]
│ ├─gnome-terminal-─┬─bash───pstree
│ │ └─4*[{gnome-terminal-}]
│ ├─goa-daemon───3*[{goa-daemon}]
│ ├─goa-identity-se───2*[{goa-identity-se}]
│ ├─gpg-agent
│ ├─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───4*[{gsd-smartcard}]
│ ├─gsd-sound───3*[{gsd-sound}]
│ ├─gsd-usb-protect───3*[{gsd-usb-protect}]
│ ├─gsd-wacom───2*[{gsd-wacom}]
│ ├─gsd-wwan───3*[{gsd-wwan}]
│ ├─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}]
│ ├─pulseaudio───3*[{pulseaudio}]
│ ├─snap-store───4*[{snap-store}]
│ ├─tracker-miner-f───4*[{tracker-miner-f}]
│ ├─xdg-desktop-por───4*[{xdg-desktop-por}]
│ ├─xdg-desktop-por───3*[{xdg-desktop-por}]
│ ├─xdg-document-po───6*[{xdg-document-po}]
│ ├─xdg-permission-───2*[{xdg-permission-}]
│ ├─xfce4-notifyd───2*[{xfce4-notifyd}]
│ └─xfconfd───2*[{xfconfd}]
├─systemd-journal
├─systemd-logind
├─systemd-resolve
├─systemd-timesyn───{systemd-timesyn}
├─systemd-udevd
├─udisksd───4*[{udisksd}]
├─unattended-upgr───{unattended-upgr}
├─update-manager───3*[{update-manager}]
├─upowerd───2*[{upowerd}]
├─vmtoolsd───3*[{vmtoolsd}]
├─vmtoolsd───{vmtoolsd}
├─vmware-vmblock-───2*[{vmware-vmblock-}]
├─vncserver─┬─(sd-pam)
│ ├─Xtigervnc
│ └─xfce4-session─┬─Thunar───2*[{Thunar}]
│ ├─agent───2*[{agent}]
│ ├─applet.py
│ ├─evolution-alarm───5*[{evolution-alarm}]
│ ├─nm-applet───3*[{nm-applet}]
│ ├─tracker-miner-f───4*[{tracker-miner-f}]
│ ├─update-notifier───3*[{update-notifier}]
│ ├─xfce4-panel─┬─panel-10-notifi───2*[{panel-10-notifi}]
│ │ ├─panel-14-action───2*[{panel-14-action}]
│ │ ├─panel-6-systray───2*[{panel-6-systray}]
│ │ ├─panel-8-pulseau───2*[{panel-8-pulseau}]
│ │ ├─panel-9-power-m───2*[{panel-9-power-m}]
│ │ └─2*[{xfce4-panel}]
│ ├─xfdesktop───2*[{xfdesktop}]
│ ├─xfwm4───19*[{xfwm4}]
│ ├─xiccd───2*[{xiccd}]
│ └─2*[{xfce4-session}]
├─whoopsie───2*[{whoopsie}]
├─wpa_supplicant
├─xfce4-notifyd───2*[{xfce4-notifyd}]
├─xfce4-power-man───2*[{xfce4-power-man}]
├─xfconfd───2*[{xfconfd}]
└─xfsettingsd───2*[{xfsettingsd}]
在Linux终端运行 pstree,可以看到上述输出。可以看到第 1号进程为:systemd
wj@ubuntu:~/DTThread/03$ echo $$
4785
wj@ubuntu:~/DTThread/03$ pstree -A -p -s $$
systemd(1)---systemd(1094)---gnome-terminal-(4777)---bash(4785)---pstree(5854)
通过 echo $$ ,可以看到当前这个命令行进程的进程号为:4785,它的父进程为:gnome-terminal-(4777) ,4777的父进程为:systemd(1094) ,1094的父进程为:systemd(1)
疑问:上述输出的进程树,为什么有2个 systemd进程?
systemd(1)为初始化进程,进行了一部分初始化工作。这个 systemd(1)初始化进程,创建了systemd(1094)进程 进行了更多的初始化工作。
wj@ubuntu:~/DTThread/03$ sleep 200 &
[1] 5895
wj@ubuntu:~/DTThread/03$ sleep 200 &
[2] 5896
wj@ubuntu:~/DTThread/03$ sleep 200 &
[3] 5897
wj@ubuntu:~/DTThread/03$ pstree -A -p -s $$
systemd(1)---systemd(1094)---gnome-terminal-(4777)---bash(4785)-+-pstree(5898)
|-sleep(5895)
|-sleep(5896)
`-sleep(5897)
通过上述输出,我们可以看到通过 sleep 200 & 创建了3个 sleep进程。他们都是 bash(4785)号进程的子进程。
如何查看进程ID
每个linux进程都一定有一个唯一的数字标识符,称为进程ID(process ID),进程ID总是一非负整数,它的父进程叫PPID。
查看进程ID命令:
ps -ef
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回:调用进程的进程ID
pid_t getppid(void); 返回:调用进程的父进程ID
第一个进程init
Linux内核启动之后,会创建第一个用户级进程init,由上图可知, init 进程 (pid=1) 是除了 idle 进程 (pid=0,也就是 init_task) 之外另一个比较特殊的进程,它是 Linux 内核开始建立起进程概念时第一个通过 kernel_thread 产生的进程,其开始在内核态执行,然后通过一个系统调用,开始执行用户空间的 / sbin/init 程序。
fork系统调用
创建一个进程很简单,先来认识一下fork系统调用:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
返回:子进程中为0,父进程中为子进程I D,出错为-1
由fork创建的新进程被称为子进程( child process)。该函数被调用一次,但返回两次,两次返回的区别是子进程的返回值是0,而父进程的返回值则是子进程的进程ID。一般来说,在fork()之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。
#include <unistd.h>
#include <stdio.h>
int main(int argc,char* argv[])
{
pid_t pid;
if((pid=fork()) == -1)
{
perror("fork\n");
return -1;
}
else if(pid == 0)
{
printf("this is child process\n");
printf("The return value %d in child process! My Pid is %d,My ppid is %d\n",pid,getpid(),getppid());
}
else
{
//usleep(1000);
printf("this is parent process\n");
printf("The return value %d in parent process! My Pid is %d,My ppid is %d\n",pid,getpid(),getppid());
}
return 0;
}
由上图可知,fork被调用了一次,返回了两次,一次是父进程执行,另外一次是子进程执行。
fork()系统调用的工作方式
execve(...)的工作方式
- 根据参数路径加载可执行程序 ==> 加载进当前进程
- 通过可执行程序信息构建进程数据,并写入当前进程空间
- 将程序执行位置重置到入口地址处(即:main())
- execve(...)将重置当前进程空间(代码&数据),而不会创建新进程。
execve(const char* pathname,char* const argv[],char* const envp[])系统调用
在当前进程中执行 pathname 指定的程序代码。
先创建进程,才能执行程序代码!
demo
来看 一个简单的 demo.
helloworld.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
printf("exec = %d, %s\n",getpid(),"Hello,World");
return 0;
}
test.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#define EXE "helloworld.out"
int main(int argc,char* argv[])
{
printf("Begin\n");
printf("current pid = %d\n",getpid());
execve(EXE,NULL,NULL);
printf("End\n");
return 0;
}
编译运行 test.c,可以看到如下结果输出:
wj@ubuntu:~/DTThread/03$ gcc test.c -o test.out
test.c: In function ‘main’:
test.c:14:5: warning: null argument where non-null required (argument 2) [-Wnonnull]
14 | execve(EXE,NULL,NULL);
| ^~~~~~
wj@ubuntu:~/DTThread/03$ ./test.out
Begin
current pid = 12096
exec = 12096, Hello,World
wj@ubuntu:~/DTThread/03$
可以看到:运行 test.out 并没有输出 End,而是转向执行 helloworld.out 进程去了。而且输出的两个 pid是一样的。
为什么没有输出 End? 因为 execve(...)将重置当前进程空间(代码&数据),而不会创建新进程。
改进demo
execve 和 fork 相结合,创建一个子进程。
改进后的 test.c 如下所示:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#define EXE "helloworld.out"
int create_process(char* path,char* args[])
{
int ret = fork();
if(ret == 0)
{
execve(path,args,NULL);
}
return ret;
}
int main(int argc,char* argv[])
{
printf("Begin\n");
char* args[] = {EXE,NULL};
printf("current pid = %d\n",getpid());
create_process(EXE,args);
printf("End\n");
return 0;
}
wj@ubuntu:~/DTThread/03$ gcc test.c -o test.out
wj@ubuntu:~/DTThread/03$ ./test.out
Begin
current pid = 12296
End
wj@ubuntu:~/DTThread/03$ exec = 12297, Hello,World
^C
wj@ubuntu:~/DTThread/03$
可以看到这次的 pid 是不一样的。这次是真正的创建了一个子进程了。
父子进程关系
使用fork系统调用得到的子进程是父进程的处继承了整个进程的地址空间,包括:[进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端]等。
fork出的子进程会继承父进程的文件描述符,如果读写文件,父子进程之间会互相影响。
./run.out 运行的程序父进程是谁?
我们来编写一个例子:
#include <unistd.h>
#include <stdio.h>
int main(int argc,char* argv[])
{
while(1);
return 0;
}
例子很简单,就是创建一个死循环的进程,编译执行。
ps -ef 查看进程ID
ps -ef > 1.txt
wj 40762 1648 0 2月23 ? 00:00:16 /usr/libexec/gnome-terminal-server
wj 558001 40762 0 11:05 pts/3 00:00:00 bash
wj 583066 558001 99 16:22 pts/3 00:01:19 ./run.out
-
从以上log可以看出来,run.out进程PID为583066,它的父进程为 558001,进程558001即为bash进程。
-
bash 的父进程为:/usr/libexec/gnome-terminal-server
-
所以大家应该明白,我们打开1个Linux终端,其实就是启动了1个gnome-terminal-server进程。我们在这个终端上执行./run.out其实就是利用gnome-terminal-server的子进程bash通过execve()将创建的子进程装入./run.out
父进程死了,子进程怎么办?
僵尸进程
如上图所示,父进程Process A创建子进程Process B,当子进程退出时会给父进程发送信号SIGCHLD;
-
如果父进程没有调用wait等待子进程结束,退出状态丢失,转换成僵死状态,子进程会变成一个僵尸进程。当父进程调用wait,僵尸子进程的结束状态被提取出来,子进程被删除,并且wait函数立刻返回。
-
当父进程调用wait,僵尸子进程的结束状态被提取出来,子进程被删除,并且wait函数立刻返回。
实例:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
if(fork())
{
while(1)
{
sleep(1);
}
}
return 0;
}
上述程序会保证父进程不退出,一直在while(1)中无限循环,而子进程会立刻退出。
由上图可知,父进程是585663,子进程是585664,子进程因为退出后父进程没有调用wait回收子进程资源,所以子进程585664变成僵尸进程defunct。
ps -ax | grep run.out 查看进程状态,进程状态为:Z+
孤儿进程
如果父进程退出,并且没有调用wait函数,它的子进程就变成孤儿进程,会被一个特殊进程继承,这就是init进程,init 进程会自动清理所有它继承的僵尸进程。
实例代码:
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
int main(int argc,char* argv[])
{
if(fork())
{
// parent process.
}
else
{
//child process.
while(1)
{
sleep(1);
}
}
return 0;
}
上述程序会保证子进程不退出,一直在while(1)中无限循环,而父进程会立刻退出。孤儿进程:
待确认?