Unix 环境高级编程(APUE) 之 七 进程关系 和 守护进程

来源:互联网 发布:sql限制返回条数 编辑:程序博客网 时间:2024/06/06 02:27

部分转载自 http://www.cnblogs.com/chuyuhuashi/p/4440944.html

shell是什么?

  shell是系统的用户界面,它提供了用户与内核交互的接口。它接收用户输入的命令并把它送入内核执行,再将执行结果显示给用户。尽管近十年来个人计算机使用的接口普遍从字符型的命令行界面转向图形用户界面,并且Linux本身在图形化环境方面也做了很大改进,但在UNIX/Linux操作系统领域,shell依然是众多系统管理员首选的操作工具。
什么是Linux终端?
  Linux终端也称为虚拟控制台,是Linux从UNIX继承来的标准特性。显示器和键盘合称为终端,因为它们可以对系统进行控制,所以又称为控制台,一台计算机的输入/输出设备就是一个物理的控制台。如果在一台计算机上用软件的方法实现了多个互不干扰、独立工作的控制台界面,就是实现了多个虚拟控制台。Linux终端采用字符命令行方式工作,用户通过键盘输入命令,通过Linux终端对系统进行控制。通常情况下,Linux默认启动6个虚拟终端。如果启动方式选择直接启动X Window,那么X Window在第7个虚拟终端上。
  虚拟控制台的选择可以通过按下Alt键和功能键Fn(n=1~6)来实现。例如,用户登录后,按Alt+F2组合键,用户又可以看到“Login:”提示符,此时看到的是第二个虚拟控制台。如果此时再按Alt+F1组合键,就可以回到第一个虚拟控制台。一个新安装的Linux系统允许用户使用Alt+F1到Alt+F6组合键来访问6个虚拟控制台。
  虚拟控制台使得Linux成为一个真正的多用户操作系统。在不同的控制台上,可以同时接受多个用户登录,也允许一个用户进行多次登录。用户可以在某一个虚拟控制台上的工作尚未结束时,切换到另一虚拟控制台开始另一项工作。例如,开发软件时,可以在一个控制台上进行编辑,在另一个控制台上进行编译,在第三个控制台上查阅信息。
  在X Window图形操作界面中按Alt+Ctrl+Fn组合键(n=1~6)就可以进入控制台字符操作界面。这就意味着用户可以同时拥有X Window以及6个控制台操作界面,在控制台操作界面中按Alt+Ctrl+F7组合键即可回到刚才的X Window图形操作界面。也就是说,用Alt+Ctrl+Fn组合键即可实现字符界面与X Window界面的快速切换。
左图shell,右图终端tty2
          


 

进程关系是《APUE》第三版的第九章,这章的内容对我们来说通常意义不大,因为它终篇贯穿着一个概念,叫做“终端”,而现在已经几乎无法见到真正的终端了。

但是不了解这章就无法解释第13章(守护进程)的内容,大家要了解终端和进程之间关系才能了解守护进程是个什么东西。

进程关系与守护进程这两章内容不多,而且有诸多关联,所以我们把这两章的内容放到一起讨论。

先来看看几个概念。

1.终端登录

真正意义上的终端是“笨设备”,只能接收命令的输入并返回结果。你问它 1+1=? 它也不知道,它只能把你的问题传给计算机,再把计算机返回的结果显示给你。

它出现在计算机既昂贵又庞大的年代。那时候的计算机昂贵到了只有一部分公司买得起、另一部分公司买不起,而且有些公司只能买一台,买第二台就要破产了的程度。

所以这么昂贵的设备如果只能给一个人使用太浪费了,于是为了让计算机可以被多人使用,就出现了终端这种设备。

 

接下来我们简单的聊聊 Linux 是如何使用户登录的。

图1 Linux 用户登录过程

如图1 所示,内核自举时创建 1 号 init 进程,init 对每一个终端执行 fork(2) + exec(3) + getty(1) 命令,getty(1) 命令的作用是要求用户输入用户名。

等待用户输入完成用户名后,getty(1) 会 exec(3) + login(1)。

login(1) 命令首先根据用户名通过 getpwnam(3) 函数得到用户的口令文件登录项,然后调用 getpass(3) 函数以屏蔽回显的形式获得用户的密码,最后再通过 crypt(3) 函数将加密后的用户口令与阴影口令文件用户登录项中的 pw_passwd 字段相比较,认证失败几次之后就会回到上图的第一步,init 进程将重新执行 fork(2) + exec(3) + getty(1)。

