Linux系统编程三:fork函数

fork函数是在一个程序中创建进程的函数,pid_t fork(void),返回值表示创建出的进程的进程号,该函数没有参数。调用一次返回两次,父进程中返回子进程的pid,子进程中返回0.因为一个进程的子进程可以有很多个,而且没有一个函数可以的到该进程所有子进程的进程号,而一个进程只会有一个父进程,还可以调用getppid函数得到其父进程的进程号。

子进程会从fork之后的代码开始执行,在父进程创建出子进程后,两个 进程是相互独立的,各自运行互不干扰。父子进程谁先运行不由fork决定,而是由系统当前环境和进程调度算法决定。

fork函数创建进程:

该段程序中,父进程中fork返回值为真值(子进程进程号),因此在父进程会执行if语句中的代码,我们知道,fork函数调用之后父子进程都会继续执行fork之后的代码,在子进程中fork返回值为0,因此会执行else中的语句,这也是我们经常用来控制父子进程的一种方法。运行结果如下:

父子进程间的数据共享:

       父子进程对于fork之前的局部变量,全局变量,堆区空间都是不共享的。如果我们写一段代码来检测其中某一数据的位置,我们会发现输出的地址是相同的,这是为什么呢?因为fork之后父子进程就是两个独立的进程,系统会为其各自分配虚拟地址空间,在各自的虚拟地址空间中他们的位置是相同的,因此会输出同样的结果,但是父子进程的虚拟地址空间映射到的物理地址是不同的,这就是为什么输出结果一样,却不共享数据的原因。

我们知道父子进程的数据是不共享的,那么子进程是何时拷贝父进程的数据空间的呢?这里会用到写时拷贝技术。

写时拷贝技术:

       Fork之后,父子进程会共用所有的空间,这时内核会将这些空间设置为只读的,如果有任意一个进程想要试图修改数据的时候,内核才会将该数据所在的页直接拷贝出来。

父子进程是否共享fork之前打开的文件描述符:我们用一段代码来测试一下:

在运行前该目录下是没有a.txt文件的,执行完之后,已经创建了该文件,并且给里面写入了字符串buff的值。

这说明,父子进程会共享一个文件描述符,但是会相互影响。因为该文件的读写偏移量会随着两个进程的操作都会发生变化,当父进程写入一段字符到文件里时,该文件的读写偏移量会走到文件结尾,这时如果子进程要读取文件内容的话,会发现读不到任何信息,因为读写偏移量已经到文件末尾了。那么子进程又是如何实现和父进程共享一个文件描述符的呢?

       当fork创建出子进程后,子进程的pcb结构中的内容会对父进程pcb中的内容进行浅拷贝,因此会将父进程中保存的文件描述符所指向的地址的值拷贝过来,这样他们就共同指向了一个struct file结构体,而这个结构体中就保存了该文件的相关信息(注意是父子进程共同指向一个struct file,而不是子进程将父进程所指向的struct file拷贝了一份)。我们这样做之后就会牵扯出一个新的问题,如果子进程先结束,并且在结束时执行close(fd),关闭文件,那么父进程中还能对文件进行操作吗?

       每个被打开的文件都会有一个strcut file结构体,而这个结构体中有一个变量是f_count,这里面就保存了指向该struct file结构的pcb个数,当f_count为1时,执行close才会真正关闭文件,大于1时只是将该引用计数减一。

第四节课:僵死进程的处理和信号

僵死进程:进程主体结束但是进程pcb仍旧存在(多进程编程中体现为:父进程未结束,但是子进程已经结束)进程结束后,进程的退出状态需要保存到pcb中,为父进程获取子进程推出状态。那么父进程是如何获取子进程的状态的呢?

wait函数

      

Wait函数返回的是已经结束的子进程的id号,参数一般为NULL。调用wait函数可以处理子进程。那么父进程是如何获取子进程的运行状态的呢,这里要用到信号

信号:信号是操作系统中一种事件通知机制,用于进程之间。信号是系统预先定义好的某些特定事件,发送信号和接收信号的主体是进程。信号一般都存放在/usr/include/bits/signum.h文件中。下面就是所有的信号宏

信号的处理方式:信号有三种处理方式,分别是默认(也就是系统指定该信号的处理方式),忽略(不对该信号做任何处理),自定义(自己定义一个函数,或者一个进程来响应该信号)。

修改信号的处理方式:signal函数。

使用signal函数可以将信号的响应方式改变,第一个参数是要被修改的信号,第二个参数是要修改成的函数(响应方式)。wait函数是阻塞运行的,也就是说,一旦调用wait函数但是子进程还未结束,父进程就会停止运行,直到子进程执行完毕,wait函数执行完毕才会继续往下执行。为了提高效率,我们通常会使用signal函数把子进程状态改变的信号响应方式写入一个函数中,而该函数的实现会用到wait函数,也就是说,当父进程接收到子进程结束信号后,系统会调用该信号的响应函数,wait函数因此得以执行,而且父进程不用阻塞。

猜你喜欢

转载自blog.csdn.net/Mr_H9527/article/details/82501228