fork()部分可能会问到的问题总结(持续更新)

2019-07-14 10:40发布

pcb是什么,有什么作用? Pcb是进程管理和控制的最重要的数据结构,每个进程均有一个PCB,其中包含:
  1. 进程标识符;
  2. 处理机的信息(通用寄存器,指令计数器,用户的栈指针);
  3. 进程调度信息(进程状态,进程优先级,事件);
  4. 进程控制信息(程序的数据的地址,进程同步和通信机制);
系统是通过pcb来管理所有的进程; 内存的分页管理,页表是什么?在程序中我们使用的是逻辑地址还是物理地址?引入虚拟内存后,对程序的执行会产生什么样的影响? 页式内存管理是将虚拟内存和物理内存分为大小相同的页,32位下页的大小为4k。系统通过页表来管理虚拟页面到物理页面的映射关系,通常页表也分为多级页表。就像图书的目录一样分为一级目录、二级目录等。虚拟地址实际由页面号和页内偏移值两部分组成MMU(内存管理单元)会分析虚拟地址,再查询用户程序对应的页表(PGB-PMD-PET),准确定位该虚拟地址对于的物理页面。   程序中使用的地址一般是逻辑地址,也成为虚拟地址;(程序在装入内存之前,通常为逻辑地址形式,有时甚至在装入内存之后,程序仍为相对地址形式。为了保证CPU执行程序指令时能正确访问存储内存单元,需要将用户程序中的逻辑地址转化为可以由机器直接寻址的物理地址,这一过程称为地址映射);   对于需要将程序一次性装入内存才能运行的存储管理方案来说,其缺点是明显的。
  1. 当一个参与并发执行的进程运行时,其整个程序必须都在内存;若进程的程序比内存可用空间还大,该程序将无法装入内存运行。
  2. 程序被装入内存后,便一直驻留在内存直至程序运行结束才释放内存,这样就会使一些需要尽快运行的作业无法被装入运行,更严重的是,若进程因I/O而长期等待或程序暂时不执行的时候,它将一直占有内存资源,导致内存不能得到充分的利用;
  虚拟内存将内存和外存结合起来,为用户程序提供一个容量远大于物理存储器的虚拟存储空间,用户程序可以先只装入一部分就开始执行,以后根据执行1的具体情况再依次请求装入剩下的部分,直至整个程序都执行完毕。这就从逻辑上扩充了内存;   在程序中malloc申请1G空间能不能成功? Malloc能够申请的空间大小与物理内存的大小没有直接关系,仅仅与程序的虚拟地址空间相关。程序运行时,堆空间只是程序向操作系统申请划出来的一大块虚拟地址空间。应用程序通过malloc申请空间,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的; 分时系统的特点?时间片?并发和并行区别? 分时操作系统的工作方式:一台主机连接了若干个终端,每个终端有一个用户在使用。用户交互式的向系统提出命令请求,系统接受每个用户的命令,采用时间片轮转方式处理服务请求,并通过交互方式在终端上向用户显示结果。用户根据上步结果发出下道命令。分时操作系统将CPU的时间划分成若干片段,称为时间片。操作系统以时间片为单位,轮流为每个终端用户服务。每个用户轮流使用一个时间片而使每个用户并不感到有别的用户存在。   分时系统具有多路性:有多个用户使用一台计算机,宏观上看是多个人同时使用一个CPU,微观上是多个人在不同时刻轮流使用CPU。 交互性:用户根据系统响应结果进一步提出新请求 独占性:用户感觉不到计算机为其他人服务 及时性:系统对用户提出的请求及时响应   并行:可以同时做多件事; 并发:一次处理很多事情;   根据底层是否有多处理器,并发与并行是可以等效的,这并不是两个互斥的概念。 (eg:我们说资源请求并发数达到了1万。这里的意思是有1万个请求同时过来了。但是很明显不可能真正的同时去处理这1万个请求!如果这台机器的处理器有4个核心,不考虑超线程,那么我们认为同时会有4个线程在跑。也就是说,并发访问数是1万,而底层真实的并行处理的请求数是4.如果并发数小一点是4的话,又或者你的机器牛逼有1万个核心,那并发在这里和并行一个效果。结论是:并行是我们物理时空观下的同时执行,而并发则是操作系统用线程这个模型抽象之后站在线程的视角上看到的“同时”执行); 产生僵死进程的条件?如何避免? 如果子进程先于父进程退出,同时父进程又没有调用wait/waitpid,则该子进程将成为僵死进程;(通过ps命令,我们可以看进程状态);   一般情况下我们可以建立一个捕获SIGCHILD信号的信号处理函数,在函数体中调用wait(或waitpid),就可以清理退出的子进程以达到防止僵尸进程的目的; void sig_chld( int signo ) {     pid_t pid;     int stat;     pid = wait(&stat);         printf( "child %d exit ", pid );     return; }   int main() {     signal(SIGCHLD,  &sig_chld); } 但是有特殊情况上述方法并不能解决: 假设有一个client/sever从程序,对于每一个连接的client,sever都启动一个新的进程去处理client的请求。在client中,发起了多个到sever的请求,sever会fork5个子进程来读取client输入并处理;当我们终止这个client进程的时候,内核将自动关闭所有由client打开的套接字。那么由这个client进程发起的5个连接基本在同一时刻终止,这就引发了5个FIN,sever端接收到这5个FIN的时候,5个子进程基本在同一时刻终止,这又导致差不多在同一时刻递交5个SIGCHLD信号给父进程; 通过测试可以发现剩下了2个僵尸进程,所以建立信号处理函数并在其中调用wait并不足以防止出现僵尸进程。其原因在于:所有5个信号都在信号处理函数执行之前产生,而信号处理函数只执行一次。 正确的解决办法是调用waitpid而不是wait,这个办法的方法为:信号处理函数中,在一个循环内调用waitpid,以获取所有以终止子进程状态。我们必须指定WNOHANG选项,他告知waitpid在有尚未终止的子进程在运行时不要阻塞。(我们不能在循环内部调用wait,因为没有办法防止wait在尚有未终止的子进程在运行时阻塞,wait将会阻塞到现有的子进程中第一个终止为止); 写时拷贝技术及其实现? 描述:可以推迟甚至免除拷贝数据。内核此时并不复制整个进程地址空间,而是让父进程和子进程共享一个拷贝。只有在需要写入的时候,数据才会被复制,从而使各个进程拥有各自的拷贝。也就是说,资源的复制只有在需要写入的时候才进行,在此之前,只是以只读方式共享。这种优化可以避免拷贝大量根本就不会被使用的数据;   虚拟地址空间的拷贝是在函数copy_mm中完成的; 父子进程中的变量n的物理地址是不是在同一块内存空间上? 在。这个一样的地址是线性地址,每个进程的相同的线性地址都可以映射到不同的物理地址上。在fork的时候,子进程从父进程copy了task_struct结构,其中task_struct里的mm就是线性地址的使用情况,mm也会被copy给子进程,所以在fork之前声明的变量,在fork后在父进程和子进程里的线性地址是一样的。 文件描述符?为什么父进程打开文件,在执行fork后,在子进程中能够继续使用? 文件描述符:相当于一个逻辑句柄,(而open,close等函数则是将文件或者物理设备与句柄相关联),句柄是一个整数,可以理解为进程特定的文件描述符表的索引; 文件描述符表:用户区的一部分,除非通过使用文件描述符函数,否则程序无法对其进行访问。对进程中每个打开的文件,文件描述符表都包含一个条目; 系统文件表:为系统中所有的进程共享。对每个活动的open,它都包含一个条目。每个系统文件表的条目都包含文件偏移量、访问模式以及指向它的文件描述符表的条目计数; 内存索引节点表:对系统中的每个活动的文件,内存中索引节点表都包含一个条目。几个系统文件表条目可能对应于同一个内存索引节点表;
  1. 在fork()之前打开的文件,子进程都会继承,与父进程共享相同的文件偏移量;
  2. 在fork()之后打开,这时父子进程关于文件的文件描述符表指向不同的系统文件表条目,也不再共享文件偏移量(fork以后两个进程分别open,在系统文件表中创建2个条目);
  替换进程的过程?Execl  execlp   execle   execv   execvp  execve
  1. 获取命令行;
  2. 解析命令行;
  3. 建立一个子进程(fork);
  4. 替换子进程;
  5. 父进程等待子进程退出(wait);
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新进程替换,从新程序的启动例程(main函数)开始执行; 调用exec并不创建新进程,所以ID并未改变。Exec只是磁盘上的一个新程序替换了当前进程的正文段、数据段、堆段和栈段;
  1. l(list):表示参数采用列表;
  2. V(vector):参数用数组;
  3. P(path):自动搜索环境变量PATH;
  4. E(env):自己维护环境变量;
  守护进程的编程流程
  1. 在父进程中执行fork并exit退出;
  2. 在子进程中调用setsid函数创建新的会话;
  3. 在子进程中调用chdir函数,让根目录“/”成为子进程的工作目录;
  4. 在子进程中调用unmask函数,设置进程的uumask为0;
  5. 在子进程中关闭任何不需要的文件描述符;
  如果父进程有三个线程,在某个线程中执行了fork之后,那么子进程会有几个线程?如果线程中使用了互斥锁,那它在子进程中互斥锁的状态是什么样子?和父进程是不是同一个锁? 在多线程程序中,在“自身以外的线程存在的状态”下一使用fork的话,就可能引起各种各样的问题。比较典型的例子就是:fork出来的子进程可能会死锁; 一般,fork做如下事情:
  1. 父进程的内存数据会原封不动的拷贝到子进程中;
  2. 子进程在但线程状态下被生成;
父进程即使存在多个线程,他们也不会被继承到子进程中;