如果认证成功则启动用户登录 shell,并使用 chown(2) 更改当前登录终端的所有权,使登录用户成为它的所有者。登录之前的步骤都是 root 身份,所以真正用户权限被降下来就是在这个时候发生的。

当然 login 要做的事情不仅仅只有这点儿,它还要做许多其它需要为用户初始化的事情。

说句题外话,大家注意到了没有,如果获取用户名或密码的时候函数的编写者敢使用类似 scanf(3) 这样的函数读取用户输入,则很容易遭受缓冲区溢出攻击。

2.进程组

进程组用来承载进程,一个进程组中有一个或多个进程,它是一个或多个进程的集合(也可以看作是容器)。一个进程不但拥有唯一的 PID,同时也属于一个进程组。

如何产生一个进程组呢?

由shell的管道将几个进程编为一组。

子进程继承父进程的进程组ID。

3.会话(Session)

     

一次成功的终端登录就是一个会话。会话相当于是进程组的容器,它能承载一个或多个进程组。

总结来说:会话用来承载进程组,进程组用来承载进程,进程用来承载线程。

setsid(2)

创建一个会话并设置进程组的ID。这个函数是我们在第 9 章最有价值的函数,没有这个函数,我们后面就无法创建守护进程。

调用者不能是进程组组长,调用者在调用之后自动变为新进程组组长,并且脱离控制终端,进程 ID 将被设为进程组 ID 和会话 ID,所以守护进程通常 PID、PGID、SID 是相同的。通常的用法是父进程 fork(2) 一个子进程,然后子进程调用 setsid(2) 将自己变成守护进程,父进程退出即可。

4 控制终端

一个会话可以有一个控制终端。

建立与控制终端连接的会话首进程是控制进程。

一个会话中的进程组分为前台进程组和后台进程组。


什么是前台进程组呢?比如你正在使用 tar 命令进行打包的时候是无法再输入其它命令的。如果 tar 命令执行的时间很长,我们就会在命令后面添加一个 & 参数,把它放到后台去运行。
ps(1) 命令的 SID(Session ID)列 就是程序运行的会话 ID。
进程是先出现的,后来人们发现进程可以拆分为多个小任务分别执行,于是便出现了线程的概念,这个到后面线程的章节会详细讨论。

如今进程已经退化为容器了,它的存在就是为了承载线程。PID 看似是进程号,实际上是线程在消耗它。??

进程和线程只是我们的说法,内核中只能看到线程,内核所谓的进程管理其实就是线程管理,内核永远以线程为单位执行任务。

登录时,自动建立控制终端,从Session和进程组的角度重新来看登录和执行命令的过程:

(1). getty或telnetd进程在打开终端设备之前调用setsid函数创建一个新的Session,该进程称为Session Leader,该进程的id也可以看作Session的id,然后该进程打开终端设备作为这个Session中所有进程的控制终端。在创建新Session的同时也创建了一个新的进程组,该进程是这个进程组的Process Group Leader,该进程的id也是进程组的id。

        (2). 在登录过程中,getty或telnetd进程变成login,然后变成Shell,但仍然是同一个进程,仍然是Session Leader。

5 作业控制

(1).允许在一个终端上起动多个作业(进程组),控制哪一个作业可以存取该终端,以及哪些作业在后台运行。作业控制要求三种形式的支持:

(a).支持作业控制的shell。

(b).内核中的终端驱动程序必须支持作业控制。

(c).必须提供对某些作业控制信号的支持。

三个特殊字符可使终端驱动程序产生信号,并将它们送至前台进程组,它们是:

• 中断字符(一般采用DELETE或Ctrl-C)产生SIGINT。

• 退出字符(一般采用Ctrl-\)产生SIGQUIT。

• 挂起字符(一般采用Ctrl-Z)产生SIGTSTP

(2)不支持作业控制的Shell

对于不支持作业控制的Shell,例如bsh,它的命令和它自身的进程处于同一个会话和前台进程组。在后台执行的命令(&)和管道命令的进程依然和Shell是同一个进程组。

