22. linux系统基础

文章探讨了递归遍历文件系统统计文件数量的方法,包括目录处理与递归逻辑,以及文件描述符复制与管理。进一步介绍了管道通信机制,包括环形队列的高效性与匿名映射的使用场景。同时,讲解了并发与并行处理概念,以及进程控制块(PCB)的作用。此外,深入讨论了mmap函数在进程间共享内存的应用,特别是非阻塞IO处理与FIFO管道的创建与使用。
摘要由CSDN通过智能技术生成

递归遍历指定文件下所有的文件,而且你还可以统计一下普通文件的总个数,既然能统计普通文件,能统计其他文件吗?比如目录文件, 这个是main函数里面我们调用了 ,这个checkdird这个函数,需要传递一个路径,

 文件打开的目录,把路径 传进去,传进来以后,我这个路径,调用opendir打开,打开之后返回一个指向目录的一个指针,DIR *pDir  

 

 接下来在一个循环里面,循环读取目录项,如果这个不过滤会怎么样?每一个目录下都有它自己,会进入死循环,这个必须过滤掉,接下来进行判断,看一下这个文件是什么类型,如果说是这种目录DT_DIR 应该是递归, 有的人是不是没把这个加上49?如果没加上,他肯定是最多打开一层或者失败,
打开失败那个perror 就是说 没有这个文件或目录,然后拼接好以后,继续让他调自己,这个自己调用自己,在这里我们是直接或间接调用自己呀?在这里是直接调自己, 50这个是函数里面调自己,这个是直接调自己,那么它返回一个n值,n+= 是什么意思?是累加,一直累加,递归是一层一层的压栈,最后一层一层的出栈,递归有些场合不用递归不行,递归又不太提倡用,为什么呢?因为说如果压栈太深,有可能会照成栈溢出,但一般情况下,系统上你的目录 层能有多深呢? 你大概10层 你还能20层100层吗?

 如果是普通文件 n++就可以了,n++ 就是在统计普通文件的个数呀?

 函数,由这个返回,返回的是普通文件的总个数,记得要关闭这个目录,否则就会照成,我打开很多目录,是不是最后只能关闭一个呀?你这个函数checkdir他肯定打开的是带路径的,这个是不是可以用相对路径呢?你要知道这个相对路径是相对谁的呀?是当前目录,因为你这个目录是不是要切换呀?但是这个切换是不是只能往下切换呀?不能往上切换,演示一下,统计一下当前目录,看看有多少文件,25个吧?

 

 如果在统计目录的话,是不是在目录项在做个m++ 如果让你统计目录和文件一块来统计,这个返回值怎么说?这个参数你可以返回结构体可以吗?

因为这个return是不是 如果返回一个普通数据类型的话,是不是只能返回一个值呀?如果你想返回多个值,可以返回一个结构体,返回一个结构体指针,或者返回一个结构体,值传递是不是也可以呀?
但是你不能返回局部变量的引用,或者是指针吧,

两个文件符指向的相同的文件,无论你对他如何操作,读或写,都会引起文件指针的变化,那么这一个文件只有一个读写位置指针,你使用哪个文件描述符,都会引起它的变化,

dup2比dup要复杂一些,

 我们打开两个文件,newfd)指向argv[1] 也就是指向oldfd指向的文件,
这个里面有一个隐含的操作,如果说,我们在进行文件描述符复制的时候,如果这个newfd已经打开一个文件了,那么你做了28这么一个操作,很显然,newfd会把这个文件给关掉,然后再做文件描述符的复制,如果说这个newfd没有打开文件,没有关系,直接复制,当你进行了文件描述符的复制以后呢,那么内核当中会给你做隐性计数,这点有点类似硬连接,如果我仅仅是把这个newfd close掉,那么你这个文件有没有会真正的关闭掉?没有,那么你close一个文件描述符,只是说使这个文件描述符隐性计数减一,如果是0的时候这个文件是真正的关闭了,要仔细联想一下硬连接,跟那个原理是一模一样的,

 这个文件描述符STDOUT_FILENON,是不是它已经把这个把标准输出给打开了吧?因为我们的进程执行起来以后默认有3个打开的文件描述,分别是 标准输入 标准输出,和标准错误输出,这三个文件是打开的,跟我们刚才讲那个是不是类似?那个是我们手动打开,这个是进程刚刚已启动就打开了STDOUT_FILENON,,

 这个例子就是一秒钟 我可以分为一千个时间片,每一个进程轮流使用这个时间片,比如说cpu是2.0ghz 这个g代表10的29次方,那么它的时间可能是1,1秒钟可以执行2.0g个任务 速度非常快, 况且 是多核的,双核 4核 
比如100个人排队买咖啡,但是一杯咖啡1分钟才能够生产出来 店家怎么让所有人在1分钟之内全部喝上 1瓶 把1瓶分为100小份,一人喝一口,这样的话每个人轮流喝一口,这样话,一分钟可以让所有人全部都喝上咖啡  好多权限来回切换 现在没有单核cpu 一般1颗cpu至少两核

 横轴表示时间,纵轴表示进程,在这个小的时间片内,A进程在执行,一个时间片内只有一个时间在执行,
一个时间段内有多个进程在执行,每一个时间片内只能有一个进程在执行,充分理解并发的概念,cpu时间片,cpu不可能只执行一个程序,如果说只执行一个程序,其他程序还能走吗?

 

并行,并行性指两个或两个以上的程序在同一时刻发生(需要有多颗)

如果你只有1个cpu 你能够在一个时间片内执行两个程序吗?要想做到并行必须有两个cpu 或者说一个cpu 两个核,

 多个人喝咖啡,这里面我们把这个人比作想要执行的程序,或者说你说静态也行,反正还没有跑起来,跑起来之后才叫进程, 把这个咖啡比作cpu,或者核,这里咱们统一说cpu就可以了,这样 我有两队人马,每一队人马 每一队里面都有一个咖啡机,这样的话 我一个时间片内,是不是就可以完成 让两个人来喝,原来的话 一分钟只能磨一杯 现在的话,一分钟 可以磨两杯 这样的话 我可以让两个人来喝了,

比如在一个时间片内,一个时间片是一个小的片段, 在这个时间片内 有几个进程在执行呀?有3个,很显然 你一个cpu做不到这一点吧? 至少有3个cpu 才能够实现并行操作 要想实现并行 至少有两个以上的cpu 

 2.3PCB进程控制块

进程控制块在内核区里面,内核区里面有一块叫 进程管理 在进程管理里面有一个pcb ,pcb当中有什么呢?有一个文件描述符表,
进程控制块的本质是一个结构体,这个结构体,大概有三四百个成员,

找的时候这个版本号就不要再加了4.4.0-96,标红的是重点知道的,叫进程id  👆

 进程id pid 类似于人的身份证,进程 在操作系统看来的话,它找到这个id 相当于找到这个进程了吧?在C语言中用 pid _t类型表示,这一种是type_defan 对这个类型进行重定义 相当于起这个别名,其实就是一个非负整数,进程的状态,有就绪、运行、挂起、停止等状态。 

当前目录, 你这个进程在哪执行,你的当前目录就是谁 跟运行环境有关系,getcwdI-pwd获取当前目录 chdir
可以改变当前的根目录,这个根目录不是死的,可以改的

umask 掩码:创建文件需要计算文件的权限, 这个权限怎么计算呢? 你指定上一个mod &umask取反
需要把文件某些权力给他掩盖掉,不让他显示出来,

文件描述符表,这里面存放的是打开的文件,


和信号的相关信息 都存在内核当中


用户id 和组id 一个进程启动起来以后,它一定属于某一个用户,

 

sleep 100 进程属于哪一个用户?uid =1000 有组id 一个进程 肯定是某一个用户,而这个用户肯定是属于某一个组,所以说这个进程 属于某一个用户 也属于某一个组,


会话 (Session)和进程组
pid 是它自己的id,ppid 它的父进程,pgid 是组id,
sid 是会话id,第三列3437 组id, 第四列2817

3437是哪个进程?是他自己,会话id 2817是-bash 是shard 父进程 创建出几个子进程以后,这个父进程就处在同一组,
如果有好几个子进程,就看每一个子进程的第三列值是不是同一个,如果是同一个,我们就认为它是同一个组,

进程可以使用的资源上限 (Resource Limit)
ulimit -a
这个进程可以无限的使用资源吗?如果你这么干,万一有病毒的话,它把你系统全部占用的话,其他进程一个也跑不起来,联想一下 在讲文件描述符的时候,文件描述符,有文件大小,也就是说你这个进程 最多可以打开1024个文件,那再打开的话不能打开了,内核就不再给你分配了,所以正常情况下,我们使用的时候,这个文件不用了要close  包括这个dup dup2也会产生文件描述符,这些东西不用了也要记得close

这个可以打印出来信息,比如说core file size 无限制max user processes 最大的用户进程数6294

file size 文件大小,在linux操作系统中,如果你这个文件太大,你用文件打开一下就知道了,你把他打开,即便是打开了 你移动不了,看起来太费劲,所以说,我们这个文件不能太大, 特别是后面讲日志文件的时候,日志是记录了系统运行的时候的一些信息,如果说这个文件太大, 那么你在查看日志的时候,你会看起来很费劲,
如果说你这个文件是100m,你用这个ue去打开 打开的话 你滚动不了,
比如你电脑配置比较低的时候,你看 高清电影的时候,费劲 卡,

一开始这个进程需要启动,启动起来以后,一开始他立刻能执行吗?他是不是得等cpu呀?先获得cpu权限,他一开始处于就绪状态,如果说得到cpu时间片他运行,如果说运行时间片,在这个是运行结束之后,进程可以退出,运行期间,可以用kill给杀死,或者他自己终止也可以, 挂起

看看这几个运行状态之间是如何进行切换的?

cpu时间片用完进入就绪
如何运行到终止,自己给他杀死了kill 人为原因 外部原因,另外进程正常结束了,运行完了 执行以下结束了,终止态
就绪态可以到终止态 
比如说你刚跑起来,然后外边立刻杀死了,
挂起态,如果在就绪态中,如果说你代码里面,有一个sleep 或者暂停,你的程序可以暂停,这个时候比如说sleep当中,他就可以回到挂起态 

 比如说收到收到SIGSTOP信号或者sleep也可以到挂起态,如果说这个sleep 一秒钟过后,你这个挂起态能够直接到运行态吗?他必须先到就绪态,
挂起态 他是不能够直接到运行态的,是不是回到就绪态,
运行态能不能到挂起态呢?可以,最直接的例子是程序正在运行着呢,外部发个信号,让他进入休眠状态, 他是可以的,
那么从运行到挂起态以后呢,那么如果说这个时候,由于外部触发,他又想执行了,他这个时候,他自己能够直接回到运行态吗?不能的,这个是单项的,那么也就是说从这个图当中 我们就可以看到,如果说想到运行态的话,必须先回到就绪状态
挂起态能不能直接终止?可以,很显然比如说你正在sleep的 时候直接给他杀死,

如果说想回到运行态,是不是首先要回到就绪态 严格的说 有一个初始态,那是一个瞬间的一个状态 很短暂,初始态也得先到就绪态

如果你的进程处在就绪态 ,你的进程是有资格去执行的,只是目前他没有权限,因为你没有cpu 没有cpu时间片,那么当你处在挂起态的进程,既没有cpu时间片,也没有cpu的执行资格,有了资格之后才能 获取cpu执行权限,

你可以把这个有没有执行时间片,看作它有没有执行权,你有cpu执行时间片 才有执行权

其实这个几个状态,是最常见的状态,严格的说,这个cpu的进程状态切换是非常的复杂,但是对我们来说理解到这个状态就够了,
你比如说我举个例子,那么这个进程之间调度的时候,是不是有这个优先级呀?这个上面没有显示出来,但是这个东西是确实存在的,这个往往系统进程的优先级很高,你关机相当于系统进程很多要比其他进程 要让其他进程全死掉,其他进程先退出之后,你这个机子就关掉了,你不能说有一个进程活着 退出不了 你这个系统就关不了机 

 

 

 

 这个进程没死,我怎么让他进入暂停态,没死的话是不是就可以用ps ajx 看到它的pid, 我用

 

 已停止,人家说已停止,没有说已终止,终止是不是彻底死掉了?还活着呢,当然看不到它的状态, ps -ajx

 

 它现在是T状态,如果执行起来,

 

 S 是sleep 的意思,如果它死掉了,还能用ps看到他吗?看不到了,-9是彻底杀死,无论你的进程是什么状态,写程序的话,关心的是哪个状态?运行态,打印日志,打printf 是不是看一下在运行过程当中这个值,是否正确,

凡是进行多进程服务开发的 都会用到fork 在linux环境下,进行多进程服务开发的,都会用到fork,一个进程能不能解决大量的业务处理呢?举个例子京东  电商,他肯定不是一个进程在做事吧?它要么多进程 要么多进程加多线程,才能处理高并发数据,一个进程是忙不过来的, 在这个linux操作系统下也有大量的这一样的服务,都用的是多进程,这linux操作系统,是不是有多个进程组成的?一个linux操作系统,是由多个进程组成的,每一个进程完成各自不同的功能,或者完成一组任务 或者类似任务,这个时候都会涉及到 多服务的开发,那么涉及到多服务的开发,就涉及到如何去创建子进程,将一个函数叫fork 在linux操作系统下面fork函数就是用来创建子进程的,

fork函数先给你讲一讲原理原理比较复杂,讲到过虚拟地址空间,一共分为几大块?
对于内核区我们见的最多的,也是我们关注的最多的是pcb pcb有 进程的pid 文件描述表, 这个是进程起来以后,操作系统给我们分配的资源,如果在你的进程里面调用了fork函数,会把这个做一个完整的复制,复制完以后 调用fork函数,创建进程,严格来说是创建子进程,那么创建 调用fork函数的这个进程,我们称之为父进程,对于父进程创建 出来的这个是子进程,子进程的空间是由父进程复制出来的,既然是复制出来的,那么用户区的数据会一样吗?复制的完全一样,内核区的数据会完全一样吗?你是复制过来的,但是内核给你做了一定的修改,起码来说有一个地方改了,每一个进程都有upid,父子进程都会有不同的pid,肯定这个pid不一样,其他的一样不一样,我们不用关心,我们执行一个进程, 包括操作系统,是不是找我的这个进程看的就是pid,你不能说你这个用父进程, 创建出一个子进程之后,子进程和父进程用同一个pid 那么父进程这个操作系统,在找一个进程,它知道找哪个吗?每一个进程都有一个父进程,
父进程创建出来子进程以后,接下来你说我这个里面有几个进程了?两个  一个父进程  一个子进程,

 现在来说,你说我这个父进程先执行,还是子进程先执行?
