fork()出来一个进程,这个进程的父进程是从哪来的?

目录

基本概念

Linux进程闭知必会

问题

Linux进程树

如何查看进程ID

第一个进程init

fork系统调用

execve(...)的工作方式

demo

改进demo

父子进程关系

./run.out 运行的程序父进程是谁?

父进程死了,子进程怎么办?

僵尸进程

孤儿进程


基本概念

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进程闭知必会

  1. 每个进程都有一个唯一的标识(PID)
  2. 每个进程都是由另一个进程创建而来(即:父进程)

问题

  1. 问题一:第一个进程是什么?init进程
  2. 问题二:如何创建进程?execve(...) 

Linux进程树

  1. 整个Linux系统的所有进程构成一个树状结构
  2. 树根由内核自动创建,即:IDLE(PID -> 0)
  3. 系统中的第一个进程是 init / systemd (PID -> 1)
    1. 0号进程创建1号进程,1号进程负责完成内核部分初始化工作 (内核模式)
    2. 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(...)的工作方式

  1. 根据参数路径加载可执行程序 ==> 加载进当前进程
  2. 通过可执行程序信息构建进程数据,并写入当前进程空间
  3. 将程序执行位置重置到入口地址处(即:main())
  4. 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

  1. 从以上log可以看出来,run.out进程PID为583066,它的父进程为 558001,进程558001即为bash进程。

  1. bash 的父进程为:/usr/libexec/gnome-terminal-server

  1. 所以大家应该明白,我们打开1个Linux终端,其实就是启动了1个gnome-terminal-server进程。我们在这个终端上执行./run.out其实就是利用gnome-terminal-server的子进程bash通过execve()将创建的子进程装入./run.out

父进程死了,子进程怎么办?

僵尸进程

如上图所示,父进程Process A创建子进程Process B,当子进程退出时会给父进程发送信号SIGCHLD;

  1. 如果父进程没有调用wait等待子进程结束,退出状态丢失,转换成僵死状态,子进程会变成一个僵尸进程。当父进程调用wait,僵尸子进程的结束状态被提取出来,子进程被删除,并且wait函数立刻返回。

  1. 当父进程调用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)中无限循环,而父进程会立刻退出。孤儿进程:

待确认?

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

repinkply

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值