inetd 守护进程介绍
程序员文章站
2022-07-12 16:48:59
...
在 4.3 BSD 系统之前,很多网络服务都是单独关联了一个进程,每个进程都占据了一个进程表项,而且这些进程几乎都含有相同的启动代码,并且大部分时间都处于睡眠状态。为了简化这些问题,4.3 BSD 中提供了一个因特网超级服务器(即 inetd 守护进程),使得基于 TCP 或 UDP 的服务器都可以使用这个守护进程。下面就来看一下该进程是如何解决上述问题的。
inetd 进程在演变成守护进程后,会接着读入并处理自己的配置文件。通常是 /etc/inetd.conf 配置文件指定 inetd 处理哪些服务以及如何处理服务请求。该文件每行中一般包含如下图所示的字段(现在很多厂商为 inetd 自行增设了新的特性,比如添加了处理其他协议和处理 RPC 服务器的能力等)。
其中的 wait-flag 字段总地来说,就是指定由 inetd 启动的守护进程是否有意接管与之关联的监听套接字。由于 UDP 服务没有分离的监听套接字和接受套接字,因此几乎总是配置成 wait,而 TCP 服务中则是 nowait 更为常见。另外,当 inetd 调用 exec 执行某个服务器程序时,总是将该服务器的真实名字作为程序的第一个参数。
下面就是 inetd.conf 文件的部分示例内容。
inetd 的工作流程如下图所示。
(1)在启动阶段,为 /etc/inetd.conf 文件中指定的每个服务创建一个适当类型的套接字,并将其加入到某个 select 调用使用的描述符集中。
(2)调用 bind 为每个套接字捆绑服务器端口和通配地址。该 TCP 或 UDP 端口号是通过 getservbyname 函数获得,其参数是相应服务器在配置文件中的 service-name 和 protocol 字段。
(3)对每个 TCP 套接字调用 listen 函数以接收外来的连接请求。
(4)调用 select 等待其中任何一个套接字变为可读。TCP 监听套接字会在有一个新连接准备好时变为可读,UDP 套接字会在有一个数据报到达时变为可读。inetd 的大部分时间都花在该阶段。
(5)在某个套接字变为可读后,如果它是一个 TCP 套接字,并且其服务器的 wait-flag 值为 nowait,那就调用 accept 接受这个新连接。如果它是一个字节流套接字,则必须关闭已连接套接字,然后再次调用 select,等待下一个变为可读的套接字。
(6)inetd 守护进程调用 fork 派生一个子进程来处理服务器请求。子进程会关闭除要处理的套接字描述符之外的所有描述符,并调用 dup2 三次,以将描述符 0、1 和 2 重定向为这个待处理的套接字描述符,然后关闭该套接字描述符。因此子进程打开的描述符于是就只有 0、1 和 2,所以它从标准输入读或是往标准输出和标准错误写实际上都是针对所处理的套接字。此外,子进程还会根据它在配置文件中的 login-name 字段,调用 getpwnam 函数获取对应的保密文件表项。如果该字段值不是 root,子进程将调用 setgid 和 setuid 把自身改为指定的用户(因为 inetd 进程是以用户 ID 0 运行的,其子进程继承了这个 ID,因而能变成任何用户)。最后,子进程会调用 exec 执行由相应的 server-program 字段指定的程序来具体处理请求,相应的 server-program-auguments 字段值则作为命令行参数传递给改程序。
前面提到,典型的 TCP 服务大都指定了 nowait 标志,这意味着 inetd 不必等待某个子进程终止就可以接受该子进程所提供服务的另一个连接。当有一个新的连接在该子进程终止前到达,则父进程再次调用 select 时就可以得到这个新的连接,最终又会派生出另一个子进程来处理该连接。
而给一个数据报服务指定 wait 标志会要求 inetd 必须在这个套接字再次成为 select 的候选套接字之前等待当前服务该套接字的子进程终止,这会使父进程执行的步骤发生如下几点的变化。
(1)父进程 fork 后会保存子进程的进程 ID。这可以让父进程能通过查看 waitpid 函数返回的值来确定该子进程的终止时间。
(2)父进程通过宏 FD_CLR 关闭该套接字在 select 所用描述符集中对应的位,从而达成在之后的 select 调用中禁止该套接字的目的。这表示子进程将接管该套接字,直到自身终止为止。
(3)父进程会在子进程终止时收到一个 SIGCHLD 信号,然后其信号处理函数将取得该子进程的进程 ID,最后再打开相应套接字在 select 所用描述符集中对应的位,使其重新成为 select 的候选套接字。
因为一般每个数据报服务器只有一个套接字,所以数据报服务器通常都需要接管其套接字直至自身终止,以防 inetd 在此期间让 select 检查该套接字的可读性。如果 inetd 不关闭对某个数据报套接字的可读性检查,而且父进程(inetd)先于服务该套接字的子进程执行,则会由于引发本次 fork 的那个数据报仍然在套接字接收缓冲区中,从而导致 select 再次返回可读条件,致使 inetd 再次 fork 一个多余的子进程,所以 inetd 必须在得知子进程已从套接字接收队列中读走该数据报之前忽略这个数据报套接字。
inetd 通常不适用于服务密集型服务器,比如邮件服务器和 Web 服务器等。另外,在 Linux 等系统上,称为 xinetd 的扩展式因特网服务守护进程业已常见,它提供了更多的其他特性,包括根据客户的地址登记、接受或拒绝连接的选项、每个服务一个配置文件的做法等等,但其背后的基本超级服务器概念和 inetd 是一样的。
参考书籍:
1、《UNIX 网络编程卷 1》第 13 章——守护进程和 inetd 超级服务器。
inetd 进程在演变成守护进程后,会接着读入并处理自己的配置文件。通常是 /etc/inetd.conf 配置文件指定 inetd 处理哪些服务以及如何处理服务请求。该文件每行中一般包含如下图所示的字段(现在很多厂商为 inetd 自行增设了新的特性,比如添加了处理其他协议和处理 RPC 服务器的能力等)。
其中的 wait-flag 字段总地来说,就是指定由 inetd 启动的守护进程是否有意接管与之关联的监听套接字。由于 UDP 服务没有分离的监听套接字和接受套接字,因此几乎总是配置成 wait,而 TCP 服务中则是 nowait 更为常见。另外,当 inetd 调用 exec 执行某个服务器程序时,总是将该服务器的真实名字作为程序的第一个参数。
下面就是 inetd.conf 文件的部分示例内容。
$ cat /etc/inetd.conf ftp stream tcp nowait root /usr/bin/ftpd ftpd -l telnet stream tcp nowait root /usr/bin/telnetd telnetd tftp dgram udp wait nobody /usr/bin/tftpd tftpd -s /tftpboot
inetd 的工作流程如下图所示。
(1)在启动阶段,为 /etc/inetd.conf 文件中指定的每个服务创建一个适当类型的套接字,并将其加入到某个 select 调用使用的描述符集中。
(2)调用 bind 为每个套接字捆绑服务器端口和通配地址。该 TCP 或 UDP 端口号是通过 getservbyname 函数获得,其参数是相应服务器在配置文件中的 service-name 和 protocol 字段。
(3)对每个 TCP 套接字调用 listen 函数以接收外来的连接请求。
(4)调用 select 等待其中任何一个套接字变为可读。TCP 监听套接字会在有一个新连接准备好时变为可读,UDP 套接字会在有一个数据报到达时变为可读。inetd 的大部分时间都花在该阶段。
(5)在某个套接字变为可读后,如果它是一个 TCP 套接字,并且其服务器的 wait-flag 值为 nowait,那就调用 accept 接受这个新连接。如果它是一个字节流套接字,则必须关闭已连接套接字,然后再次调用 select,等待下一个变为可读的套接字。
(6)inetd 守护进程调用 fork 派生一个子进程来处理服务器请求。子进程会关闭除要处理的套接字描述符之外的所有描述符,并调用 dup2 三次,以将描述符 0、1 和 2 重定向为这个待处理的套接字描述符,然后关闭该套接字描述符。因此子进程打开的描述符于是就只有 0、1 和 2,所以它从标准输入读或是往标准输出和标准错误写实际上都是针对所处理的套接字。此外,子进程还会根据它在配置文件中的 login-name 字段,调用 getpwnam 函数获取对应的保密文件表项。如果该字段值不是 root,子进程将调用 setgid 和 setuid 把自身改为指定的用户(因为 inetd 进程是以用户 ID 0 运行的,其子进程继承了这个 ID,因而能变成任何用户)。最后,子进程会调用 exec 执行由相应的 server-program 字段指定的程序来具体处理请求,相应的 server-program-auguments 字段值则作为命令行参数传递给改程序。
前面提到,典型的 TCP 服务大都指定了 nowait 标志,这意味着 inetd 不必等待某个子进程终止就可以接受该子进程所提供服务的另一个连接。当有一个新的连接在该子进程终止前到达,则父进程再次调用 select 时就可以得到这个新的连接,最终又会派生出另一个子进程来处理该连接。
而给一个数据报服务指定 wait 标志会要求 inetd 必须在这个套接字再次成为 select 的候选套接字之前等待当前服务该套接字的子进程终止,这会使父进程执行的步骤发生如下几点的变化。
(1)父进程 fork 后会保存子进程的进程 ID。这可以让父进程能通过查看 waitpid 函数返回的值来确定该子进程的终止时间。
(2)父进程通过宏 FD_CLR 关闭该套接字在 select 所用描述符集中对应的位,从而达成在之后的 select 调用中禁止该套接字的目的。这表示子进程将接管该套接字,直到自身终止为止。
(3)父进程会在子进程终止时收到一个 SIGCHLD 信号,然后其信号处理函数将取得该子进程的进程 ID,最后再打开相应套接字在 select 所用描述符集中对应的位,使其重新成为 select 的候选套接字。
因为一般每个数据报服务器只有一个套接字,所以数据报服务器通常都需要接管其套接字直至自身终止,以防 inetd 在此期间让 select 检查该套接字的可读性。如果 inetd 不关闭对某个数据报套接字的可读性检查,而且父进程(inetd)先于服务该套接字的子进程执行,则会由于引发本次 fork 的那个数据报仍然在套接字接收缓冲区中,从而导致 select 再次返回可读条件,致使 inetd 再次 fork 一个多余的子进程,所以 inetd 必须在得知子进程已从套接字接收队列中读走该数据报之前忽略这个数据报套接字。
inetd 通常不适用于服务密集型服务器,比如邮件服务器和 Web 服务器等。另外,在 Linux 等系统上,称为 xinetd 的扩展式因特网服务守护进程业已常见,它提供了更多的其他特性,包括根据客户的地址登记、接受或拒绝连接的选项、每个服务一个配置文件的做法等等,但其背后的基本超级服务器概念和 inetd 是一样的。
参考书籍:
1、《UNIX 网络编程卷 1》第 13 章——守护进程和 inetd 超级服务器。
上一篇: SCTP 事件通知
下一篇: 获取和设置套接字选项