怎么标识哪个是父进程 哪个是子进程?
用fokr函数的返回值来标识它是不是父进程 是不是子进程,如果你没有加标识,那么后面的代码都是一样的,
pid = fork();是我调用fork函数,调用的子进程,这个横线以下,是父子进程都有的代码吧? 咱们子进程也有,因为代码是完全一样的,这个如果说pid ==0 是子进程,如果说pid>0 是父进程,为什么 我父进程返回的是大于0的数?fork函数返回值有两个吧?你看着是一个 实际上是两个,为什么是两个?实际上是各自返回一个值,父进程一个值,子进程返回一个值,那么父进程返回的是一个大于零的数,其实就是子进程的pid 那么子进程返回=0,在这个逻辑里面 ,这下面的代码,虽然说父子进程都有,但是在执行的时候他分开了,这个=0的逻辑也就是子进程执行的逻辑,大于零是父进程执行的逻辑,他俩是相互独立的,
一个子进程会有几个父进程?只有一个,一个父进程有几个子进程?大于等于1,这就是为什么说为什么在fork函数里面返回一个大于零的数,如果说它返回的是等于零的数,那么它知道这个子进程是谁吗?他能记录一下它的子进程pid是谁吗?记录不下来,那你子进程 它能知道它的父亲是谁吗?提供的有个函数 他能获得父进程,getpid 但是linux操作系统没有给我们提供一个接口来获取它的子进程pid,所以说你只能在创建中呢,把它保存下来,假如说我循环创建10个子进程,我想把这10个子进程pid我全记录下来, 创建一个 我记录到数组里面一个,我循环创建10个子进程以后呢,我的数组里面就保存了所有的进程pid,反过来,我子进程 我用记录父进程的pid吗?不用记,因为他有函数调用它的getpid 就可以获得它的父进程的pid

有同学可能认为:这个fork函数返回值,是父进程自己返回两个值,一个大于零 一个=0,那你原来学过返回整型值,能返回两个吗?返回整型值 只能返回一个,如果返回两个 一定是有其他的操作在里面,并不是一个进程返回两个值,而是由父子进程各自返回一个值,那么各自返回一个值 一共加到一块是两个值,如果严格来说你说fork返回两个值是不对的,严格来说不对的,因为它的返回值就是一个pit_t的一个变量, 是一个无符号整型,那么一个无符号整型, 怎么表示两个值?不能表示,肯定是父进程返回一个值,子进程返回一个值, 银行里面有一个人,他让别的同事改代码  用fork它说不安全,因为它不懂原理,如果说改fork值太麻烦了,一天两天 改不完,后来我给他说用fork完全可以做到,它就想着用两个进程来做嘛,然后它没改 我查了一下资料,没改完全好使,没影响,没有让他改,不懂原理说不清楚父子进程的执行逻辑和执行代码怎么分?根据fork函数的子进程,如果你不写 pid==0 pid>0,下面的父子逻辑都执行, 子进程会执行child上面的代码吗?不执行,因为那个时候 还没有子进程,但是子进程也有这个代码 因为用户数据完全一样,代码在用户区 完全一样,

 演示一下fork函数怎么去使用,

 先加头文件
创建一个子进程, //pid_t fork(void); 看着很简单,它给你提供的接口简单,实际上内部实现的东西很复杂,首先我们要定义一个pid_t类型的变量,有可能创建失败,什么时候可能创建失败?如果你这个系统里面进程数太多 有可能创建失败,

 getpid()当前进程的pid 谁调用它就返回谁,如果说 pid==0 是子进程,

 

 父进程是3540  子进程是3541 先创建的进程pid小,后面越来越大,
如果在frok之前打印一句话 fork之后打印一句话 

 10会打印一次,29会打印两次,这个父子进程都会执行,

if erse逻辑之外的语句 是不是父子进程都有啊?都会执行,通过这个代码 我们看到了,刚才讲的这个理论,第一点 首先 返回值,父进程返回的是>0的值,实际上是子进程的pid  那么子进程返回的是0,你说父进程子进程谁先执行?谁后执行?这个是我们控制不了,为什么加sleep(1)?
是让父进程后退出,1秒钟的时候子进程也不见的完全能够优先执行,只是说概率更高一些,如果说我把这个sleep(1)注掉呢?有没有可能父进程先执行完 子进程后执行完呢?完全有可能,但是如果父进程先执行完 退出了,子进程后退出,那么子进程的pid会不会发生变化?

21我把父进程id也打印出来,

 把每一个进程的父进程都打印出来,如果父进程先死,这一个后进行,然后你看一下26 fpid会有什么不同,

 父进程的fpid是2815 子进程的fpid 是3570  2815是当前的shell 

 把22先注掉,父进程有可能先退出,子进程就后退出,这个时候子进程的fpid就变了,如果父进程后退出,后面就不变了,现在父进程已经死掉了,fpid==[1] 是init进程,这个进程是很多进程的父进程,很多进程就是被这个1号进程拉起来的,你可以想一下,我们这个fork这个进程是不是被当前shell拉起来的,所以我这个fork进程的父进程是shell,现在我们看到的第一种结果 第一次子进程先执行,父进程后退出,所以说这个子进程的fpid=3853就是./fork
但是第二种情况不一样 接下来进程变了,前后结果不一样,正好体现了父子进程谁先执行谁后执行 我们不能保证,谁先抢到cpu谁先执行,第二种情况是父进程先死掉了,子进程后死掉了 ,所以说子进程的父进程变成1号进程,如果一个孩子他的父母没了,这个孩子就变成孤儿了,在我们这也一样,这时候子进程就被1号进程领养了,1号进程init进程pid就是1,

 如果出现第二种情况 给人的直观感觉,有可能在阻塞吧?这个时候你敲一下回车,如果你一敲回车他立刻就退出了,实际上他没有阻塞,为什么会出现这种情况呢?你看一下 我们这个fork函数,fork.c这个代码里面,一开始 父进程先执行的,fork之前都是父进程,执行了fork之后才有了子进程,这个图里面,我这个父进程创建出子进程来,他是不是在内核区里面把这个pcb也复制了,pcb有文件描述表,文件描述表存放的是什么?打开文件,那标准输出是不是文件呐,所以说父子进程共享标准输出

打印出来的是不是在同一个终端上?如果说我父进程结束了,子进程后结束,你说这个,父进程结束之后你这个终端有没有真正的关闭,没有,因为子进程还在着呢,但是当前shell 它知道你这个fork有一个子进程吗?不知道,实际上这个父进程先退出,那么这个父进程就回到了shell 父进程已经回到shell 子进程后执行完的,子进程还没有回到shell 在这里面执行的每一个进程,你在这个shell执行的每一个进程,他退出之后都会回到当前的shell

比如我当前 执行sleep 1 一回车他就回到当前shell 这个是不是也这样的,是不是也回到当前这种情况?这个时候应该是父进程先退出,子进程后退出 才会有这种情况,这个时候,实际上就已经结束了,你一按回车的时候是不是就立刻退出了,关于这个代码
大家了解 一个是返回值,第二个 父子进程的执行逻辑,用的是pid 第三个父子进程 谁先执行,有时候是父进程 有时候是子进程,我这个父进程 创建出来子进程以后,我这个父进程和子进程并行执行,只不过这两个进程是有关系,是父子关系,实际上我们在写代码的时候,这两个模型是最简单的模型,一般情况下我们会在子进程执行一个函数,让这个函数拉起一个可执行程序,一般这么来干,

 

 写个例子,让父进程循环创建三个子进程,

 

 

 要循环创建 在一个for循环里面,并且打印出这个进程是第几个,大家看一下,如果你这样创建的时候,会创建几个呀?9个?怎么算出来的呢?打印几个child 就创建几个子进程,创建一个就打印一个child: pid 

 7个child: pid 连父进程本身是不是一共有8个进程?
它的父进程都是一样的吗?有的是3644 有的变成1了,很显然,这个创建出来的所有的子进程,它的父进程不见得都是一个吧?

 加个sleep 都不退出,现在是7个进程,你光看子进程 它自己是
3676 它的父进程是3673是第一个进程,

16  17  18 这三个都是子进程,这三个都是同一个父进程,

第二个父进程是3675  是18 它是一个子进程,这个子进程 22是由一个子进程创建出来的, 
这个23  3674是17 这个子进程也是子进程创建出来的子进程,

24 3674
25这个父进程是3678是24

这个里面我看到一个 现象,子进程,也在创建子进程,这一种结果,通过我们分析的这种现象,我们是不是应该这样呢?在我们代码里面,如果我们这么做,刚才我们分析了,创建出来了一共有7个子进程,而且这7个子进程,它的父亲不是同一个,有的子进程 也会创建子进程,

画图:

一开始有一个父进程,
当i=0 父进程创建一个子进程,n=1
这个父进程创建出来一个子进程以后呢,这个子进程是不是有和父进程相同的代码段,此时是不是继续循环呀?

