尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【Linux进程篇】Linux学习第十四弹——一个 fork 为什么能返回两次?

【Linux进程篇】Linux学习第十四弹——一个 fork 为什么能返回两次? 个人主页MSTcheng · CSDN代码仓库MSTcheng · Gitee 精选专栏:《C语言》《数据结构》 《算法学习》《C由浅入深》《Linux操作系统》《Mysql数据库》 《白话大模型》座右铭路虽远行则将至事虽难做则必成在上一篇文章中我们学习了什么叫做进程学习了进程的PCB即进程的描述结构体以及粗略的了解了进程的切换调度以及上下文切换(进程切换调度后面会详细介绍)本篇就来学习如何标识一个进程创建进程逐步过度到进程的状态。进程篇【Linux进程篇】Linux学习第十三弹——进程理解【Linux进程篇】Linux学习第十二弹——宏观理解冯诺依曼的体系结构文章目录一、创建进程并查看 PID / PPID动手实操1.1如何标识进程1.2父进程PPID1.3终止进程CtrlC二、父进程与子进程关系2.1 谁是我们命令行程序的父进程—— bash三、父进程如何创建子进程 —— 通过fork 系统调用第一阶段3.1 为什么必须用系统调用3.2 fork 是什么3.3 fork 使用入门现象先行3.4 那为什么要分流3.5 关键fork 的返回值——区分父子进程3.6、关于 fork 的三个深度问题核心难点四、/proc 伪文件系统进程的实时数据库4.1 /proc 目录是什么4.2 进程存活 ↔ 目录存在动态验证4.3 进程属性文件4.4 重点属性CWD当前工作目录重点五、总结一、创建进程并查看PID / PPID动手实操1.1如何标识进程在上一篇文章中已经对于进程有一个比价宏观的认识了进程进程描述结构体代码和数据那我们怎么看到进程呢进程如何获得自己的标识呢通过一个命令top就可以查看当前系统中的进程情况在图中我们发现每一个进程的左边都有一个进程号那么这个进程号是什么呢——这就是进程的标识符PID进程的标识就是从描述该进程的内核数据结构中拿到对应的值而用户不可能直接获取到这份数据结构所以Linux必须提供一个系统调用——获取自己的PID。这就引出了getpid()函数它的功能是获取当前进程的pid———用于标识一个进程。演示//在当前目录下创建一个test.c文件 mstchengubuntu-dev:~/117/code/test1$ ll total40drwxrwxr-x2mstcheng mstcheng4096Oct316:43 ./ drwxrwxr-x9mstcheng mstcheng4096Sep1111:17../ -rwxrwxr-x1mstcheng mstcheng16000Oct316:42 test* -rw-rw-r--1mstcheng mstcheng133Oct316:42 test.c然后在test.c文件中写入一下代码调用getpid()函数#includestdio.h#includeunistd.hintmain(){printf(我是一个进程我的进程PID是: %d\n,getpid());return0;}//运行一下代码发现输出了当前进程的PID为38914 mstchengubuntu-dev:~/117/code/test1$ ./test 我是一个进程我的进程PID是:389141.2父进程PPID给该进程打上死循环我们还可以通过ps命令去查//这个PID与上面那个不同是因为加了死循环重新编译运行之后进程号不一样了 mstchengubuntu-dev:~/117/code/test1$psajx|head-1psajx|grep42469PPIDPID PGID SID TTY TPGID STATUIDTIME COMMAND6493656665666566pts/042469Ss10000:00 /usr/bin/bash656642469424696566pts/042469R10001:51 ./test42550465674656642550pts/346566S10000:00grep--colorauto42469细心的读者可能就会发现在PID这一列的左边有一列PPID这个PPID是什么引出PPID是谁的问题PPID其实是父进程该进程的pid是从父进程ppid下所创建出来的。Linux系统中终端中所创建的新的进程往往是通过父进程bash的方式创建出来的。bash——就是一个命令行解释器负责解析用户命令在前面的文章也有介绍【Linux学习】Linux学习第五弹——外壳Shell的理解 —命令与运行原理反复运行程序PID如 8488、9194、9616…每次都在变但PPID如 7633一直不变。为什么这就要谈到创建子进程了。1.3终止进程CtrlCCtrlC可以终止前台正在运行的进程。mstchengubuntu-dev:~/117/code/test1$ ./test 我是一个进程我的进程PID是:42469对于上面我们加了死循环的进程它是不会主动退出的而且会一直这样运行下去那我们的键盘输入就不会被bash获取怎么终止掉该进程呢CtrlC既可以了mstchengubuntu-dev:~/117/code/test1$ ./test 我是一个进程我的进程PID是:42469^C mstchengubuntu-dev:~/117/code/test1$二、父进程与子进程关系2.1 谁是我们命令行程序的父进程——bashmstchengubuntu-dev:~/117/code/test1$psajx|head-1psajx|grep42469PPIDPID PGID SID TTY TPGID STATUIDTIME COMMAND6493656665666566pts/042469Ss10000:00 /usr/bin/bash656642469424696566pts/042469R10001:51 ./test通过ps ax | grep 42469查询可以看到PID为 42469 的进程的PPID是 6566即bash—— 即命令行解释器。核心结论重要在命令行(终端)启动的任何程序包括你自己写的程序、ls、ps等命令其父进程都是bash。bash本身就是一个进程。bash命令行解释器负责解析你输入的命令然后创建对应的子进程去执行这些命令。 用「王婆开婚介所」理解父进程把bash想象成开婚介所的王婆王婆本人稳坐“婚介所”就是那个父进程。每逢遇到棘手的命令任务王婆并不亲自出马而是招一批实习生子进程替她出去办事——这些实习生正是bash创建的子进程。实习生在外面出了差错也不会波及王婆王婆依然该干嘛干嘛下一单继续“创业”派活。⚠️ 易错点提醒常用的系统调用只有两个getpid()拿自己的PIDgetppid()拿父进程的PID。三、父进程如何创建子进程 —— 通过fork系统调用第一阶段3.1 为什么必须用系统调用创建子进程 系统里多一个进程多一个 task_structPCB。作为普通用户没有资格直接在操作系统内部 new 出一个内核结构体对象更不能自己往里填属性值。所以创建进程必须通过系统调用由操作系统来完成3.2fork是什么fork—— Linux 中创建一个新进程子进程的系统调用。作用调用一次fork就能创建一个子进程。参数什么都没有无参。返回值很特殊重点后面详讲。3.3fork使用入门现象先行在test.c中写入下面等等代码#includestdio.h#includeunistd.hintmain(){printf(我是一个进程, PID%d, PPID%d,getpid(),getppid());// fork 之前打印一次fork();printf(我是一个进程, PID%d, PPID%d,getpid(),getppid());// fork 之后打印一次return0;}mstchengubuntu-dev:~/117/code/test1$ ./test 我是一个进程,PID8006,PPID7669//fork之前第一个肯定是父进程打印的 我是一个进程,PID8006,PPID7669//被重复打印了 我是一个进程,PID8007,PPID8006//这个PPID是父进程的PID 这是子进程打印的神奇现象fork之前只有 1 个执行流父进程。fork之后执行流一分为二。同一句printffork之后的竟然被打印了两次原理现象层面fork调用后会创建一个子进程。父子进程会共享一部分代码继续执行所以fork后面的printf被父子两个进程各执行一次一共打印两次。实测数据父进程PID8006PPID76697669 是当前bash。子进程PID8007PPID8006父进程是PID8006。结论8006 是父进程8007 是它的子进程二者形成父子关系。注意站在bash的角度bash是父test是它的子站在test的角度test是父它新fork出来的进程是子。同一个进程在不同场景下既可以是父也可是子。3.4 那为什么要分流创建子进程的本质目的是完成任务而不是让父子执行同样的代码。父进程创建子进程通常希望父子各做各的事父干父的子干子的并发并行能提高效率。所以fork后通常要分流让父子执行不同代码块。3.5 关键fork的返回值——区分父子进程fork返回值规则官方手册务必牢记成功返回子进程的 PID 给父进程返回 0 给子进程。失败返回-1给父进程且没有子进程被创建。代码模板#includestdio.h#includesys/types.h#includeunistd.hintmain(){printf(我是父进程: %d\n,getpid());sleep(1);pid_tidfork();if(id0){// child 子进程执行的代码while(1){printf(我是子进程: %d, 我的父进程是: %d\n,getpid(),getppid());sleep(1);}}else{// father 父进程跳过子进程的代码往后执行while(1){printf(我是父进程: %d\n,getpid());sleep(1);}}return0;}mstchengubuntu-dev:~/117/code/linux-code$ ./myprocess 我是父进程:8766我是子进程:8782, 我的父进程是:8766我是父进程:8766我是子进程:8782, 我的父进程是:8766含义用 fork 的返回值做if/else判断 →父子分流。一份代码fork 后变成两个进程可以有两个死循环同时运行。如果愿意fork 十次、八次都可以多进程并行不再是问题。3.6、关于fork的三个深度问题核心难点上面那份代码就是fork之后父子各自执行自己的代码从运行结果上看也非常清晰直观从这份代码中引出三个问题1、为什么在代码中子进程要返回的是 0执行id0的条件给父进程返回的是子进程的pid进程的pid是标识一个进程最重要的标识符通过这个标识可以指定一个子进程未来好控制。同时一个父亲可以有多个孩子要给每个孩子进行命名才能更好的将他们管理起来。2、为什么一个fork()函数可以返回两次首先理解返回再来理解两次3、fork()这个过程到底在干什么注意上面的内容涉及到了一些进程虚拟地址空间中断等内容后面的章节会详细介绍。四、/proc伪文件系统进程的实时数据库4.1/proc目录是什么在 Linux 根目录存在一个名为/proc的目录proc process。/proc目录下包含很多用数字命名的目录/文件夹这些数字就是系统当前正在运行的进程的PID。Linux 的一切皆文件操作系统把内存中的所有进程信息以文件形式实时呈现给用户。⭐⭐⭐/proc与task_struct的关系内核内存有task_struct这个 C 结构体变量/proc提供一套文件形式的 API 接口。用户层用普通文件IOopen/read/cat去调用接口接口内部去读取内核变量临时生成文本返回给你没有永久实体文件。4.2 进程存活 ↔ 目录存在动态验证mstchengubuntu-dev:~/117/code/linux-code/process/processTest/Process$ ./processTest 我是进程,pid:9256, ppid:7669我是进程,pid:9256, ppid:7669我是进程,pid:9256, ppid:7669mstchengubuntu-dev:~/117/code$ls-a/proc/|grep92569256//可以直接查到9256这个目录是存在的 因此我们才能ls列出来运行程序PID 9256则/proc/9256目录存在。用CtrlC杀掉进程则该目录消失。再运行一次新PID 9547对应目录又出现。结论就是进程在 → 文件夹在进程不在 → 文件夹被自动关闭/消失。⚠️【注意】/proc里的目录是动态的、按需出现的反映的是内存中实时运行的进程它不是磁盘上固定存在的文件列表。4.3 进程属性文件mstchengubuntu-dev:~/117/code$ls/proc/9686/ arch_status cwd limits numa_maps schedstat task attr environ loginuid oom_adj sessionid timens_offsets autogroup exe map_files oom_score setgroups timers auxv fd maps oom_score_adj smaps timerslack_ns cgroup fdinfo mem pagemap smaps_rollup uid_map clear_refs gid_map mountinfo patch_state stack wchan cmdline io mounts personalitystatcommksm_merging_pages mountstats projid_map statm coredump_filter ksm_stat net root status cpu_resctrl_groups latency ns sched syscall进程 PID 对应目录如/proc/9256里保存着该进程的所有属性。比如磁盘映像exe指向该进程对应的可执行二进制文件路径。实际ps aux查到的PID、PPID、关联文件、当前状态、程序名等都是从这些属性文件里筛选出来的结果。4.4 重点属性CWD当前工作目录重点问题引入在 C 语言里用fopen(log.txt, w)新建文件时如果不带路径文件总会建在当前路径下。那么当前路径到底是什么先见一见cwdmstchengubuntu-dev:~/117/code/linux-code/process$ls/proc/34542-ltotal0lrwxrwxrwx1mstcheng mstcheng0Oct712:28 cwd -/home/mstcheng/117/code/linux-code/process通过查询进程的pid属性发现所创建的进程是在当前路劲下创建的怎么标识当前路径其实是进程中的cwd标识了当前路径即当前的工作路径每一个进程启动的时候都会默认有自己的cwd。如果想要证明当前路径可以通过chdir(change working directory来更改指定的路径那我们就使用chdir修改一下工作路径写一个demo代码#includestdio.h#includeunistd.hintmain(){intcnt12;while(cnt--){printf(hello world!\n);sleep(2);}chdir(/home/ljc/117/code);while(1){printf已经改变了工作路径了\nsleep(2);}return0;}运行之后 小结cwd与chdircwd是每个进程自带的“当前工作目录”Linux 以软链接形式暴露为/proc/PID/cwd。写fopen(log.txt, w)这类不带路径的文件操作系统都相对当前进程的cwd创建文件。chdir()用于修改当前进程的cwddemo 中执行chdir(/home/ljc/117/code)后进程后续的相对路径访问都基于新目录。五、总结本文围绕Linux进程的创建与查看展开1、进程以PID为唯一标识getpid()获取自己的PID。2、getppid()获取父进程的PPID终端中启动的程序父进程基本都是命令行解释器bash由它创建子进程执行。3、创建子进程必须通过fork系统调用它让执行流一分为二并用返回值区分父子父进程拿到子进程PID子进程拿到0失败返回-1之后借if/else让父子各干各的实现并发。4、Linux 还通过/proc伪文件系统把内核进程信息实时暴露成文件:/proc/PID目录对应当前进程进程在目录在、进程退出目录消失其中cwd标识进程当前工作目录相对路径操作都基于它可用chdir()修改。
返回列表