如果一个后台进程试图取走终端,例如cat > temp &。在有作业控制时,后台作业被放在后台进程组中。如果后台作业试土读控制终端,则会产生信号SIGTTIN。在没有作业控制时,其处理方法是如果该进程自己没有重定向标准输入,则Shell会自动将标准输入重定向到/dev/null。读/dev/null则会产生一个EOF让cat读到文件末尾,正常结束。


5.守护进程

守护进程的栗子我就不写了,因为《APUE》第三版 P375 图13-1 已经有详细的代码了,我针对书上的栗子总结一下。

守护进程的特点:

1)脱离控制终端,ps(1) axj tty 为问号(?)。

2)是进程组的 leader,也就是 PID 和 PGID 相同。

3)通常没有父进程,由 1 号  init 接管。

4)创建了一个新会话,是 session 的 leader,所以 PID 与 SID 相同。

使用 ps(1) axj 命令查看,PID、PGID、SID 相同的进程就是守护进程。

守护进程也可以使用标准输出,但是不符合常理了,因为守护进程没有控制终端,所以守护进程一般会关闭或重定向标准输入输出流。

 写守护进程的时候我们会切换工作路径,把它切换到一个一定会存在的路径,比如 /。因为假设你的守护进程是在一个可卸载设备(如U盘)上被启动的,如果不修改工作路径,该设备无法被卸载。

调用 umask(2) 是为了将文件模式创建掩码设置为一个已知值,因为通过继承得来的掩码可能会被设置为拒绝某些权限,如果守护进程中需要这些权限则要设置它。

对于书上的栗子,有两点要吐槽:

1)SIGHUP 信号用于通知服务进程软重启,比如修改了某服务的配置文件之后可以通过给服务进程发 SIGHUP 信号使它重新读取配置文件,所以如果没有特殊要求不必忽略该信号。

2)如果没有特殊要求,不必关闭所有的文件描述符,仅关闭标准输入、标准输出和标注错误即可。

 

6.系统日志

守护进程不应使用标准输出,那么当守护进程需要记录一些事件或者是错误的时候怎么办呢?那就要采用系统日志了。

系统日志一般保存在 /var/log/ 目录下,但是这个目录下的日志文件权限几乎都是只有 root 才能读写,那么普通用户的日志如何写入呢?这就需要借助系统日志函数来写日志了。

root 用户授权给 syslogd 服务专门写日志,然后其它程序都需要通过封装好的一系列函数调用 syslogd 服务来记录日志。这样就提高了日志的安全性了,可以防止日志文件被非法篡改。

openlog(3) 函数并非是打开日志文件,而是与 syslogd 服务建立链接,表示当前进程要写日志。

参数列表:

  ident:表明自己的身份,由程序员自行指定,写什么都行。

  option:要在日志中附加什么内容,多个选项用按位或链接。LOG_PID 是附加 PID,这个是最常用的。

  facility:消息来源。一般只能指定一个。

表1 facility 参数的常见需选项

syslog(3) 函数用于提交日志内容,参数列表:

  priority:优先级。详见下表:

表2 日志优先级

以 LOG_ERR 为分界线,如果遇到了程序无法继续运行的问题,要报 LOG_ERR 以上的级别(包括 LOG_ERR)。

如果遇到的问题不会影响程序继续运行,报 LOG_ERR 以下级别的就可以了。

日志太多肯定对磁盘空间的要求就比较高,而且无用的日志太多会影响日志审计。日志文件中会记录哪些级别的日志是在配置文件中配置的,默认的情况是 LOG_DEBUG 以上级别的日志都会被记录。

  format:类似于 printf(3) 函数的格式化字符串。注意不要使用转义字符 \n,否则日志中会记录一个字符串"\n"而不是记录一个换行符。

  ...:format 中占位符的参数。

closelog(3) 表示日志写入结束。

 

7.单实例守护进程

有些守护进程需要在同一时间只能有一个实例在运行,它们称为单实例守护进程。它们在启动的时候会在 /var/run 下面创建一个进程锁文件,守护进程启动的时候会先判断这个锁文件是否存在,如果已存在就报错并退出,如果不存在就继续运行并创建一个锁文件,退出的时候再删除它。

守护进程如果想要开机自动启动,可以配置到自动启动脚本中:/etc/rc.d/rc.local


0 0
原创粉丝点击