也就是说父进程在fork之后,子进程在这个逻辑 printf("child: pid打印完以后,它是不是也回去循环,i=1,当i=1的时候它的父进程是不是会创建出第二个进程来,子进程也会创建子进程 i=1  孙子进程,当i=1时创建几个子进程?两个,一个孙子是一个子进程,他们是叔侄关系,一共有两个子进程

当i=2的时候,这个父进程它自己还是会创建,也是子进程,这个进程它也会创建,左边i=2也会创建子进程,是孙子进程,中间也会创建进程 ,也是孙子进程,


孙子进程也会创建一个子进程,第4代重孙 

i=2一共创建4个进程,
最后一共创建多少个子进程? 7个加上父进程自己一共8个
 

一个公式:2的i次方

 如果创建3个子进程 加上父进程一共4个怎么办?创建所有的子进程都是兄弟关系,你要做的就是让子进程不再创建子进程 只要是子进程 就break;

 如果我父进程创建子进程 这个子进程还循环吗?这个break之后是不是就跳出来了,就不再fork了,这样的话父进程就只创建3个子进程 这三个都是兄弟关系,

第二个要求,让你判断一下每一个子进程是第几个?这个创建子进程是不是有先后顺序呀?怎么把第几个子进程打出来?假如说i=0 创建出来一个子进程,我在这里break break这个子进程i是几? 是0;

再创建一个i=1了,这个父进程是不是又创建一个子进程了?又创建一个子进程 当是子进程的时候break,那么这个i是1

i=2 也是这样
i=3 不满足条件 直接往下走了,只能是父进程,这个逻辑完全可以使用i进行区分呐

加一个判断,每一个子进程都有这个i的值,每一个子进程的i值 他们之间相互影响吗?他们虚拟进程空间都是复制的,互不影响, 
好几个子进程都有i的值,但是这个i的值都是相互独立的,你子进程把这个i的值改了,影不影响其他的子进程呀?不影响,

 

 是不是这样的,其实有一个更好的办法,打出pid
你观察一下 按照刚才的推理,这个i的值打印出来 0
1  2  这三个子进程,是不是和咱们child 打开的是一样的 27? 这个你是不是就可以把每一个子进程的父进程打印出来,这三个子进程的执行顺序不一定说 先创建先执行吧,可以区分了 [0]是第一个子进程 
 [1]是第二个子进程   [2]是第三个子进程 
 
 
 这三个子进程 它的父进程都是3729 就是一开始的那个进程,

测试父子进程是否能够共享全局变量 不能 各自是各自的

如果能后面还有进程间通讯的说法吗?父子进程 属不属于进程间?如果能还有第六天的内容吗?

 父进程和子进程变量是一样,但是不是一个东西,因为进程虚拟空间不一样,是两个虚拟进程空间,不一样的,vi fork1.c
循环创建   在main函数外面定义一个全局变量,在父进程里面把这个值改一下,然后看一下,子进程这里面的值变了没有,如果变了那就意味着它是共享的,如果没变意味着没有共享,++他自己变成一百了,自己验证有没有弊端?有没有可能父进程还没有执行,它就开始执行了,这样的话,它肯定是99,这样我们应该怎么办?应该让子线程先执行 sleep(1) 这样可以让父进程更大限度的先执行,

 

 父进程里面,他自己肯定是100,通过这个例子我们就明白了,你说父子进程,能不能共享一个全局变量啊?不能,

 如果只是读,不加加,这个父子进程里面的值是一样的, 如果说打印一下变量的地址呢?地址一样,为什么地址一样呢?因为这个是虚拟的,而且这个是完全复制过来的,我们打地址打印的是物理内存地址还是虚拟内存地址?物理内存地址是不能够被直接访问的,虚拟地址空间里面的变量也好代码也好,是不是最终要加载到物理内存呀?这个加载的过程是系统做的,有一个叫mmu的 一个模块 叫内存管理单元,他来做的,他会把虚拟地址空间变量 给你映射到物理内存当中去,试想一下,这个物理内存是不是被所有的进程共享,都会使用这块内存,它用完后,他退出来,然后另外一个进程又进去了,你用你进来,你不用你走,

父子进程能不能共享原理 说一下:父进程fork一个子进程来,父子进程有两个进程 如果说你这个进程想真正执行的话,必须要加载到物理内存里,物理内存, 用户区有一个变量叫g_var 这两个变量在用户区,仅仅对变量做读操作,并没有做任何的修改,他在屋里内存当中是这么一种情况,g_var 变量要映射物理内存,如果仅仅是读操作,他们俩实际上在内存当中是一块,

如果说父进程或子进程,不管是哪个进程,想对这块内存做修改操作,他会做一块空间的复制出来,把这个99做修改操作 

这个时候父进程的值变成100了,这个时候如果仅仅是保留在物理内存行吗?他要映射回来,这个时候父进程的值由99变成100了,父进程的值变了, 子进程的值变了吗?子进程没有修改 它用的还是这一份,如果子进程也想对这个变量进行修改,它也拷贝一份出来,这个98也要映射回来,

写时复制读时修改 也就是说这两个父子进程,仅仅是对这个变量做读操作,没有做任何修改,这个时候在物理内存当中,它是怎么样?共享的,如果说一旦有一个进程对这个变量进行修改了,那这个时候,在物理内存当中他会,会复制一个副本来,那么在这个副本上去修改,修改完以后,把这个值映射回去,是不是就变了,
为什么这个linux内核会这么干?可以节省资源,一个变量也算节省资源?你要来个结构体呢?如果有一个很大的结构体,那么这种做法就省内存了,物理内存,就一个内存条,内存的资源是有限的,不可能一个进程一直占用内存,有没有一直占用内存的进程?有,那是系统进程,有些是常驻内存的,父子进程 他是不能够用全局变量进行通讯的,相应的手段,如管道,管道左边是一个进程,管道右边也是一个进程,这两边怎么通讯呢?用的就是管道,那ps -ef的输出,是不是写到管道里面了?那么这个grep bash 是不是从管道里面读呀?就完成了这么一个通讯,使用全局变量是不行的,

 从虚拟进程空间映射到物理内存,还有从物理内存映射回去,这整个这么一个过程 不是你自己做的,是谁做的?是系统做的,有一个叫mmu的内存管理单元,我的程序要执行的话,是不是要加载到内存 用完之后是不是要退出?实际上 你的内存越大,你的系统越快, 这个虚拟机比较耗内存,你开两个,系基本上就卡死,手机也是一样的,如果你的cpu快,但是你的内存太小,也不行,

 

 ps命令是查看进程相关信息的,比如说最重要的pid 这个进程属于哪个组 哪个用户 属于哪个会话 等等,经常用到的有aux ajx 
ps -ef 也行,这个比较简单,如果你想仅仅是想找这个pid的话,用这个就行了ps -ef grep bash,
-a 当前系统所有用户的进程
-u:查看进程所有者及其他一些信息
-X:显示没有控制终端的进程 --
ps -aux 前面带?的是没有控制终端,

 没有控制终端的话,就不能在屏幕上输出,当然它也不能读取用户的输入,这些是带控制终端的,

👆,

j:列出与作业控制相关的信息  这是一组进程,

我们阅读的是aux ajx 我给大家来演示一下什么叫着aux ajx

-不写也行,每一列代表什么意思?


USER 进程属于哪一个用户?
PID  进程的pid  init进程  1号进程,

%CPU 使用率 , %MEM 内存,
VSZ 内存
RSS  虚拟的,

TTY 控制端
STAT 状态
START 这个进程是什么时候启动的

CRMMAND 执行的命令,或者是可执行程序,

一般情况下我们关心pid 的值,要杀死某一个进程 就kill - 2039 就可以了 2039是pid

ps -ajx
它能够看的信息多一些了,

PPID  父进程 每一个进程都有父进程  1号的父进程是0号进程,0号进程没有父进程了,这相当于最开始的进程,
PID 是它自己的PID


PGID 是组id 每一个进程都属于某一个组,也属于某一个会话, 一栋大楼里面有很多单元,你可以把一栋大楼里面,每一个家庭,可以看成 一组,一栋大楼有很多户,有一户一户的人家,你可以把一个人看成一个进程,一家子看成一组,这一组进程肯定是有关系的,你可以把一栋楼看成一会话,你说这个组 与会话是什么关系?一个会话里面包含多个组,一个组里面包含多个进程,一个进程也可以单独的一个组,也可以单独的一个用户,你比如说我就起一个进程,它就是一个组,
SID 是会话的意思,

1号进程,pid PGID SID 是同一个值,这一个进程属于同一个用户,也属于同一个组,
比如说sleep 100  是我启动的进程, 这个sleep  2039是父进程, 2117 是它自己的pid ,2117是它的组id,这个组里面是不是只有这一个进程? 会话是2039 是他的父进程,是bash

因为你这个sleep 100 是由这个bash拉起来的,

TTY是终端
TPGID一般不用
一般关心的是PPID  PID  SID 等
UID 用户id
TIME 启动时间,
COMMAND 启动命令

如果说你想杀死一个进程,怎么杀死?首先是要找到他的pid 然后 kill -9 一定能使这个进程死掉,为什么会提供这样一个手段,一定让一个进程死掉呢?

如果 你没有这样一个机制的话,万一有一种病毒 来个死循环,是不是退出不了了?是不是没有办法你只能重启,万一他也重启呢?
如果说你这个进程杀不死,你就切换root用户,让root用户去杀死他,那肯定能杀死他,root 用户拥有最高权限,如果root干不了 那就完蛋了,你只能够把电源一关,然后再来一次 ,遇到这种情况,你电脑卡着不动了,你急得要命,拔电源 再插,

 ps命令详解.docx

kill是一个命令,还是一个函数,在这个linux操作系统下,很多linux命令都有对应的系统函数,举个最直观的例子 read 既是命令又是函数 要查看man kill命令可以这么写, 如果说要查这个函数,需要加个 man 2 kill,

这个是个函数,这个函数也有pid 和哪个信号,是不是标识跟哪个进程发送信号?或者是杀死哪个进程,
kill -l 可以查看系统下有哪些信号 我们常用的9号信号是SIGKILL

上午用过SIGSTOP  SIGTERM 是15号,列出系统下能够,支持的信号,经常用到的,咱们在讲信号的时候,我会给大家列出来,

 

族 家族 不止一个 ,一给你个支持6个函数,man execl 这6个里面也不是让每一个都掌握,其实都是类似的,你掌握一到两个就够了, 一般情况下其他的用不着,

函数的作用,上一次我们讲fork 可以创建子进程,

  这个子进程里面 写了这么一个操作,26行,往往这个子进程不是这么干的,他可以调用execl函数拉起一个可执行程序,或者是执行一个系统命令,举个例子,比如说在一个子进程里面 想做这样一个操作,当前目录下有哪些文件,是不是执行ls-lread  ls-lls是一个应用程序,那么怎么执行呢?那就用到了今天execl  或者说,如果你有这样的一个需求,我想在一个进程里面 我想拉起一个可执行程序,或者是想执行一个系统的命令, 这个时候  你优先应该想到 先fork 然后在子进程里面执行execl就可以了
 
 

 父进程里面也可以执行execl 是不是我说了,只要是进程里面都可以执行这个,那么这个函数有个特别之处,

fork出一个子进程, 我们要在子进程里面执行一个ls-l的一个命令, 查看目录下当前有哪个文件,或者查看目录下文件详细信息,这个时候,就用到了 我们今天讲到的模型,

父进程里面有代码段 .txt 当你在子进程里面,你要想执行这么一个ls-l 这样一个命令呢,需要调用execl 这个函数是什么样的呢?第一个是路径  第二个是ls  第三个是参数  null结尾,

内部实现怎么做的呢? 
ls 应用程序,他会把这个东西的代码段, 这个ls是一个应用程序,应用程序是不是都有代码段呀?他会把这个代码段画到这来,ls是不是有代码段我这个子进程也有代码段呀?那这样的话这个ls的代码段,就把这个子进程的代码段给换掉了,那么你说这个子进程的pid变化了没有呀?那么子进程的pid是不是在上方啊?在内核区 这里的没有换,子进程的地址空间 有没有变化呀?也没有,子进程的pid也没有变化,

通过我们这个把ls这个代码段换掉了原有子进程的代码段,这个有点类似于 你买个电脑,你这个电脑系统不好使了 要重装系统,系统是你的灵魂,这个整个子进程 相当于躯体,主机是一堆硬件,你把系统放进去以后,是不是相当于新的系统,把旧的系统给换掉了,那么你在执行的时候,相当于开机的话 现在开始执行的是新的系统,---换核不换壳---
只是换了他的思想,整个进程,他的核心思想是代码段,那么代码段换了,堆栈里面的数据会不会换?也跟着换 而且你要让这新的代码适应这个环境,肯定是该换的全换,

总结:
exec 函数是用一个新程序替换了当前进程的代码段、数据段、堆和栈;原有的进程空间没有发生变化,并没有创建新的进程,进程 PID 没有发生变化。

 
这个内核里面的ipd没有变化,但是堆栈 代码段全换了,你这个代码段肯定有 变量 成员变量   堆 等等 这些不换能行吗?是不是你这个新的代码不适应这个环境呀?这样再执行 一定是从这个ls命令开始执行的吧?

 execl函数

execl函数 函数原型 参数1 这个路径是要执行程序的绝对路径,也可以使用相对路径,只要是能找到这个命令也行,这个参数以null结尾,表明这个参数结束了,

通常第一个参数是arg  这个参数其实是一个占位符,你随便取 一般我们用的时候 就写应用程序的名字,或者命令名就可以,比如说你想要执行ls命令,你这里就写ls,反正你得有,相当于占位符

arg后面 其实就是你这个应用程序 或者是命令的参数,我这个ls是不是要执行一个命令叫ls-l 后面写-l就可以,咱们一会通过一个例子说一下,最后
参数没有了要加个null,是不是告诉这个函数 这个参数结束了,
这个函数有点特别之处,成功不反回,如果失败之后才会返回,我们可以通过perror.打印错误原因。

在这你可以猜一下,什么情况下会失败  通过参数也能看出来,最直观的例子 没有这个应用程序就会失败,

execlp  p代表路径的意思,它本身 有路径,很显然这个地方不写path了 写file 很显然这个是不带路径的,不带路径的 他怎么知道我这个命令或者可执行程序在哪呢?他根据
PATH 环境变量找,PATH 环境变量里面往往配的是系统里面的命令,或者是你经常使用的应用程序,他所在的路径,
windows里面的PATH路径也是这样配的


之所以能找到,之所以你在这里没有写路径 他能找到,就是因为他找的就是这个环境变量,

第二个参数和后面都是完全一样,

既然说这两个函数都能够完成 拉起一个可执行程序,那么这两个函数有什么不同呢?
就在path  或file 如果你用path的话一般情况下拉起的是自己写的一个应用程序, 你比如说 我在我当前目录下写了一个hello.c 我编译成一个可执行程序hello 这个时候你想拉起他,就用这个


比如说你想 执行一个ls-l命令, 你就用file  因为ls-l命令是不是已经,他的路径是不是已经配到这个环境变量里面来了?这个可以找到

下面我通过这个代码给大家演示一下

 

  cp fork.c exec1.c

which 1s  查看这个ls命令在哪个路径

第二个是占位符 ,通常情况下我们就写命令的名字就可以了,第三个参数是命令的参数,第四个参数是null

如果这个函数调用成功,后面这个函数能不能打印出来
如果调用成功后面的不再打印,如果调用失败 后面会打印perror 如果你在用这个perror 没有错误的话,他仅仅打印execl error后面:没有错误的话,他仅仅打印execl

如果说失败才会往下走,如果成功这句话执行不到,

是不是已经把这个当前目录下的文件列出来了?父进程先退出,子进程后退出,就出现这种情况, 实际上子进程已经死掉了,刚才这句话没有打印出来 perror 这就是我用这个函数执行,一个系统命令,我能不能执行一个自己写的应用程序呢?

test.c
命令函参数加上,
这个返回值可以在进程回收的时候得到,现在先不用关心这个,返回0就可以了,这些 hello world ni hao 是不是就是这个test 应用程序的命令行参数,他这个命令行参数用main接收

 

 execlp 带路径也行,不带路径,也行 本来就不用带路径,因为他搜寻的是环境变量,

 execlp能不能拉起一个可执行程序,自己写的,你配置环境变量是一个方法,再一个我能不能把这个路径写道这呀?

所以说这两个函数,你掌握一个就好了, execlp如果说是命令的话,你连路径都不用写,如果说不是路径,那么你就把路径配上

execl无论如何你都要加上路径,

如果说我把这个命令改一下,命令不退出,用ps命令给看一下,退出了就看不到了,

 这样 我拉起来,他退出不了,这样就可以用ps看到他,ps之后最后一列是那个名字,看一下那个名字是什么,

 

 现在跑起来了,这这个时候子进程应该停在那个sleep,这个是不是子进程?你看那子进程 父进程 现在变成几了?为什么变成1,因为这个子进程 父进程已经退出了,他已经被1号进程领养了,后边显示 test hello world ni hao 这个test是程序的名字, hello world ni hao是他的参数,这个参数,就是你调用execlp传进去的,
如果说把这个test名改一下 这里会不会发生变化?虽然说可以改,但是一般人不会去改他 一般这个名字就和./test名字是一样的,那这样的话你看起来就很直观,

 如果说你这样写的话,人家知道你执行的是哪个程序?所以说你写这个execl这个函数的时候,那么这个arg这个第二个参数,就写应用程序,或者命令的名字就可以了,不要乱写,除非你有意让别人看不到,比如说你执行的是l命令,然后你写的m,

 

 为什么要进行资源的回收,
父进程也可以创建子进程, 子进程在退出的时候,它能够自己回收自己用户的资源,那么用户区的资源是不是就,环境变量 命令行 栈 堆...

 但是他并不能够完全回收内核资源,内核资源需要他的父进程完成回收,如果不回收会占用系统资源,你这个进程如果说没有回收他会占用系统资源, 那么你再创建子进程的时候,他的资源会越来越少,如果你每一个都不回收 子进程死掉之后呢,父进程得一直活着,父进程一直活着,这个子进程不能被1号进程领养,这个子进程会立刻死掉  就会卡死不动,就是因为子进程不能完成对内核资源的回收,他只能完成对用户区资源的回收,这一块内核区的资源只能由他的父进程来回收,
如果父进程先死呢?父进程先死 子进程后死 他就成了孤儿进程了,孤儿进程被1号进程领养了,那么1号进程就能够完成对他的回收,当然前提是这个子进程死了之后,如果活着能够回收吗?活着就没有回收的说法,

什么是孤儿进程 什么是僵尸进程,
在linux操作系统下,必须保证每一个进程都有一个父进程,像这种情况,这种孤儿进程,他的父进程,就变成了1号进程,也就是说这个孤儿进程已经被1号进程领养了,他是不是init进程,如果这个时候 这个孤儿进程也死掉了,那么他的回收操作就交给了1号进程,1号进程就可以完成对所有子进程的回收,

 

 

 应该保证父进程先死 子进程后死 在26加sleep
父进程死掉了,子进程getppid 值会发生变化应该是1

 一开始这个
父进程的pid是子进程的pid
child的fpid =2417  是father 的pid =2417
2418指的是子进程的pid,fpid变成1了,这个子进程pid的变化是从原来的父进程 变成1号进程,这样就模拟了一下这个孤儿进程,如果说父进程先死,子进程后死,那么这个就变成了孤儿进程,这个重点把概念搞清楚就可以了,
他有一个父进程,只要有父进程,那么这个子进程的内核资源就会被回收,

 

 

 

 

如果说你看进程状态的时候,看到一个这样 defunct 
父进程名字和子进程的名字是一样的,

defunct 如果后面出现defunct 了,表明这个进程就是僵尸进程,僵尸进程能不能用kill命令杀死呢?
僵尸进程是一个死的进程,他死了 他还能接收信号吗?

 还有,如何才能将僵死进程给彻底给抹掉呢?怎么回收,可以把他的父亲杀死,他的父亲死后,那么僵尸进程就会被1号进程领养,1号进程就立刻把他回收掉了,
这个是一个瞬间的过程,非常的快,你感受不到,

 

 

 在真正开发的时候,不会出现孤儿进程这样的一个问题,因为孤儿进程他本身不是错误,因为孤儿进程是要给活着的进程,但僵尸进程就不一样了,因为僵尸进程,已经死掉了,但是没有回收,没有回收,就意味着他占用着系统资源 如果说有大量的僵尸进程会导致你不能创建新的进程,一个系统里面不能创建新的进程是非常可怕的,既然说有僵尸进程,咱们是不是要想办法如何去解决他?
杀死父进程是临时性解决,但是实际上 这样解决是不妥的,我们完全有方法在父进程里面完成对他的回收,

 

介绍两个函数 一个是 wait函数,一个是waitpid函数,这两个函数是可以完成对子进程的回收的,
从这个参数来看
wait就一个参数,

waitpid 有三个参数,功能强大


wait 他的作用, wait函数在哪个进程中调用?在父进程里面调用这个wait函数,完成对子进程的回收,但是这个函数他是一个阻塞函数,如果说你这个父进程里面调用了这个函数,但是子进程里面没有退出,这个函数一定阻塞,直到子进程退出了,这个函数立马就回收

获取子进程的退出状态,在这 从哪可以体现出来?
这个参数status 这个status是一个参入 还是参出?
这个是一个参出参数,也就是说这个子进程退出的状态 会给你保存到这个里面,接下来你要看一下 这个状态是几呀?

实际上status  这个宏有一大堆,但是我们用的最多的就是这几个,

WEXITSTATUS 获取他到底返回的是几?

你先用if语句来判断这个WIFEXITED是否为真,为真的话你可以用这个宏WEXITSTATUS 来获取这个status值 


WIFSIGNALED(status) 是不是被信号杀死了,我们这个子进程有可能会信号杀死,
你可以在base shell 里面可以用kill -9给杀死,

如果说被信号杀死可以用这个来判断,WIFSIGNALED(status)  如果这个为真,return true 或false 

如果被信号终止了,你可以用这个来WTERMSIG(status)获取被哪个信号来杀死的,那么我们好多可以用kill-9来杀死呀? 除了-9 还有好多,还有-15
上午咱们还来个暂停,等等,那个当然不是死掉,


WCOREDUMP(status) core掉了可以收到信号


WSTOPSIG(status) 也是某一个信号,实际上是被这个哪个信号?slick stop

这几个宏里面我们经常用的比较多的是,

WIFEXITED  WEXITSTATUS  WIFSIGNALED     WTERMSIG   

如果说你对这几个状态不关心,这几个宏你可以不用,不用的话,就意味着pid_t wait(int *status  status 可以传null

看自己的需求,如果这几个函数成功 返回什么  失败返回什么


成功:清理掉的子进程 PID;
失败:-1(没有子进程)

我们这个父进程 可以创建出说有的子进程来,如果说他返回一个其他的值,他返回的不是这个子进程pid 那么他知道回收的是谁吗?不知道,现在他返回的是子进程的pid 那么如果说
失败,返回-1 通常什么情况下返回-1? 没有子进程了,比如说父进程创建了三个子进程,你回收了三次,是不是再回收他就返回-1了,
再想一个问题,你说我父进程创建了三个子进程,如果说我让你使用wait回收的话你应该怎么写
你应该写到循环里面,最后你怎么知道回收完了?
如果说他返回-1了就回收完了,也就是说你一共循环了几次?4次,n+1
循环了3次回收了三个,再循环一次返回-1了立刻break
退出,

//父进程调用wait函数完成对子进程的回收

这个函数不要跑到子进程里面去调用,wait函数只能在父进程里面调用

如果pid>0 这里相当于已回收了,
如果说我这个子进程没有退出,这个父进程会怎么样?
会阻塞等待,就这一点来说,我们这个程序里面是不是可以保证,子进程全退出,父进程后退出 为什么 因为你是阻塞的嘛
只有子进程退出之后,这个20函数才会立刻返回

我在sleep(5); 期间 这个wait是不是一直不返回呀?

那么如果说他这个返回之后呢?把他的pid打印出来,21 

也就是这句话,是不是应该在在5秒钟之后才会打印出来,

 

表明已经回收掉了,
验证退出状态wait的参数要用一下了,
整型值 int status  传个指针进去,
接下来判断一下,看一下他到底是怎么退出的,这个宏你记不住,就翻帮助手册,man 2 wait

如果说WIFEXITED(status) return true  正常退出这个宏为真,
正常退出我们要看一下这个值是几,这个退出是什么意思,这个退出是不是有return呀?
return几 他就获得是几,

怎么获得呢? 有一个叫WEXITSTATUS(status) 用这个来获取,

退出方式不可能有两种退出方式,要么被信号杀死退出了,要么是正常退出的吧,不可能两种死法吧?


WIFSIGNALED(status)如果说是被信号杀死的,那么这个信号就返回真,


WTERMSIG(status)
 

 

 

 

这个子进程退出的时候,这儿26 应该打印出是几来呀? 是9

这39 是不是他的返回状态呀?

这个9就是retrun的这个9,下面我们验证一下被信号杀死的,被信号杀死的 你在这个子进程里面多让他sleep一会,否者 我们反应不过来,

 是不是15  在这里为什么不用9呢?因为这39行有一个9 万一是不是看不出来呀?

15是SIGTERM  这个也能够使进程终止,但是这个信号和9有区别,
在讲信号链条的时候再说

这个SIGTERM 信号能够被进程捕获
9号不能捕获,这个是无条件的 必须死

假如说有10个子进程,死了9个,那么你这个wait函数是不是还会阻塞?如果你写到循环里面,那这个时候第10个子进程也退出了,是不是这个wait函数立刻返回退出了 再循环一次 才会返回-1
那就意味着所有的子进程全部结束了,这个时候你break循环退出就可以了,

wait函数可以完成回收,比他更高级的一个函数,比他更强大的函数waitpid 
pid 肯定是等待某一个子进程,这个后面有好几个参数,如果说 这个值是-1的话,标识等待任一子进程,

特别是比如说你循环创建10个子进程,那么这10个子进程你想全部都回收,这个时候你指定-1就可以了

如果>0你是在等待一个指定的子进程,

比如说10个子进程 我只想回收一个 其他的我不管,这个时候你就要把那个子进程pid填到这就行了,pid = 0
等待进程组ID与目前进程相同的任何子进程,也就是说任何和调用waitpid()函数的进程在同一个进程组的进程。
一般用不着,这就是设置所属组的,这个一个进程属于某一个组,这个的意思是回收这个组里面的所有子进程,

pid<-1 等待其组ID 等于pid的绝对值的任一子进程,(适用于子进程在其他组的情况)一般我们不用,父进程 创建了好几个子进程,默认情况下,这一个父进程和其他子进程都是在同一个组当中,
你循环创建n个子进程,然后你把他的组id看一下是不是同一个,我这几个子进程和父进程,谁是组长?肯定是父进程,这是正常情况下,这个系统还可以把某一个子进程,可以让别人去领养,去放到别的组里面去,他虽然说在同一个父进程,但是不在同一个组了,一般情况下开发我们用不着他

标红的这两个用的最多的,

options 默认情况下是阻塞的,阻塞情况下填0,如果说你想让他不阻塞,可以设置为WNOHANG,W wait的第一个字母,将父进程设置为非阻塞有什么好处?父进程可以执行其他操作,如果你的父进程是阻塞的,他能干其他事吗?通常情况下我们第三个参数设置为非阻塞。

函数返回值

>0: 返回回收掉的子进程 ID:
-1:无子进程
0:参3为WNOHANG,且子进程正在运行。表示他有子进程活着呢 目前没有子进程退出,没有子进程退出,他返回的就是0。

如果说你的第三个参数 设置的不是WNOHANG 而是一个0 0是阻塞的意思,没有子进程退出的话,他会返回吗?
不会返回,所以说就一直阻塞,只有当第三个参数设置为WNOHANG 的时候,我们的这个函数才不阻塞,不管你有没有子进程退出,他都不阻塞,如果说没有子进程死掉,他就直接返回0就可以了,是不是表示子进程还活着呢,当然如果说 有退出的话 返回>0的数,如果全部都回收了返回-1, 

循环创建三个子进程,最后要回收 三个子进程,回收写到一个while循环里面,而且第三个参数,你指定为WNOHANG就可以了,什么情况下,回收就已经全部完成了呀?判断一下返回值是否=-1,如果=-1就回收完了,这个时候break 跳出循环就可以了,

 

 

//父进程调用waitpid函数完成对子进程的回收

在这特意指定为pid ,因为我们只有一个子进程,如果我不指定为一个具体的子进程的话,我可以给一个-1,-1适用面,比较广一些,管他几个 一个两个三个都可以,
第三个 0 阻塞,

ps -ef

kill -9 完全一样, 这个是被信号杀死的

正常退出也行,需要等20秒,20秒过后这个就返回的是9
这个写法和刚才的wait函数一模一样,

 

 -1表示等待任一一个子进程,WNOHANG不阻塞,

 会发生什么情况?非阻塞这个子进程还没有死,
父进程会立刻返回,24立刻返回这个wpid=0,那么如果说这种情况,你说会发生什么情况?到底父进程有没有调用waitpid完成回收了?

如果父进程没死,子进程成为一个什么进程?sleep(2) 42期间 这个waitpid 函数是否立刻返回了?立刻返回这个0,表示这个子进程还活着, if wpid>0 26 只有你wpid>0 的时候,也就是实际上才回收掉了子进程,回收掉了子进程你才有必要去使用这个宏来判断一下 来判断他是被哪个信号杀死了,
如果 wpid=0 子进程还活着,

 

 wpid=-1 子进程已经全部退出了,在这加sleep=100的目的是想看什么?是不是想看僵尸进程产生了,
在我这个子进程 sleep(2) 期间 这个wpid 24是不是已经返回了? 返回后是不是应该走到这个37逻辑这里了?子进程还活着,父进程 接着往下走 sleep(100)46期间 他sleep(2)53是不是已经耗尽了,这个时候僵尸进程已经产生了 僵尸进程之所有产生,就是因为我们只做了一次位pid

 

 2675是child pid 现在子进程 变成僵尸进程了,此时解决僵尸进程怎么解决?怎么去解决 我们代码里面僵尸进程的产生 把这个sleep(100)46去掉,这个根本不是解决问题的根本方法  阻塞是可以,如果我不让你阻塞呢?
假如说 一开始这个子进程还活着,如果说再回收一次 我只要循环回收,直到他死去之后,我才推出 就可以了
把这个wpid写到while循环里面?那么你应该在哪退出呀?应该在wpid=-1 break;

能不能写在wpid>0 break;一个还行,两个还行吗?所以你这儿不用写 大不了你这的循环多跑一次 一个子进程 的确可以这么写,你两个三个 只要是大于1的你这样写就不合理,所以不写在这里,

 这样的话,会打印大量的41 这句话,因为sleep期间会循环n多次, 正常退出,再循环一次,返回值 wpid==-1 这个时候相当于没有子进程了,没有子进程 break 你说在这阻塞 是阻塞哪了? 是sleep(100)  这个时候就可以删掉了50,因为我们这样做的话,就完全可以完全可以保证父进程肯定是晚于子进程退出,创建子进程的模板就可以写了,两个一组合就可以把这个作业搞定了吗,如果你这种情况下,你还让这个wpid在这打印吗?是不是应该把这个注掉啊?在测试阶段你可以这样写的,如果说。。。27这句话是不是也应该给他注掉? 否则是不是也是大量打印,是不是正在等两秒钟?一开始肯定子进程还活着,他是不是一直在循环?你在这里看到 只打印出这几句话,其实他的内部已经循环n多次了,当有子进程退出了,child normal exit, status==[9] 这句话 wpid>0,再循环no child is living, wpid==[-1]  这个数返回-1 如果返回-1 这就意味着没有子进程了,这个时候,直接跳出循环46 父进程结束,整体上就这么一个套路,

 这两个函数的功能,是回收子进程的资源,父进程回收,这两个函数一定是在父进程里面调用的,

waitpid函数 他的阻塞和不阻塞 取决于第三个参数,

pid>0 表示等待指定的子进程
如果pid=-1表示任一子进程 调用一次回收一个


如果说返回的值,是>0的数,表示回收的子进程是pid,如果=0 取决于第三个参数,若options取决于WNOHANG,则表示子进程还活着
如果-1 表示已经没有子进程了,所有的子进程全部结束了,但凡是只要有一个子进程,他的值不会返回-1,

 如果是调用waitpid函数,在循环里面进行回收的话,那么至少循环几次呀?
比如说创建3个子进程,我让你调用waitpid在循环里面回收,至少会循环四次,3+1,回收3个,再循环一次,就会返回-1,这个是不是就可以break了?

waitpid可指定回收某个进程资源 取决于第一个参数,如果这个pid>0的数,是不是就意味着回收指定的子进程呀?

wait或waitpid调用一次只能清理一个子进程

父子进程对应的虚拟地址空间中的数据永远都是相同的

考察的是父子进程 能不能共享一个全局变量 如果能共享,他是不是肯定相同呀? 能共享吗?不能共享,只要你一改,他就不一样了,读时共享,写是复制 父子进程,不能共享全局变量,

进程里面,我们仅仅创建了一个子进程,

我们在父进程里面完成了对子进程的回收,回收中我们用的是waitpid的函数,这个函数功能比较强大,第一个函数,指定-1,表示任一子进程,回收任一子进程, 特别是如果说你这个父进程创建多个子进程,而且我们需要在父进程完成对多个子进程的回收,这个一定要指定-1,

pid_t wpid = waitpid(-1,&status,WNOHANG);status这个状态不用说,反正你自己看,如果你不关心,可以传个null就可以了,
WNOHANG 表示的是这个函数不阻塞,如果你传0,是阻塞,返回值一共分了三种情况,务必记清楚,如果你不清楚 记得查手册,

 -1 是任一一个

 

 如果成功,返回子进程的pid,如果你设置WNOHANG这个参数,但是没有子进程改变状态,是不是改变就退出的意思?没有退出就返回0,如果说返回失败就返回-1,实际上在我们这往往是没有子进程了这个是返回-1,所以我们可以把他作为会,退出while条件的的一个条件,wpid>0 表示子进程已经退出了, 这个时候你可以用这几个宏,把他的状态打印出来,wpid=0表示子进程还活着,
wpid=-1表示所有的子进程 全部都退出了,这个时候就直接break;
整个这个代码,你只要理解这里面的参数代表着什么含义,理解了他的返回值,那么几种情况,那么我讲的话这个你就理解了,务必清楚,为什么要在父进程里面调用wait函数去回收,原因是因为子进程退出之后,他不能回收自己的内核资源,他的内核资源必须由他的父进程进行回收,所以才有了在父进程里面调用wait或waitpid 完成对子进程回收这种情况,我这个程序里面,我在这个while语句循环里面,我循环的话,我这个父进程 其他事情也做不了呀?假如说我这个while语句循环里面,我这个子进程
我这个父进程循环里面, 他就不退出,是不是你在这里面会一直循环呀?
后面会讲使用信号 完成对子进程的回收

我这个父进程,我一开始可以做其他事情,当有子进程结束了,内核会给我发出一个信号,我收到一个信号之后,我再去回收,那么这样的话,是不是就相当于异步操作了?那么你这个父进程知道你这个子进程什么时候退出吗?一开始不知道,但是只要有退出的,是不是内核会通知他呀?通知他他才去回收这个有点类似于什么?
你这个在网上买东西了,你是不是不知道你的货是不是到啊?你到不了货没关系, 你还可以做你的事情啊,上网  看电影 聊天 如果你的快递来了,快递会给你打电话,一打电话 马上去拿 给刚刚说的那种情况类似

 

父子进程能不能共享全局变量呢?不能 读是共享 写时拷贝 读的时候在内存当中只有一份拷贝,父子进程可以共享一块内存的,但是一旦涉及到写操作就会在原有副本的基础之上 再复制出一份出来,修改的话是不是在副本上进行修改呀?

这两个函数 execl execlp,可以在一个进程里面,去执行一个可执行程序或者是一个命令, 这两个函数,是不是都能拉起一个可执行程序,或者命令,唯一不同的是 execlp不需要路径, execlp需要路径,原理 相当于把它要把子进程里面的代码堆 栈 这些数据全部都替换掉,要用被拉起来的可执行程序,他自己堆栈的数据,代码段数据 等 实际上 当一个子进程里面调用了这个函数 拉起一个可执行程序,那就意味着拉起的这个可执行程序 它的核心思想是,把原来的子进程的核心思想给换了,相当于灵魂换掉了,一旦换掉之后你说 这个子进程原有的代码段 还能执行吗?不能执行了,

出现僵尸进程,需要在父进程里面回收他 你的代码在运行的时候出现了,你可以杀死它的父进程,最终解决办法 还需要在代码当中去调用wait或waitpid进行回收,

父进程 创建子进程 相当于有一个地址空间复制,地址空间内核区里面 有一个文件描述表,这样的话fork之后 子进程也有一个文件描述表,而且内容和父进程内容完全一样,这个文件 你父进程 有我子进程也有,我父进程是可以共享一个文件的 这个 大家在写代码的时候,你打开文件是在fork之前,还是在fork之后?你打开文件之后,你这个文件描述表 才有文件描述符的? 然后再fork 是不是子进程 也有了?不要搞混了,有的人是在fork之后打开的,说了半天,老师 这不能共享,结论是错的,它验证的方式不对,
这个 把这个代码打开 咱们可以看一下,

首先打开一个文件,这个文件是一个现存的文件,也可以是一个新建文件也行,接下来调用一个fprk函数  创建一个子进程,这样的话,相当于一个子进程里面也有一个文件描述符,如果父进程里面把这个文件描述符给关掉,你们这个文件有没有真正关闭?没有,是不是只是使它的计数减一了,我在这个父进程里面写了一句话,30 给它关掉,接下来我在这个子进程里面也开始读,
那么我在父进程里面写了数据以后,我这个文件指针是不是也发生变化了?如果你不调用lseek 你能读得到吗?这一点 和dpu2或者dpu 是不是完全一样啊?原理差不多,那个是两个文件描述符 也指向一个文件,这个是不是也是这样的?
在这有一个小的问题,我为什么加了一个sleep(1)38首先你得保证你写完之后再去读,否则的话 你这个read是不是读不到数据?这个fork之后一个子进程以后,你并不能保证 父进程一定先执行,万一子进程先执行的话,是不是你如果不加sleep(1)的话,它读不到数据?你要知道为什么?

 

 这个代码验证一下,父进程写 子进程读 ,要保证父进程先写完 子进程再读 而且你在读之前要记得 将文件指针移到文件的开始处,

读到数据这个,父进程先退出,子进程后退出这种情况,我肯定优先保证 父进程先写完,他写完之后 是不是就立刻退出了?然后子进程再去读,通过这个代码我们就验证了这一点,

父进程的fd 和子进程的fd 他俩指向相同的文件,值虽然都一样,是不是它复制出来的,
父进程把这个文件描述符关掉,它的子进程还是打开着呢?因为你这个文件描述符表里面这个文件描述符,被两个,有没有两个 引用的地方?一个是父进程 一个是子进程,你关掉任何一个地方,都没有关系,除非是你把所有的全部关掉,这个文件才被真正的关闭了

第二个作业有一定的难度了,如果大家如果把昨天的咱们讲的那两个文件模型搞懂了,是不是组合到一块就可以了?

 我们在循环里面连续创建了 三个子进程, 切记这个子进程一定要加break 如果不加break 子进程 也会创建子进程,那最后创建出来的子进程总进程的个数是2的n-1次方,如果连父进程一块加上总进程的个数是2的n次方

 

 

 在父进程里面做的事情,就是回收子进程,在子进程里面 每一个子进程分别执行一个命令,或者是可执行程序,第一个是用户自定义的可执行程序,hello 自己写的,这两个参数会被程序的main函数接收,这个函数有一个特点,就是调用成功,程序不往下走,因为在这里全部换掉了,下面你写什么是不是没用啊?如果调用失败,这句不会不会打印出来?perror();i=1 
这三个程序是不是有不同的返回值,用exit(-1)退出的,当然这几句话 return -1;是不是没用了?因为有失败的情况 
父进程里面做的事情,是回收,在回收 的时候,我们调用的是waitpid表达式,这个函数非常的重要,-1表示回收任意一个子进程,如果有3个表示这三个都回收,第二个status 是表示他要获得子进程的退出状态,这几个状态,你要关心就写,不关心就传null,
第三个参数,WNOHANG 指定waitpid是阻塞还是非阻塞,如果你传0,表示阻塞,如果你传WNOHANG 表示非阻塞,也就是说这个函数不管有没有子进程退出,立刻返回
waitpid的三种返回值,如果说wpid 返回0,表示这个父进程,没有回收任何子进程,而且子进程还活着呢,
wpid ==-1 表示子进程全部死掉了,而且全部退出了,跳出循环 或者是退出进程就可以了exit(0)
continue ;不写也行,因为if else if 只会执行一个,不写也行,
wpid >0 表示有子进程退出,有子进程退出,你可以调用这几个宏把退出状态打印出来,关心就打,不关心也无所谓,如果你的目标很简单,就是想在一个子进程里面执行可执行程序,status可以不关心,就是传null就可以了,这几个可以宏,不用调用

在这个代码里面我为什么用到了while(1) 调用waitpid 只能回收一个,如果是你这儿没有用while(1)那么你这个很可能只能回收一个,或者一个回收不了, 万一说你这个waitpid 还开始没有调用,三个子进程全死掉了,这个已经调完了,父进程还没有退出,这三个子进程才退出,这个时候是不是应该一个也回收不了?如果说我们在while循环里面,有可能会做一些无用功,如果你这三个子进程都没有退出,是不是它一直循环呐?当然你也可以把这个WNOHANG改成阻塞啊,0 它就是死一个回收一个 死一个回收一个,然后死了三个,第四次循环再调用的话44,那么你说这个函数是不是会立刻返回?因为那个时候已经没有子进程了?
但是一般情况下,我们用这个WNOHANG 而且我们这个父进程,一般情况下父进程做的事情,也不只是回收这个操作,也可以做其他的操作,当然你这样写的话,我们的这个父进程是不是做不了其他事情?
第七天讲信号的时候,会讲信号完成子进程的回收
你只要把这个弄清楚了,那个回头说一下你就明白了,这个情况下我们父进程不能说它仅仅是只执行回收这个操作,也可以执行其他的操作,

举个例子,比如说 我有这个服务,我固定开5个子进程,但是有的时候,有某些子进程会异常的死掉,那么异常的死掉只会呢,我的父进程就可以知道哪个子进程死了,为了保持我这个服务里有5个子进程,那么死掉一个,是不是我收到它死的信号之后,我是不是立刻拉起一个来呀?那么这样的话可以维持5个子进程 那个用的是信号,咱们第七天再讲,你先把这个给弄懂了。
那么你说为什么这个父进程要回收子进程?因为这个子进程退出的时候,它这个子进程退出的时候,它并不能回收它的内核资源,用户区的资源 它是能回收的,比如说栈,这个进程退出的时候,是不是自动释放了就?这一块资源需要它的父进程来进程回收 假如说 我这三个子进程都拉起来了,如果说,我不让你用父进程进行回收,我不让你用父进程调用wait,同时我也告诉你不要让他产生僵尸进程,直接父进程退出就可以了

那么你这个父进程是不是拉起三个子进程呀?拉起之后呢,你这个父进程立刻退出,父进程如果退出的话,那么这三个子进程是不是变成孤儿进程了?变成孤儿进程之后,它的父进程是不是就变成init进程了?那么它这子进程然后死了之后,是不是被init进程进行回收啊?init进程是1号进程,init进程可以回收任何子进程,它内部的操作,这个不用你管,
一开始的时候在主进程里面比如说你让他sleep(100)
然后这三个子进程执行完以后是不是在一秒钟 一百秒期间这三个都是僵尸进程,父进程回收之后,这三个进程 被init进程回收,

 一开始都在等待子进程退出,wpid==[0]是不是子进程还活着啊?这个是执行一个什么呀?ps

这个你让父进程sleep(100)秒,你肯定是父进程先退出之后呢,如果没有回收的话呢,是不是全部都变成僵尸进程了 这个时候如果100秒过后呢,这个子进程就全部被回收了 被init进程回收了,因为你这个父进程死后,那三个是不是就变成了孤儿进程,孤儿进程被init进程领养了?
最后呢子进程也就被1号进程来回收了,

今天分大块 pipe  fifo mmap
 

进程间通信

 pipe只能用于有血缘关系的进程间通讯,比如父子进程 兄弟进程,两个毫不相干的进程,它是不能够使用pipe进行通讯的,
使用pipe进行兄弟进程间通讯

pipe 就是之前讲命令的时候那个| 

fifo是linux操作系统上系统文件类型之一,p开头的ls-f  第一个字符 每一行第一个字符是不是文件类型?一共7种,
后面再讲socket之后会看有socket文件是s

mmap既能用于有血缘关系的,也能用于没有血缘关系的进程间通讯,都可以,共享也很虚

实际上用的时候mmap用的不是很多,这个pipe父子进程之间用的还是比较多的,因为它简单,那么如果说不用pipe行不行?不用pipe 也有别的方式,进程间通讯方式有很多种,咱们讲的用的见的比较多的,而且随着技术的发展,有些也慢慢不再使用了,

.

 父子进程是不能够用全局变量进行通讯的,因为父子进程是属于两个进程,他们仅有的关系是父子关系,他们要想通讯 使用全局变量不行,那么如果说在这个linux 操作系统下面,两个进程想通讯,必须通过内核

进程1 进程2 不管他是不是父子关系,有没有血缘关系,都得通过内核,比如说 我进程1想把数据发给进程2,进程1把数据写给内核,然后进程2从内核把数据拿走,这样的话就实现了进程1进程2的之间通讯,这个内核类似于什么东西,举一个现实生活中的例子,
你租房,找不到房东,找中介,然后从中介那打听一下有租房的,然后中介告诉你哪里有租房,你就可以通过中介找到房东


套接字 有一个叫本地套接字通讯是在一个机器上,还有一个是网络通讯,网络通讯是跨机,

 两个进程间通讯,必须通过内核,不管有血缘关系,还是没有血缘关系, 它必须通过内核,
信号也是通过内核 

管道就是| 管道左边有命令,右面也有命令,管道左边命令的输出,是不是写到管道里面去了,管道右面是从管道的读端,读走数据,

 

当你调用调用pipe会在内核当中开辟一块缓冲区,这块缓冲区,我们就称之为管道内核缓冲区,管道有两端,一个管子只有两头,其中这个管道有两端,分别是读端 和写端,那么数据在交换的过程, 进程B它可以把数据写到管道中的写端,这个时候相当于水流进去了 然后A从管道的读端,读走数据,那么这样的话
进程A和进程B之间是不是就完成了数据交换了?
图里面只是说进程B把数据写到管道,然后进程A把数据
读走,只能做到进程B往进程A发数据,如果说我让你做一个既能发也能收,应该怎么做?可以用两个管子,进程A是写,然后进程B去读
管道的本质其实是一块内核缓冲区,这一块缓冲区有读写两端 其实我们在用的时候,这个读写两端分别用各自两头用一个文件描述符来表示,分别代表管道的读端和写端,那么涉及到文件描述符,那么你操作的时候怎么操作,是用read write 
数据的流向是规定好的,数据的流径它只能从写端流到读端,
如果进程结束了,这两个进程都结束了,父进程里面创建管道了,我子进程里面是不是可以使用这个管道? 文件描述符是不是 这个文件描述符是不是可以被子进程继承啊?如果AB进程都结束了,这个管道它会自动消失 不用你close

管道的读端和写端 默认是阻塞的,普通文件是非阻塞的
管道 socket 文件描述符都是阻塞的

如果说进程B把数据写到了写端,进程A在读端把数据读了,这个管道里面数据有没有?这个数据是不会留到里面的,只要读到就没有了,如果说不这样做的话,会产生什么情况呢?这个管道会满,满了之后 管道还能写吗?读走了就没有了

 内部使用的是环形队列,队列是两端,先进先出,使用环形队列有什么好处?效率高,有两个变量,记录了管道的进的端和出的端 环形队列效率高,内部的实现,你只需要知道它是队列就可以了,
为什么说他是一个队列?是不是你通过这你能看得出来呀?他在写的时候,只能从写端写,读的时候只能从读端出,他是单项的,我们打饭的时候流动也是单项的,排队往后面排,前面打完饭直接就走了,
默认缓冲区大小是4k,

那么 512 这是bytes  这个是8, 那么4kb是怎么来的?是512*8 这是8块,每一块是512 一共是4k
实际操作过程中缓冲区会根据数据压力做适当调整。
我们这个管道只有4k,默认是4k的,假如说你写数据写到5k,他就报错,那你写4k+10 他可能不报错,所以这是适当调整,你不能超出缓冲区大小太大了,一般情况下, 你只要是多端,不停的来读,写端 不停的来写,那么他的数据就不会留在这个管道里面,他也不会说,这种填满的概率不高,如果说你光写不读,当然写满之后他就不再写了,进程获取不了这个文件描述符,那么父子进程间就可以,我fork出一个子进程以后,子进程也有一个文件描述符,他是不是就可以操作了?
 

 有读有写,填满的可能性不大,不会阻塞,除非是你读的慢写的快,
如果说你读的快,写的慢,这种情况根本就不存在,

 

 

管道就是内核当中的一个缓冲区,只不过你用的时候,你只能用两头,我可以操作这个文件描述符,这两个文件描述符我是如何获取的,


int pipe(int fd[2]); 2告诉你,传一个长度为2的数组进来,整型数组,文件描述符都是整型的,int*fd 也可以,但是你传int*fd 和这个有什么区别?
sizeof(fd)这两个值一样,
为什么这里写了一个fd[2] 他的意图告诉你,你要传递参数,要传一个长度为二的数组进来,
int fd[10]; 吧这个fd传进来 是一样的,但是我们都清楚管道只有两端,所以你传10有一点多余, 

这个意思告诉你,你要传递一个长度为2的整型数组就可以了,如果你写int *fd 和int fd[2]是一样的,
这个函数调用成功,内核会给 会告诉你两个文件描述符,怎么告诉你的?就是通过这个参数返回给你的? 当然返回值是int 只能返回一个值,他能告诉你两个运算符吗?
fd[O]存放管道的读端,fd[1]存放管道的写端
接下来你再操作的时候,直接用read write这样的函数,来操作这两个文件描述符就可以了?和普通文件接近是一样了,
你得到这两个文件描述符,接下来你使用的操作和操作文件是一样的,但是 你实际上操作这两个文件描述符,你操作的实质是操作内核缓冲区。你操作文件的本质是不一样的,但是表象是一样的,

 

 

进行父子进程间通信的时候是在fork之前还是fork之后?在fork之前先创建一个管道,在fork之后这个子进程是不是就可以使用这两个管道了?
父进程创建管道,他得到了读写两端,fd[0]指的是读端,fd[1]:指的是写端
第二步:父进程要fork一个子进程来,子进程也有这两端,

第三步:父进程关闭 fd[o],子进程关闭 fd[1]

父进程是要写或者读,这个子进程也只占一端呐?所以你用哪端就留哪端,不用哪端就关哪端,在这里父进程关闭的是fd[0],他保留了fd[1],子进程关闭的是fd[1]保留的是fd[0],父进程可以从管道中去写,子进程从管道中读,这样就完成了父子进程间通讯

一个进程能否使用管道完成读写操作呢? 可以,你一个管道能,两个管道是不是更能?
一个进程从这个管道里面写进去,另一个进程是不是从这个管道里面读出来呀?在这里面有意义吗?这样不是多此一举吗?自己给自己设置障碍,

使用管道完成父子进程间通信?

数组名,就代表数组的首地址,就一个地址,数组名本质上是常量指针,这个指针改不了,你如果想改只能定义一个int* ,一个p指向它啊?fd i =fd+2 不行,这个是一个常量指针,

大多数情况下系统调用,成功返回零,失败返回-1,并设置error number

创建子进程,

在父进程里面写数据,在子进程里面读数据,图上已经说明了,用哪端留哪端 不用哪端 关闭哪端
父进程写操作,所以说 关掉读端

 

子进程读数据,要关闭写端
有没有出现这种情况,子进程先执行的,父进程后执行的,read函数是阻塞的,如果说没有数据他就阻塞,他那父进程写完数据,read会立刻返回
要知道为什么read函数肯定读到数据,是因为假如说一开始没写进去,你开始读了,他就阻塞,当然一开始写好的,直接就读出来了,read函数此时是一个阻塞函数,read在没有数据的时候阻塞,wrait写满了阻塞
 

 出现了子进程先退出,父进程后退出的情况了,如何让父进程先退出?
32的返回值,就是子进程的pid,这样写就保证了,子进程先退出,父进程后退出,wait是阻塞函数,他执行之后就立刻返回了,

 read如果函数没有数据可读的话,他就阻塞,
子进程阻塞到43这个地方了,父进程阻塞到31这个地方了
5秒钟过后父进程写了,子进程立刻就返回了,返回之后这句话44就能打印出来了
我们在用管道的时候,读写两端在一个进程里面,只能保留一端。

 

 ps aux | grep bash 这句话显示ps aux 会有一大堆结果,但是我只想看,这一行里面,每一行里面,
这一行只看包含bash的,不包含bash的,我不关心, 

ps aux 输出会写到管道里面去,如果让你实现这一个操作,你优先会想到什么? ps aux 命令写到标准输出了,现在不让你写到标准输出了,写到管道的写端,文件重定向,dup2 (dup不能做) 如果 ps aux 命令输出的结果已经写到管道当中去了,那么后面用grep bash再读的话,你还能够从终端读吗?这个数据已经流向管道了,原来的grep bash数据从哪读?标准输入,最后的整个的命令结果输出,输出到哪里去了?又显示到屏幕上去了,显示的屏幕还是标准输出,但是中间这句,从管道读这一个,原来读的是标准输入,现在读的是管道 是不是又是重定向操作?
怎么在我的程序里面执行 ps aux 这样的命令呢?调用excel这样的一个函数,
完成父子进程间通讯,首先应该创建管道啊?pipe就用上了 创建子进程就用fork,
这里面有子进程,子进程退出,需要回收资源用wait,

父进程里面需要创建一个管道,从写流到读端,pcb里面有一个文件描述表,父进程会fork一个子进程出来,
在我们的命令里面 ps aux 应该写到管道里面去,管道创建完以后,

文件描述符表里面, 3和4 指的管道的那个?
3指向读端,4指向写端,

子进程3指向读端,4指向写端,
在父子进程间,完成ps aux | grep bash这个命令
其中管道的左边执行ps aux命令,后面的grep bash让他在子进程里面执行,
父子进程要执行excel的这样的函数, 要执行命令,我们原来的ps aux这个命令是输出到标准输出,标准输出他对应的文件是哪个?/dev/tty原来标准输出是1,现在你想让这个子进程读数据的话,也就是这个管道两端,要想完成通讯过程,你就不应该让这个ps aux 这个命令输出,再往这个标准输出写了,应该往管道的写端写入,应该做一个重定向操作dup2(fd[1]. STDOUT_FILENO);
现在不应该往/dev/tty写了,应该往管道的w写,应该写到管道的写端,实际上数据已经流向管道了,现在管道里面有数据了,那么grep bash应该从管道里面读,原来是标准输入,接下来是不是我们也应该和管道左边的操作一样?也做一个重定向?将这个标准输入,重定向的管道的读端,管道的读端是不是数据流出的部分?

原来子进程的0是标准输入 /dev/tty grep读的时候都是从这个终端写进去的?终端写就相当于输入,
ls往外写,是不是相当于就是输出?一个输入一个输出,是不是反向的?
你这个grep bash原来的是从标准输入读的,现在你还能再从标准输入读吗?从标准输入读还有东西吗?啥也没有,这个数据是不是流到管道,已经从管道的写端写进来了,
你应该从管道的读端读走数据,应该也要做一个重定向,

子进程原来文件描述符的1往 /dev/tty 写,0是操作这个 /dev/tty文件的意思,你相当于写的话是往这个终端写,

往这个终端写,相当于你操作的是0这个文件描述符,
现在说,你在读的话还能这样干吗?

原来的话你这个0 读数据相当于从这个 /dev/tty文件里面读,现在应该不从这里读了,从管道的读端

数据的流向,是父进程1到管道w,管道r到子进程0,这一步操作0到 /dev/tty 打× 
怎么着,把管道r到0, 把/dev/tty关掉,重定向操作, dup2
管道的读端,复制到标准输入,dup2(fd[0], STDIN_FILENO),我做了这个操作之后,后面的执行前面的,后面这个 STDIN_FILENO 原来指向 /dev/tty 这个文件,现在来说他应该指向r这一端,这样的话,这个0就和r连接到一起了,
父子进程要想使用管道的话, 用一端,留一端 不用哪端关哪端?对我的父进程来说,他应该关闭哪端?父进程只写把f[0]关掉 是3,
对于子进程来说,他只留一端,他留的是r 所以说他应该把4关掉,

兄弟进程间通信,实现 ps aux|grep bash使用 execlp 函数和 dup2 函数 父进程要调用 waitpid 函数完成对子进程的回收
 

 //使用pipe完成ps aux |grep bash操作

 

 

 如果说我仅仅是这么写,grep bash 后面跟文件名,搜索文件当中有没有这个字符串,
文件名手动输入的,标准输入,grep hello 后面是一个文件,而我们管道的两端是文件描述符,这个文件描述符,操作起来和普通文件是一样的啊?
grep hello pipe.c 读取的,不就是从标准输入进去的吗?是认为的,
而我们在管道里面,现在管道里面是不是有数据了?那么这个管道里面的数据是不是最终要经过读端读出来呀? 也就是说我这个grep bash应该需要从管道的读端读出来,原来是读的标准输入,现在应该是读的是管道的读端?所以必须做重定向,

这个主进程,execlp后面的wait有用吗?因为你调用成功之后这后面得不到执行啊?这个你给他删掉就可以了。如果你不删也行,万一失败了呢?打印错误 peeror 38
如果说这个命令执行失败,他是不是会往下走?会往下走,他是不是就可以回收掉了?

即便是wait不写,有没有影响?你这个父进程退出之后,如果父进程先退出,子进程后退出,这个是不是也没事啊?它会被1号进程领养了,1号进程对他完成回收,
当然这个peeror38是不是要写一下啊?
只有在调用这个函数失败之后,后面的才会执行,

 

 效果一样,显示的行完全一样,一共是5行,
排序有的时候不完全一样,红色的标记给他显示出来,他是不是加了-color=auto bash 你把这个写到代码里面去就行了,这个是作为grep的参数,

 管道有两端,默认是阻塞的,能不能设置非阻塞? fcntl可以修改文件描述符的flag标识,获取flag属性,
前面讲fcntl函数的时候,我给大家举那个例子,从文件末尾追加,oappend在这不用oappend 用o_netblock 非阻塞,
无数据,没数据再读读不到,如果写端全部关闭了,这个时候你再读的时候他就不再阻塞, 立刻返回,

 管道的这个写全部关闭了,你现在读的话,而且如果没有数据的话,你还能读到吗? 第一肯定没数据,第二
你还有必要在那阻塞吗?你管子没了,你管子写那不写了,验证一下是不是,write函数不写了,
接下来close(fd[1])关闭写端了?接下来你再读,这个read这会不会阻塞,
这个管道只有两端,我关闭一端,然后写端关闭之后,你再读,看能不能读到数据,第一肯定读不到,第二他会不会阻塞,会立刻返回,不阻塞了,

 全部关闭read会阻塞,没有关闭写端,是不是意味着人家有可能会写数据呀?如果你全部关闭了,意味着全部都不写了,也写不了了,那时候还阻塞还有什么意义吗?这样 read函数是立刻返回还是阻塞👆原因是你没有关闭写端,但是你又想读,这个时候就会阻塞,

 读端全部关闭,举个例子,现实生活中水管,如果把水管的出水口给扎死,这个时候你猛灌水 是不是就喷你一脸?
管道破裂,进程终止,内核给当前进程发 SIGPIPE 信号

程序里面保留了一端,写端,我把读端关掉,你看我这个进程会发生什么情况?

他就写不了,因为你读端已经全部给关闭了,管道就破裂了,
如果读端关闭,你再写的话,这个进程根本就跑不起来,立刻就死掉了,

读端没全部关闭
缓冲区写满了write 阻塞缓冲区没有满

这样写,这个进程按理说不会退出,

 阻塞在write
会停下来,停在write那了 write阻塞了,因为管道写满了,他就停下来了,管冲去没有满,write继续

 fcntl函数可以设置文件描述符的flag属性,我们前面讲open的时候讲了一个网文的末尾追加,用的是o_append现在我给大家讲一个如何设置 管道的阻塞 非阻塞,默认是阻塞的 只能把阻塞设置为非阻塞 怎么设置呢?就通过这三句话
 

 还在程序里面把管道的读端设置为非阻塞,没数据的话,立刻就返回,原来默认情况下,如果没数据就会阻塞,前提是没有关闭管道的写端 注销掉write 相当于没写东西,没写东西在这读的话默认会阻塞,而且我这个管道两个都没关,
设置管道的读端为非阻塞

 

 

 没有数据可读 read返回-1,有数据可读你也把那个数据读出来,然后立刻返回了,

 

管道的读写行为,也可以正常测一测,
除了命令之外,还有可以通过函数获取管道的大小,
long fpathconf(int fd, int name);

fd是文件描述符,这个文件描述符既可以是读端也可以是写端,他是一个整型值,后面是一个name
这个函数是一个多功能函数,如果说你要获取管道的大小,你要填这个宏

long pathconf(const char*path, int name)yaoyong 要用FIFO.的路径 ,不要用普通文件的路径,
_POSIX_PIPE_BUF 用这个也行,对应的宏是这个_PC_PIPE_BUF

 这个一般情况下,我们也用不着他,
是不是有两个?fd[0] fd[1]这两个都是指向的管道 都表示管道的意思 

4096 =512*8 这个函数用的不太多
 

FIFO 常被称为命名管道,前面讲的管道(pipe)是匿名管道,

操作pipe 我们要用文件描述符,很显然如果一个进程里面你创建一个管道,那么另外一个进程是没有办法获得这两个文件描述符,只能是父子进程间 或者兄弟进程间才有这个东西,所以他只能用血缘关系的,兄弟进程之间也可以,但通过FIFO,不相关的进程也能交损数据。

FIFO 文件是一个管道文件,那这个管道文件的话,ls-l 第一个字母是p 这个标签用来标识内核中一条通道。在操作的时候 就操作这个文件就可以了,操作这个文件,就相当于操作这一块内核缓冲区

 如果说你要使用fifo这种管道进行通讯的话,首先要创建fifo文件,创建fifo文件有两种方法,一种方法可以通过使用命令 mkfifo 

另外可以通过一个函数,叫mkfifo
只要你创建这个fifo文件,在内核当中就会给你分配一块内核缓冲区,你在操作的时候,你是不能够直接操作内核缓冲区的,不能直接操作,你要么你使用他提供给你的接口像pipe 最后你可以得到两个文件描述符,
你必须通过操作fifo文件,你操作fifo文件就相当于操作内核,比如说我进程A write这个文件,文件是1,你write这个文件其实
写到这个进程B里面来了, 
然后你read这个文件,相当于从内核缓冲区去读,

大家想一下是fifo快还是pipe快?应该是pipe快 但是pipe有局限性,
fifo能用于没有血缘关系的两个进程间,同样他照样也可以运用于有血缘关系的,
但是如果是有血缘关系的你使用pipe  那个更简单

如果没有血缘关系,你也要使用管道的话,你要使用fifo

如何创建fifo管道呢
一个是命令,一个是fifo函数,
只要你用mkfifo函数创建管道以后,这个返回值,是成功或者失败的意思,你有这个返回值,接下来你可以使用open函数打开它,打开以后接下来的操作就和操作普通文件类似,但是你不能使用lseek() 为什么不能使用lseek() 因为你这个文件里面没有内容,没有内容会有文件指针吗?
这个文件就相当于标签,你操作的时候就操作这个文件就可以了,操作文件实质上就是在操作这一块内存缓冲区,因为你写文件的话,就写到文件内存缓冲区里面来了,读的话从内核缓冲区里面读数据

 使用mkfifo myfifo

p表明他是一个管道文件,上午讲pipe不是文件类型之一,那个没有 在本地磁盘有文件,但是我们使用命令的时候那个| 实质上就是那个pipe
实质上你是要能够会操作普通文件,你就会操作fifo
只不过你操作普通文件的时候,你事先要先创建一个fifo文件,

fifo文件创建好了,我进程A open 这个文件,我进程B也open这个文件,open这个文件一定是同一文件,他们之间联系纽带一定是这个文件啊,
进程B write一个  进程A read一个就可以了

同理你这个进程A把数据写到管道当中去呢,如果进程B把这个数据读走了 管道当中没有数据,

进程A和进程B你操作文件的时候呢,你既可以读也可以写 我进程A我写的,我进程A能读吗?可以,我进程B我写的,我进程B能读吗?但是这样没有意义,使用这个fifo文件的目的是在两个进程间完成通讯,那么你一个进程你使用fifo有什么意义呀?那你还不如定义一个栈空间呢,直接

写完之后,不用了 关闭文件
怎么让程序不退出?敲一个回车就退出了,随便敲一个键就退出了,c++里面讲的systenpost 在linux下不能用,可以用getchar(); 他是不是等待一个输入啊? 一回车马上结束,#if 0
#endif 

开两个窗口,应该把这个myfifo文件给删掉? 我用函数创建他

读不到应该是退出的原因,加一个sleep(10) 并注销close  是正常的

但是以前没有遇到这种情况,如果把sleep放到上面呢?

我如何保证启动fiforead 我启动起来也不报错?
我判断myfifo文件是否存在,如果存在我就不创建,如果不存在我就创建,这个时候就用到一个函数,用man access  这个函数可以检查文件是否存在,
int access(const char*pathname,int mode);第一个参数是pathname 第二个是mode 是读权限的意思
F_OK 检查这个文件是否存在的意思,什么时候存在,什么时候不存在呢?要看他的返回值,如果他返回值,认为这个文件是存在的,如果失败 认为这个文件不存在,
报错 返回-1,
循环读 循环写

memset写在这个wile循环,每次都要需要初始化,
但是你这样干的话,会不会照成打字太快啊?那边一直写,这边一直读,循环的速度非常的快,
正常情况下,如果我向看到更好的效果是不是你应该加sleep 
一直在闪,都打不开了,太快了,而且没有加回车,

如果加到read那 一会缓冲区就满了,其实不如加到write好,你加在read那是不是有可能马上就满了,

如果说我让你打印这个buffer是第几次写过来的,也打印出来你怎么来?

写进去,读出来,
sprintf可以进行格式化,

先把这个读打开,否则会照成可能会写满的情况,但是这个不会吧?这个有sleep(1)

这个access不仅可以判断文件是否存在,而且可以判断这个文件权限,
 R_OK, w_oK,and X_OK .F_OK 
但是他测的到底 都是谁的权限呢?应该是用户的吧?如果你不指定的话,应该是当成用户的 ,没有说谁的,我想着应该是
当前用户的,或者是你这个进程有没有对这个文件读写权限,那么你这个进程有没有 就看你这个用户有没有
如果当前用户对这个文件有权限,那么你这个进程肯定有,因为你这个进程是属于这个用户的,

 int ret = access(
ret 如果=0 表明这个函数已经存在,不等于0有两种情况存在,要么是你这个调用函数失败了,要么是这个文件不存在,在这里我们统一归结为没有这个文件,你就调用mkfifo这个函数来创建一个就可以了,
接下来你是不是open文件,然后接下来就是读写了,当然我这里只有一个进程写,另外一个进程读,而且最后我们改了循环读写,如果说你使用pipe想进行循环的来回的读写的话,应该使用两个管道
当然我们这个里面 ,你现在他是不是写了? 我这里是不是可以读啊?

是不是A给B写了一个,B给A也写了一个?

40 write(fd, buf,strTen (buf));阻塞了,阻塞在44这个位置了,40write阻塞了

40read不写了,

这两个会相互阻塞,程序的目的使这个管道用于两个进程间通讯,

 存储应摄区io io操作实际上指的是什么操作?文件io或者设备也有io,

 

内存 磁盘文件, 他能够把磁盘文件,映射到内存当中去,这样你操作内存数据块,内存里面的数据就相当于在操作文件了,
mmap映射的部分 相当于磁盘文件, 内存地址空间是内存,他把这个文件的内容映射到内存当中去,这样的话你就可以使用内存 操作相关的函数,mmap copy stringcopy这些东西,就可以直接操作了,读就相当于读的文件内容,写也可以对文件照成影响,你写的这块内存 他会反映到文件里面去啊?当然你也可以不让他反应,
还有相关参数可以设置,就是把文件映射到内存,操作内存就相当于操作文件,直接操作内存快,还是直接操作文件快?内存,主要目的就是这个,如果
一个进程读内存地址空间,另一个进程也读内存地址空间 是不是相当于利用这个mmap文件来实现两个进程间通讯啊? 原理前面类似,接下来如果你操作这块内存的话你还用read 或write 吗,

直接使用vreycopy,stringcopy就行了吧?就像你前面内存操作函数都行,
mmap函数怎么用,

这个函数参数比较多,虽然多呢,但是有些参数有默认值,真正让你填的没几个,
1.void *addr 是一个万能指针,填什么类型都可以,但是这个值 我们一般情况下都设置为null 表示这个函数的返回地址由系统给你指定,也就是说你这个参数如果你填一个地址的话呢,这个地址没有用还行,如果这个地址被使用了,那他就直接报错了,实际上这个地址我们能确定吗?这个地址意思是,你想把这个文件映射到物理内存的哪个位置上去?你能确定吗?
那你不确定就交给内核给你设置,所以这个你直接传null就可以了, 

size_t length你是不是要把文件映射到内存当中去?我这个文件2k 我只映射其中的1k可以吗?当然可以了,这个length一般情况下填文件大小就可以了,这个文件大小怎么获取,有两种方法,第一种是用lseek返回值,另外一种方法是用stat具体用哪个,你看你到时候用哪个方便,如果这个文件打开了用lseek方便,
看第三个prot 这个参数主要是映射区的保护方式,最常用的:
读:PROT_READ
写:PROT_WRITE
读写: PROT_READ|PROT_WRITE
你用的时候不知道用哪一个,到时候你把这个全写上就可以了,既可以读 又可以写
这一点有点类似于 open函数的flag 打开的标识,打开的方式


flags这个参数比较重要,
flags 映射区的特性,经常用到的有2个,一个是
MAP_SHARED  这个文件是不是已经映射到文件当中去了?你对文件的修改,你对这个映射区的修改,也就是你对内存的修改,你让不让他映射回文件,如果你对内存的修改,你想把他反应到文件上去,那么你就要用MAP_SHARED 是共享 ,如果你不想让他反应到文件当中去,也就是说你对内存修改了,你呢不想让他反映到文件当中去,那么你就用MAP_PRIVATE:
一个能够对文件修改,一个对文件不能修改,具体用哪个是不是看需求啊?如果说我把这个文件映射到内存当中去,我们仅仅对这个文件进行读操作没有写操作,那么是不是用第二种比较合适啊? 当然你用第一种是不是也没事啊?

int fd   这个是不是你要首先打开一个文件啊?然后把打开文件的返回文件描述符 传到这就可以了?
还有off_t offset表示从这个文件什么位置开始映射,一般情况下从0开始就可以了,比如说我把整个文件映射过去,是不是从文件的第0个位置,一直到文件的末尾啊?全部映射到文件当中去吧?

这个参数 虽然非常多,对于我们来说,我们这个很多都可以有默认值,

如何获取文件大小,lseek 先把文件指针移动到文件末尾,然后再调用lseek 返回值就可以了吧?
或者使用stat函数

flags:
MAP_SHARED: 
你对映射区的修改,能够反映到文件当中去,是不是相对于能够对文件进行修改?
对映射区的修改会反映到文件中,可以对文件进行修改
MAP_PRIVATE这个意思是,你对映射区的修改没有办法反映到文件当中去吧?对映射区的修改 不会对文件产生修改,

如果你仅仅是对这个文件进行读操作的话,就是读一下,那么你使用MAP_PRIVATE就可以了,

fd 打开的文件的文件描述符,
fd= open();
offset:把这个文件内容映射到内存当中去的时候,那么你指的是从文件的哪个位置开始映射,一般情况下,传的时候,从文件的开始处映射就可以了, 一般传0,

调用这个函数之前,优先打开一个文件,打开一个文件就可以调用lseek这个函数能够获取这个文件大小,而且addr你也不用传,直接传null就可以了

你也不用自己指定,
这个函数虽然多,实际上并不难,
我们的目的是把文件映射到内存,那么这样的话呢,我A进程可以读内存,B进程也可以读内存,那么这样的话,是不是就可以实现两个进程间通讯了,实际上,这两个进程通讯的时候,实际上是借助了这个文件,
我如果A进程我想与B进程使用mmap进行通讯的时候呢,

过程肯定是 我A进程 我打开这个文件,我映射内存,我B进程也要打开这个文件, 你这个内存地址 你怎么让另外的一个进程知道啊?这两个进程之间是相互独立的,你只能是进程B也打开这个文件,然后也映射过去,那么这样的话,你这里A进程和B进程打开的是相同的文件,他们映射到内存当中去,是不是同一块啊?那么这样的话是不是就可以操作了?

验证一下到底使用mmap 能不能完成 两个没有血缘关系的进程间通讯,那么有血缘关系的进程间通讯 不只是有血缘关系,没有血缘关系也可以

只要是在没有血缘关系的两个进程间通讯,他是不是能够在有血缘关系的进程间通讯啊?
因为你可以把有血缘关系的进程相对于关系更紧密一些啊?

munmap这个函数和mmap函数正好相反,

函数作用:
释放由 mmap函数建立的存储映射区

释放有两个参数一个是addr ,很显然这个参数是怎么来的,这个是由mmap返回的,他返回的是一个指针void *,这个指针就用到这个addr地方
你释放的话,你得知道释放哪吧?这个哪怎么表示啊?是不是那个地址啊?
这个大小 是不是就是mmap size_t length 大小啊?

你释放多少啊?是不是可以告诉他?
成功返回0,失败返回-1,

对于我们这个mmap函数来说,成功返回映射的区的首地址吧?

建立共享映射区应该在哪建立?应该在fork之前啊?
man mmap

使用mmap函数 建立共享映射区

在这里我就不新建了,因为新建肯定大小为0,

接下来这个fd有了,是不是还缺一个?length 
在这里我已经打开文件了,我用lseek是不是方便呀?

第一个参数fd, 0, SEEK_END

返回一个指针,这个指针,这个调用mmap函数是不是有可能失败啊?

失败的情况也比较多, 什么情况

如果失败value MAP_FAILED  实际上值为-1,

空行一个逻辑段加一个,
pid>0是父进程,
pid=0是子进程,父进程里面写一句话,让子进程去读,
父进程写上去了,子进程是不是可以读啊?
为了保证 这个memcpy先完成,我这个子进程里面可以让他sleep(1) 有可能子进程先执行,那么先执行 你这个父进程还没有memcpy 你说你的子进程能读到数据吗?读不到

当然你这个printf应该是打印一个地址啊,我给他来一个强制类型转换,
这样你再把这个p打印出来就可以了吧?

 文件总要有文件大小吧,你不写东西 文件大小为0,为0,还映射什么区啊,映射的时候文件大小必须大于0,=0是没法映射的,
现在子进程死了,因为父进程先退出,子进程后退出,如何保证父进程后退出啊?
加一个wait就可以了,

文件也变了,这个是你写的,这样的话是不是把文件内容覆盖啊?把文件参数改一下,改成MAP_PRIVATE,


 
上面是MAP_SHARED 可以映射到文件当中去,可以反映到文件当中去,


你看现在改成MAP_PRIVATE还能不能反映到文件当中去
他连写都写不了

如果说你想让两个进程 在这个使用mmap进行通讯的话呢,你尽可能用MAP_SHARED


在这里MAP_PRIVATE他并没有改掉,我们用这个memcpy 是不是相当于 在这个共享映射区里面写东西呢,现在呢,没有写,你看这个文件不就知道了,他要写了,他能读不出来吗?
memcpy不能够对文件产生影响
如果说你使用memcpy 一般情况下我们是把文件映射到映射区以后,我们仅仅是对这个文件进行读操作,不改,如果你想改,如果你想反映到文件当中去,就用MAP_SHARED,通过这个例子我们就验证出来了


我这个指针 void *addr 27这个指针是不是在这个fork之前定义的,那么我在这个子进程里面也用到这个指针啊49?这个addr这个变量和父进程的addr是一回事吗?但是为什么现在能用呢?用的不是他的变量本身,用的是他的地址,这个指针addr49和他的addr27是不是指向了同一块内存啊?所以能用,

addr 26 43这俩值是一样的,而不是这个变量本身
这一块父进程相当于对这块地址,也就是addr 43 这块内存的首地址做修改?所以说因为这块内存是不是我的子进程也能使用啊,所以说,你才能在这个地方打印出来50


否则能打印吗?
父子进程 除了能够共享映射区,还能够共享什么呀?
父子进程还能够共享文件描述符啊?
父子进程打开同一个文件,包括我们这个管道,管道是不是在fork之前 我创建管道,我在子进程里面是不是也可以使用这个管道啊?管道的话是不是在这个父子进程之间通讯啊?这是不是就是说他共享啊?

还有一个就是mmap

其他的能共享吗?成员变量是不是不能共享啊?

堆栈都不能共享,
刚才是用mmap完成两个父子进程间,相当于有血缘关系的进程间通讯吧?

如果说这两个进程毫无关系呢?是不是也能够完成进程间通讯呢?先来一个read.c

 

接下来,读进程 肯定是一个进程写,一个进程读,读
读可以读其中某一个数据,比如说只读前十个,
memcopy stringcopy也可以,都行,这些都是内存操作函数,
写p p是首地址,我直接写addr行不行?是可以的,

void *memcpy(void *dest, const void *src
size_t n);

src 就是 void* 你可以不用转换,这必须得写一个长度,那就写10吧
是不是拷贝10个出来啊?把这10个打印出来 

写👆

拷贝出来一份,基本上都一样,



在这我直接mamcpy 一个是不是就可以了?
咱们的目的是,看那个read进程是不是能读到啊?
能读到是不是意味着,这两个进程可以通讯,

34 地址直接写addr可以吗?这个函数前面是不是也是void*啊?你可以直接用,
 

 那你说先执行哪个啊?先执行写吧,你肯定是写了之后才有数据,

 这个东西肯定是写到文件当中去了,

这就是他使用mmap完成两个毫不相干的进程之间通讯,
这个其实没有什么思路可言吧,是不是你就建立一个共享内存区,然后一个进程读,一个进程写,
首先得打开一个文件,你要想使用共享内存区,首先要打开一个文件因为你这个参数里面有一个文件描述符吧?fd  
你不打开他那这个怎么填啊?
这个fd一定是什么文件描述符?一定是一个普通文件的描述符 你不要把管道的fd 往这里面填
这里只能是打开一个文件,为什么?因为你这个mmap他这个原理是不是把文件的内容映射到内存啊?你只能用文件吧?你不能用管道

打开一个文件,我们得到一个文件描述符,接下来获取文件大小,有两种方法,一种是stak函数,一种是lseek 在这我们用的是lseek,在这用lseek 方便一些,因为你这个文件是打开的,如果说你这个文件没打开,那你就用stak 
 

接下来调用mmap函数,这个mmap函数参数比较多,虽然说参数比较多,其实有的已经定死了,
唯一让你自己造出来的是不是 len 和fd 啊? 这个是你自己造的,其他你不用造,
这个函数如果调用失败了,是不是返回一个MAP_FAILED
这个其实本质上是void*-1 啊? 你用的时候就用宏,人家让你怎么用你就怎么用就可以了

接下来就是开始读数据了,你得到这个共享映射,首地址之后,接下来直接memcpy出来就可以了 然后在这里只读了10个,读出来之后,直接打印出来


对于写的过程是不是也是类似的,你只要得到了内存的首地址 是不是接下来你就可以操作这一块内存了啊?
那么所使用的函数,就是前面大家学c语言那些简单函数,
这一块使用mmap,完成两个毫不相干进程间通讯就说到这

创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区
因为你这个文件内容,最终要映射到内存当中去,你肯定有一次读文件的操作吧?这个操作是你做的吗?里面,他里面自己做的,

当MAP SHARED 时,要求: 映射区的权限应 <=文件打开的权限(出于对映射区的保护)。而 MAP_PRIVATE 则无所谓,因为mmap中的权限是对内存的限制。
举个例子,如果说你这个文件,没有这个读写权限,那么你分析这个mmap能调用成功吗?很显然不能,因为他内部是不是有一个隐含的读操作啊,你把他的读操作去掉了,还能吗?

对于这个MAP_PRIVATE无所谓,因为这个MAP_PRIVATE是不是这个映射区,因为你对这个映射区修改之后,他是不是并不会写到文件当中里面来啊,他这个无所谓

可以试一下,

现在这个文件,一个权限都没有了,能执行吗?./mmap1
你打开就打开不了吧? 这很显然,你这一关都过不了

 映射区的释放与文件关闭无关; 只要映射建立成功,文件可以立即关闭。
也就是说你只要映射成功了, 这个文件你就可以关闭,只要映射成功,后面的文件可以关闭,

 

现在这个mmap 26已经创建映射成功了,创建成功我立刻close

那么close之后,大家看一下,我后面再这个 再做操作的话,这个看看还能不能读出来51
也就是说你这个映射区域内存空间成功之后,你把这个关掉了吧?关掉之后,如果说不行的话,按理说他是不是应该内容是不是反应不到内存当中去啊? 这是一个,再一个的话,那么你是不是也应该读不出来啊? 读是可以读,但是文件内容没有吧?
因为你已经关闭文件了
把这个权限加上,

没有影响吧,是不是可以读出来啊?是不是没有影响啊,这个文件只要是这个映射区建立成功了,这个文件关不关闭没有影响,
这些东西你也不用记,怎么说呢,你是不是可以测啊?


特别注意,当映射文件大小为 0 时,不能创建映射区。听以,用于映射的文件必须要有实际大小;mmap,使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。

当映射文件大小为 0 时,不能创建映射区。 因为你建立映射区的大小是不是指的文件大小啊?你如果说你这个文件大小为0,你说你建立映射区有什么意义啊?你最终建立内存区相当于一个点,没有意义,所以这个文件必须有大小,大家再想一个问题,那么你说我刚刚创建的文件 能建立映射区吗?你要写东西行,你也就是说只要这个文件有大小就行

刚刚建立新的文件,按理说是没有的,大小没有,那就是0,只有写之后,这个文件才有大小,

munmap 传入的地址一定是mmap 
的返回地址。坚决杜绝指针++操。
这个就类似于你用mylock动态申请了一块内存,是指的一块地址,一个指针,然后你把这个指针做加加操作减减操作,最后你再freek 直接close掉,
你freek的那块内存地址一定是mylock出来的那一块内存地址,你不能是不一样的,

文件偏移量必须为 0或者 4K 的整数倍 文件的偏移量你要么指定为0,要么指定为4k 8k 你不能指定成1 2 这样的值,

咱们给他试一下,来一个其他的值行不行,

 报错了,无效参数,能指定4k吗?你这个文件大小没有4k,你在这直接写个4k不行吧?写数字4096

越界了,文件没有这么大小,所以说这个东西,你就填0就可以了,你别乱填,乱填之后会照成很多的麻烦,如果你这个文件超过4096是可以的,你这文件比如说8k,你这写4096是不是可以啊?
你一个文件 10几 20几个,你填个4k

而且你这个大小指定的是len,你从这个偏移量的位置,是不是一直是一共有len个大小啊?

mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

凡是这种返回指针的这种函数,一定要检查返回值,返回值指针的这种函数,是不是相当于返回一个内存地址啊?那么你后续是不是要想对这个内存地址进行操作,你必须操作一块反内存啊?如果你这块内存本来就没有成功,那么再去操作马上就colse掉,

因为这个mmap函数经常犯一些错误,给大家呢 做了一些总结,你把总结大致看一下


第一个参数写成 NULL密 第二个参数要映射的文件大小 >0 映射区的大小,是不是就是文件大小啊,但是这个值必须大于0, 等于0行吗?需要试一下吧?

你根本就创建不了,是不是error?这东西不要记,你试一下就可以了,

第三个参数:PROT READ 、PROT_WRITE 可以指定为读写,这个权限的意思,这个权限  是不是应该等于open函数的那个参数了?那个权限了?也就是应该小于等于open函数的那个参数了?那个权限了,应该小于文件本身的权限吧?

第四个参数: MAP_SHARED 或者 MAP_PRIVATE
他俩的区别已经说过了,一个可以反映到文件上去,一个不可以反映到文件上去

第五个参数:打开的文件对应的文件描述符


第六个参数: 4k 的整数倍, 0也是4k的整数倍

可以 open 的时候 O_CREAT 一个新文件来创建映射区吗?
使用open的时候,O_CREAT创建一个新的文件 能建立映射区吗?很显然不行,除非是你建立完文件以后,你对文件进行写操作,那么也就是说你要保证你这个文件大小不等于0,


如果open 时O_RDONLY,mmap 时 PROT 参数指定 PROT_READ|PROT WRITE 会怎样?
很显然不可以,open的时候是不是指定的那个参数flag
那个打开方式,他的权限是不是必须大于这个mmap啊?
我举个例子,你打开权限的时候你指的是O_RDONLY
但是你使用mmap的时候,你用来写 很显然是不行的, 肯定是不行的,

mmap 映射完成之后,文件描述符关闭,对 mmap 映射有没都影响?
没有影响


如果文件偏移量为 1000 会怎样?
这个值应该必须是4k的整数倍,不能随便给一个值

对 mem,越界操作会怎样?
肯定会有的时候会tao掉

如果mem++,munmap 可否成功?
肯定不行,


mmap什么情况下会调用失败?
很多,比如说文件大小等于0, 权限open指定那个只写,你那个mmap弄个只读,是不是也不行啊? 很多种情况,
文件偏移量不指定4k倍数


如果不检测 mmap的返回值,会怎样?
肯定不行,调用 mmap有可能会失败,失败会返回一个mmapfile
那么你再操作那块内存的话,是不是肯定报错啊?
只要是返回指针的,都要检查返回值

即便是咱们前面讲过的,一些函数,没有返回指针的,是不是咱们还检测返回指针啊?那么况且是这种呢?

 

父进程 子进程 可以通过文件,
父子进程,我给大家演示的时候,第一个父子进程
直接使用mmap 没有使用文件吧?他用文件吗?
有文件是在父进程之前打开的吧?只打开一次
如果说你两个毫不相干的进程的话是不是打开两次啊?

a进程打开一次,a和b进程打开同一个文件,那么他们这块文件内容会映射同一块内存啊?那这样的话,他就可以直接通讯了

在父子进程打开文件的时候,只能打开一次
应该是只需打开一次

使用 mmap函数建立匿名映射:  匿名映射不使用文件,跟文件没有关系,他的用法是这样的,前面用法都一样,

mmap(NULL,4096, PROT_READ PROT_WRITE, MAP_SHAREDG |MAP_ANONYMOUS, -1, 0):

这个你需要加一个MAP_ANONYMOUS 匿名的意思,这个文件描述符,你就不能填一个文件描述符了吧?填个-1,这个怎么去找?
在帮助上有,
MAP_ANONYMOUS和MAP_ANON是同义词是不是相当于这个两个是同一个事啊? 已经不再使用了


MAP_ANONYMOUS 这个映射不需要任何文件的支持,backed 是支持的意思,也就是说不需要任何文件的支持,也就是说,他不用写文件,
他的内容这块映射区的内容初始化为0, 再看文件描述符 和offset忽略了, 我们这个mmap函数是不是有一个offset是最后一个参数吧?还有一个文件描述符啊?忽略了
然而一些实现要求 文件描述符设置为-1,
一些可移植性程序,需要保证这一点

MAP_ANONYMOUS 这个宏,必须要结合MAP_SHARED来使用,如果你在这里指定的是MAP_PRIVATE 你这不行,用不了,

从since 2.4以后,我们这个内核版本还是比较高的,
所以用的时候,就像我们上面说的

MAP_SHARED和MAP_ANONYMOUS一块来用就可以了

而且fd指定-1,了解一下就可以了,试想一下这一种匿名映射能用在 这个的话没有文件,两个进程之间没有任何关系,你找不到关系,找不到纽带,你那个的话 你有文件的话,还可以找到这个文件进行进程间通讯的纽带,所以说他只能应用于有血缘关系的进程间通讯,

 

 

 应该改有fork的,这个里面有一个,复制进程,
不用打开文件,指定大小4096

 

 父进程写的,子进程去读,其实呢我就觉得,如果说你想完成父子间进程间通讯,你使用这个更简单啊?不用打开文件,也不用使用文件,不用考虑文件的事了,而且这个参数看起来也比较固定啊?你肯定要指定大小4096 你不指定大小,这个映射区是不是没有空间啊?共享映射区没有空间,而且这个东西是不是可以写死啊?你用的时候把这个复制过去就可以了

 这个用的比较多,
因为你用于非血缘关系进程间的话呢,你找不到两个进程间之间通讯的纽带,
前面讲过使用文件进行进程间通讯的话,他是不是有纽带?那么两个是不是都可以打开同一个文件啊?
这个没有,所以他用不了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值