Linux-进程概念(3)
文章目录
- 补充两个小知识
- 研究父进程与子进程的关系
- 代码创建子进程的方式
- 1. fork 创建子进程
- 2. fork 的返回值
- 三个问题
- 1. 为什么 fork 给父子返回各自不同的返回值?
- 2. 为什么一个函数会返回两次
- 3. 为什么一个变量,即等于0,又大于0?导致 if else 同时成立
补充两个小知识
在进程运行时,可以通过命令:ls -l /proc/进程ID
,查看进程的相关信息。其中需要关注两个,一个是exe
(它指向的是进程对应的可执行文件),另外一个是cwd
(current work dir
,它指向的是当前工作路径)
之前学习C语言时,用fopen
函数打开文件有两种方式,一种是:fopen("a/b/c/d.txt", "w")
,另外一种是:fopen("d.txt", "w")
。那这两种打开方式有什么区别呢?
第一种打开方式,它是以绝对路径的方式找到指定文件,对文件进行各种操作
第二种打开方式,因为进程它会记录下来自己的当前路径,遇到这种打开方式后,就在当前路径下创建一个文件名为d.txt
的文件,对该文件进行各种操作
这个当前路径就是上述cwd
所指向的current work dir
,可以通过代码来验证一下。只要在当前路径下出现了hello.txt
文件,就没问题
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{fopen("hello.txt", "a");while (1){sleep(1);printf("我是一个进程!我的pid: %d\n", getpid());}return 0;
}
之前不是提到,在该进程运行时将可执行文件删除,进程还会继续跑吗。但是此刻去查进程属性,exe
所指向的地方就会飘红,因为可执行文件被删除,系统找不到了
另外再分享一个系统调用:chdir
,可以通过手册(man 2 chdir
)进行查找,它能在当前进程启动时,将进程的当前路径改变为其传递的参数
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{chdir("/home/xiao");fopen("hello.txt", "a");while (1){sleep(1);printf("我是一个进程!我的pid: %d\n", getpid());}return 0;
}
研究父进程与子进程的关系
Linux
系统里所有的进程都是被它的父进程创建的,Linux
系统是一个单亲繁殖的系统,没有母进程,那么子进程由父进程创建
那么我们自己的程序是一个进程,它要有它自己的父进程,当然父进程也有他的一个父进程,一个父进程可能创建多个子进程,所以Linux
当中所有的进程可以看成一棵进程树,从一号进程开始,然后依次有各种子进程,是一棵多叉树,每一个节点都是一个进程
一个进程是通过父进程创建的,父进程怎么创建子进程现在不知道,但我能不能先获取一下我的父进程呢?可以通过系统调用getppid
来获取父进程ID
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{while (1){sleep(1);printf("我是一个进程!我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}return 0;
}
其实上面的父进程bash
就是一个命令行解释器,它本身就是一个进程。而OS
会给每一个登录用户,都分配一个bash
,可以这么理解[xiao@hcss-ecs-28ce dir_PCB]$
,它就是bash
的printf
打印出的字符串,等待着你输入命令,就是bash
的scanf
等待读取你输入的内容
代码创建子进程的方式
1. fork 创建子进程
可以通过系统调用fork
去创建一个子进程,下面那段代码可以尝试着去猜一下运行结果
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{printf("父进程开始运行,pid: %d\n", getpid());fork();printf("进程开始运行,pid: %d\n", getpid());return 0;
}
通过上面结果,能直观看出来的一点是,该程序的进程(父进程)执行了整段代码,两个printf
都进行了打印,而fork()
创建子进程后,子进程估摸着只执行了后一个printf
可以这么去理解,进程=PCB(task struct)
+ 自己的代码和数据!那子进程它压根就没有自己的代码和数据,它是被fork
出来的,目前根本就没有程序加载
所以可以得出一个结论,该子进程和父进程共享一套代码和数据,子进程会执行fork
之后的代码,而子进程的PCB
大部分的内容都是拷贝父进程的
2. fork 的返回值
可以使用man
手册查看fork
的返回值,可以输入:/return value
成功时,父进程中返回子进程的PID(进程标识符),子进程中返回0。失败时,父进程中返回-1,不会创建子进程,并且errno
会被适当地设置
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>int main()
{printf("父进程开始运行,pid: %d\n", getpid());pid_t id = fork();if (id < 0){perror("fork fail");return 1;}else if (id == 0){// child,给子进程留的while (1){sleep(1);printf("我是一个子进程!我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}}else {// parent,给父进程留的while (1){sleep(1);printf("我是一个父进程!我的pid: %d,我的父进程id: %d\n", getpid(), getppid());}}return 0;
}
父进程fork
返回的id
值为子进程的PID
,所以会走else
语句。其次该父进程会去创建一个子进程,子进程会执行fork
后的代码,而对于子进程来说,子进程fork
返回的id
值为0,会去执行else if
语句
这段代码对于父进程和子进程都是共享的,只不过会因为fork
的返回值不同而执行不同的逻辑
三个问题
1. 为什么 fork 给父子返回各自不同的返回值?
实际上在我们的系统里,对于父进程和子进程,他们两个的比例是1:n
,任何一个父进程可以有一个或多个孩子或者零个孩子,任何一个子进程它就只有一个父进程,所以一定要把子进程的PID
返回给父进程,为什么?
因为父进程要通过不同的PID
来区分它不同的子进程,而子进程就不需要获得父进程的PID
。因为可以通过getppid
函数获得父进程的PID
,所以子进程只要表明自己是否成功建立就行了,所以他不需要标识父亲的PID
。而父亲可能因为有多个孩子,所以必须拿到每一个孩子的PID
,未来方便对自己的工作进行管理
2. 为什么一个函数会返回两次
我是这么理解的,父进程执行了fork
函数后,创建了子进程,子进程甚至被调度了。此时父进程的fork
函数内部走到了return id
语句,而子进程被调度后也会执行后面的代码,同样会走到fork
函数内部的return id
语句,那这条语句不就执行了两遍,返回了两次
返回两次的身份不同,一个是父进程,返回的是子进程的PID
。另外一个是子进程,返回的是0(创建子进程成功的情形下
)→返回值不同,最后有阐述
3. 为什么一个变量,即等于0,又大于0?导致 if else 同时成立
这里会牵扯到虚拟地址空间,所以只阐述一半原因。先展示代码和结果,再进行分析 + 说明
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>int gval = 100;int main()
{printf("父进程开始运行,pid: %d\n", getpid());pid_t id = fork();if(id < 0){perror("fork");return 1;}else if(id == 0){printf("我是一个子进程 !, 我的pid: %d, 我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);sleep(5);// childwhile(1){sleep(1);printf("子进程修改变量: %d->%d", gval, gval+10);gval+=10; // 修改printf("我是一个子进程 !, 我的pid: %d, 我的父进程id: %d\n", getpid(), getppid());}}else{//fatherwhile(1){sleep(1);printf("我是一个父进程 !, 我的pid: %d, 我的父进程id: %d, gval: %d\n", getpid(), getppid(), gval);}}printf("进程开始运行,pid: %d\n", getpid());}
其实归根结底要讨论清楚,子进程和父进程的全局变量gval
为啥会显示出两个不同的值
下面先说一个结论,这是理解第三个问题前半部分的一个必要因素。先举个例子,今天我在运行抖音时,抖音直接挂掉了,那会不会影响我刚刚启动的微信?并不会
我想说的是,每一款软件启动的时候都是进程,但你会发现在现实生活中,如果一款进程挂掉了,并不会影响另一个进程。由此就能得出一个结论:进程具有独立性
那进程存在PCB
,可是PCB
独立性怎么体现的?这个父进程的PCB,子进程的PCB,他们的内核数据结构(PCB
),你是你,我是我,所以你父进程挂掉,跟我子进程没关系
而且共享的代码是只读的,没有任何父进程与子进程能修改。那么父进程和子进程在执行代码中,各自执行各自的,两者也是互不影响的
可是数据呢?父进程有一个quit
全局变量,进程要根据这个全局变量来判定父进程是否退出。如果我们子进程看到了父进程的数据,子进程更改父进程的quit
的变量,把quit
由真改成假,那不就影响到父进程了吗?
这样的话,进程的独立性就不能保证了。所以父子在数据层面上默认是共享的,但一旦有任何一个人想尝试去修改对应的数据,也就是说不管是父进程还是子进程,只要有任何一个人尝试把quit
这个变量修改掉,那么操作系统自动就会把这个变量在底层给你拷贝一份,让目标进程去修改这个拷贝变量
比如子进程要写,父进程依旧访问老的,子进程要访问新的,这种技术叫写时拷贝
父进程与子进程哪个先return
,谁就先修改这个id变量,所以底层发生了写时拷贝之后,父与子就拿到了不同的变量