Linux系统编程(第五章)笔记

本文详细介绍了程序、进程和线程的区别,阐述了Unix系统中的进程创建(fork)、程序执行(exec)以及写时复制技术。还讨论了vfork的特殊性、进程的终止方式,特别是僵尸进程和守护进程的概念及其在系统中的作用。了解这些概念对于理解操作系统中的多任务执行和后台服务至关重要。
摘要由CSDN通过智能技术生成

进程管理

程序、进程和线程

   程序是指编译过的、可执行的二进制代码,保存在存储介质如磁盘上,不运行。一个c程序的编译过程
   进程是指正在运行的程序。包括二进制镜像,加载到内存中,还涉及很多其他方面:虚拟内存实例,内核资源如打开的文件,安全上下文如关联的用户,以及一个或多个线程。
   线程是进程内的活动单元。每个线程包含自己的虚拟存储器包括栈,进程状态如寄存器,以及指令指针。

运行新进程

   在Unix中,把程序载入内存并执行程序映像的操作与创建新进程的操作是分离的。一次系统调用fork()是用于创建一个新的进程,它基本上相当于复制其父进程。一次系统调用会把二进制程序加载到内存中,替换地址空间原来的内容,并开始执行。这个进程称为“执行”一个新的程序,是通过一系列exec系统调用来完成。

#include<sys/types.h>
#include<unistd.h>

pid_t fork(void);

   当fork()调用成功时,会创建一个新的进程,新的进程称为子进程,原进程称为父进程,在子进程中fork()返回0,在父进程中返回子进程的pid。

#include<unistd.h>

int execl(const char *path,
          const char *arg,
          ...);

   execl()调用会把path所指路径的映像载入内存,替换当前进程的映像。
   execlp(),execle(),execv(),execvp(),execve()这些也是exec系列调用的函数。

写时复制

   在早期的Unix系统中,创建进程就是调用fork(),内核会按页复制所有的内部数据结构,复制进程的页表项,然后把父进程的地址空间按页复制到子进程的地址空间中。缺点是十分耗时。
   现代Unix系统采用了更优的实现方式。如Linux采用写时复制的方式而不是整体复制。写时复制是一种基于惰性算法的优化策略,为了避免复制时的系统开销,其前提假设就是:多个进程读取它们自己那部分资源的副本,复制就没有必要,每个进程只要保存一个指向这个资源的指针就可以了。避免复制的开销,如果有进程需要修改写操作该资源,那么就开始复制该资源,把这个副本提供给这个进程。
   写时复制在内核中的实现非常简单,这些页被标记为只读,并对内核页相关的数据结构实现写时复制。如果有进程试图修改某个页,就会产生缺页中断。内核处理缺页中断的处理方式就是对该页执行一次透明复制。这时会清空该页的写时复制属性,表示这个页不再被共享。

vfork()

   vfork()调用成功时,其执行结果和fork()是一样的,除了子进程会立即执行一次exec系统调用,或者调用_exit()推出。vfork()系统调用会通过挂起父进程直到子进程终止或执行新的二进制镜像,从而避免地址空间和页表拷贝。在这个过程中,父进程和子进程共享相同的地址空间和页表项,并不使用写时复制。

终止进程

   POSIX和C89都定义了一个标准函数,可以终止当前进程:

#include<stdlib.h>

void exit(int status);

   exit()的调用通常会执行一些基本的关闭步骤,然后通知内核终止这个进程。这个函数无法返回错误值——实际上它也从不返回。因此在exit()之后执行任何指令都没有意义。
   在c/c++语言中,当main()函数返回时会发生隐式exit()调用。
   如果进程收到SIGTERM和SIGKILL信号,该信号对应的处理函数是终止进程。
   还有一种进程终止方式就是内核强制终止。内核可以杀死执行非法指令,引起段错误,耗尽内存,消耗资源过多的任何进程。

僵尸进程

   一个进程已经终止了,但是它的父进程还没有获取到其状态,那么这个进程就叫做僵尸进程。僵尸进程还会消耗一些系统资源,虽然消耗很少——仅仅够描述进程之前状态的一些概要信息。保留这些概要信息主要是为了在父进程查询子进程的状态时可以提供相应的信息。一旦父进程得到了想要的信息,内核就会清除这些信息,僵尸进程就不存在了。
   只要有进程结束了,内核就会遍历它的所有子进程,并且把它们的父进程重新设为init进程(即pid为1的那个进程)。这保证了系统中不存在没有父进程的进程。init进程会周期性地等待所有子进程,确保不会有长时间存在的僵尸进程——没有幽灵进程!因此,当父进程在子进程之前结束,或者在退出前没有等待子进程,那么init进程会被指定为这些子进程的父进程,从而确保了它们最终会退出。

守护进程

   守护进程运行在后台,不与任何控制终端相关联。守护进程通常在系统启动时就运行,它们以root用户或者其他特殊的用户(例如apache和postfix)运行,并处理一些系统级的任务。习惯上守护进程的名字通常以d结尾(就像crond和sshd),但这不是必需的,甚至不是通用的。
   守护进程运行在后台,不与任何控制终端相关联。守护进程通常在系统启动时就运行,它们以root用户或者其他特殊的用户(例如apache和postfix)运行,并处理一些系统级的任务。习惯上守护进程的名字通常以d结尾(就像crond和sshd),但这不是必需的,甚至不是通用的。
   一般来讲,进程可以通过以下步骤成为守护进程。
1.调用fork(),创建新的进程,该进程将会成为守护进程。
2.在守护进程的父进程中调用exit()。这会确保父进程的父进程(即守护进程的祖父进程)在其子进程结束时会退出,保证了守护进程的父进程不再继续运行,而且守护进程不是首进程。最后一点是成功完成下一步骤的前提。
3.调用setsid(),使得守护进程有一个新的进程组和新的会话,并作为二者的首进进程。这也保证不存在和守护进程相关联的控制终端(因为守护进程刚创建了新的会话,不会给它分配一个控制终端)。
4.通过调用chdir( ),将当前工作目录改为根目录。因为守护进程是通过调用fork()创建来创建,它继承来的当前工作目录可能在文件系统中的任何地方。而守护进程往往会在系统开机状态下一直运行,我们不希望这些随机目录一直处于打开状态,导致管理员无法卸载守护进程工作目录所在的文件系统。
5.关闭所有的文件描述符。我们不希望继承任何打开的文件描述符,不希望这些描述符一直处于打开状态而自己没有发现。
6.打开文件描述符0、1和2(标准输入、标准输出和标准错误),并把它们重定向到/dev/null。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值