资源描述
BSD rc.d脚脚脚本本本编编编程程程实实实战战战Yar TikhiyyarFreeBSD.org版版版权权权 2005,2006 The FreeBSD Project$FreeBSD:doc/zh_CN.GB2312/articles/rc-scripting/article.sgml,v 1.3 2010/01/2013:28:06 loader Exp$FreeBSD 是是是FreeBSD基基基金金金会会会的的的注注注册册册商商商标标标NetBSD是是是NetBSD Foundation的的的注注注册册册商商商标标标。许许许多多多制制制造造造商商商和和和经经经销销销商商商使使使用用用一一一些些些称称称为为为商商商标标标的的的图图图案案案或或或文文文字字字设设设计计计来来来彰彰彰显显显自自自己己己的的的产产产品品品。本本本文文文档档档中中中出出出现现现的的的,为为为FreeBSDProject 所所所知知知晓晓晓的的的商商商标标标,后后后面面面将将将以以以 或或或 符符符号号号来来来标标标注注注。初学者可能会发现,难以通过正式的文档,基于BSD 的rc.d框架,编写一些实际任务的rc.d脚本。本文中,我们采用了一些复杂性不断增加的典型案例,来展示适合每个案例的rc.d特性,并探讨其中的工作原理。这样的实验为大家进一步研究设计有效的rc.d应用程序提供了一些参考点。目目目录录录1 简简简介介介.12 任任任务务务描描描述述述.23 虚虚虚拟拟拟的的的脚脚脚本本本.34 可可可配配配置置置的的的虚虚虚拟拟拟脚脚脚本本本.45 启启启动动动并并并停停停止止止简简简单单单守守守护护护进进进程程程.66 启启启动动动并并并停停停止止止高高高级级级守守守护护护进进进程程程.77 链链链接接接脚脚脚本本本到到到rc.d 框框框架架架.108 给给给予予予rc.d 脚脚脚本本本更更更多多多的的的灵灵灵活活活性性性.139 进进进一一一步步步阅阅阅读读读.141 简简简介介介历史上BSD 曾有过一个单一的启动脚本,/etc/rc。该脚本在系统启动的时候被init(8)程序所引导,并执行所有多用户操作所需求的用户级任务:检查并挂载文件系统,设置网络,启动守护进程,等等。在每个系统中实际的任务清单也并不相同;管理员需要根据需求自定义这样的任务清单。在一些特殊的情况中,还不得不去修改/etc/rc文件,一些真正的黑客乐此不疲。1BSD rc.d脚本编程实战单一脚本启动方法的真正问题是它没有提供对从/etc/rc启动的单个组件的控制。拿一个例子来说吧,/etc/rc不能够重新启动某个单独的守护进程。系统管理员不得不手动找出守护进程,并杀掉它,等待它真正退出后,再通过浏览/etc/rc得到该守护进程的标识,最终输入全部命令来再次启动守护进程。如果重新启动的服务包括不止一个守护进程或需要更多动作的话,该任务将变得更加困难以及容易出错。简而言之,单一脚本在实现我们这样的目的上是不成功的:让系统管理员的生活更轻松。再后来,为了将最重要的一些子系统独立出来,便尝试将部分的内容从/etc/rc分离出来了。最广为人知的例子就是用来启动联网的/etc/netstart文件。它容许从单用户模式访问网络,但由于它的部分代码需要和一些与联网完全无关的动作交互,所以它并没有完美地结合到自启动的进程中。那便是为何/etc/netstart被演变成/etc/work的原因了。后者不再是一个普通的脚本;它包括了庞大的,由/etc/rc在不同的系统启动级别中调用的凌乱的sh(1)函数。然而,当启动任务变得多样化以及久经更改,“类模块化”方法变得比曾经的整体/etc/rc更缓慢费事。由于没有一个干净和易于设计的框架,启动脚本不得不全力更改以满足飞速开发中基于BSD 的操作系统的需求。它逐渐变得明朗并经过许多必要的步骤最终变成一个具有细密性和扩展性的rc系统。BSDrc.d就这样诞生了。Luke Mewburn 和NetBSD 社区是公认的rc.d之父。再之后它被引入到了FreeBSD 中。它的名字引用为系统单独的服务脚本的位置,也就是/etc/rc.d下面的那些脚本。之后我们将学习到更多的rc.d系统的组件并看看单个脚本是如何被调用的。BSDrc.d背后的基本理念是良良良好好好 的模块化和代码重用性。良良良好好好 的模块化意味着每个基本“服务”就象系统守护进程或原始启动任务那样,通过属于它们的可启动该服务的sh(1)脚本,来停止服务,重载服务,检查服务的状态。具体动作由脚本的命令行参数所决定。/etc/rc脚本仍然掌管着系统的启动,但现在它仅仅是使用start参数来一个个调用那些小的脚本。这便于用stop来对运行中的同样的脚本很好地执行停止任务,这是被/etc/rc.shutdown脚本所完成的。看,这是多么好地体现了Unix 的哲学:拥有一组小的专用的工具,每个工具尽可能好地完成自己的任务。代代代码码码重重重用用用 意味着所有的通用操作由/etc/rc.subr中的一些sh(1)函数所实现。现在一个典型的脚本只需要寥寥几行的sh(1)代码。最终,rcorder(8)成为了rc.d框架中重要的一部分,它用来帮助/etc/rc处理小脚本之间的依赖关系并有次序地运行它们。它同样帮助/etc/rc.shutdown做类似的事情,因为正确的关闭次序是相对于启动的次序的。BSDrc.d的设计在 Luke Mewburn 的原文 中有记录,以及rc.d组件也被充分详细地记录在各自的联机手册 中。然而,它可能没能清晰展现给一个rc.d新手,如何将无数的块和片进行关联来为具体的任务创建一个好风格的脚本。因此本文将试着以不同的方式来讲述rc.d。它将展示在某些典型情况中应该使用哪些特性,并阐述了为何如此。注意这并不是一篇how-to 文档,我们的目的不是给出现成的配方,而是在展示一些简单的进入rc.d的范围的门路。本文也不是相关联机手册的替代品。阅读本文时记得同时参考联机手册以获取更完整正规的文档。理解本文需要一些先决条件。首先,你需要熟悉sh(1)脚本编程语言以掌握rc.d,还有,你需要知道系统是如何执行用户级的启动和停止任务,这些在rc(8)中都有说明。本文关注的是rc.d的FreeBSD 分支。不过,它可能对NetBSD 的开发者也同样有用,因为BSDrc.d的两个分支不只是共享了同样的设计,还保留了对脚本编写者都可见的类似观点。2 任任任务务务描描描述述述在开始打开$EDITOR(编辑器)之前进行小小的思考不是坏事。为了给一个系统服务写一个“听话的”rc.d脚本,我们首先应该能回答以下问题:该服务是必须性的还是可选性的?脚本将为单个程序服务,如一个守护进程,还是执行更复杂的动作?2BSD rc.d脚本编程实战我们的服务依赖哪些服务?反过来哪些服务依赖我们的服务?从下面的例子中我们将看到,为什么说知道这些问题的答案是很重要的。3 虚虚虚拟拟拟的的的脚脚脚本本本下面的脚本是用来在每次系统启动时发出一个信息:#!/bin/sh./etc/rc.subrname=dummystart_cmd=$name_startstop_cmd=:dummy_start()echo Nothing started.load_rc_config$namerun_rc_command$1需要注意的是:一个解释性的脚本应该以一行魔幻的“shebang”行开头。该行指定了脚本的解析程序。由于shebang行的作用,假如再有可执行位的设置,脚本就能象一个二进制程序一样被精确地调用执行。(请参考chmod(1)。)例如,一个系统管理员可以从命令行手动运行我们的脚本:#/etc/rc.d/dummy start注注注意意意:为了使rc.d框架正确地管理脚本,它的脚本需要用sh(1)语言编写。如果你的某个服务或port 套件使用了二进制控制程序或是用其它语言编写的例程,请将其组件安装到/usr/sbin(相对于系统)或/usr/local/sbin(相对于ports),然后从合适的rc.d目录的某个sh(1)脚本调用它。提提提示示示:如果你想知道为什么rc.d脚本必须用sh(1)语言编写的细节,先看下/etc/rc是如何依靠run_rc_script调用它们,然后再去学习/etc/rc.subr下run_rc_script的相关实现。在/etc/rc.subr下,有许多定义过的sh(1)函数可供每个rc.d脚本使用。这些函数在rc.subr(8)中都有说明。尽管理论上可以完全不使用rc.subr(8)来编写一个rc.d脚本,但它的函数已经证明了它真的很方便,并且能使任务更加的简单。所以所有人在编写rc.d脚本时都会求助于rc.subr(8)也不足为奇了。当然我们也不例外。一个rc.d脚本在其调用rc.subr(8)函数之前必须先“source”/etc/rc.subr(用“.”将其包含进去),而使sh(1)程序有机会来获悉那些函数。首选风格是在脚本的最开始source/etc/rc.subr文件。注注注意意意:某些有用的与联网有关的函数由另一个被包含进来的文件提供,/etc/network.subr文件。3BSD rc.d脚本编程实战强制的变量name指定我们脚本的名字。这是rc.subr(8)所强调的。也就是,每个rc.d脚本在调用rc.subr(8)的函数之前必须设置name变量。现在是时候来为我们的脚本一次性选择一个独一无二的名字了。在编写这个脚本的时我们将在许多地方用到它。在开始之前,我们来给脚本文件也取个相同的名字。注注注意意意:当前的rc.d脚本风格是把值放在双引号中来给变量赋值。请记住这只是个风格问题,可能并不总是这样。你可以在只是简单的并不包括sh(1)元字符的词句中放心地省略掉引号,而在某些情况下你将需要使用单引号以防止sh(1)对任何的变量的解释。程序员是可以灵巧地由风格惯例获悉其语法以及使用的。rc.subr(8)背后主要的构思是rc.d脚本提供处理程序,或者方法,来让rc.subr(8)调用。特别是,start,stop,以及其它的rc.d脚本参数都是这样被处理的。方法是存储在一个以argument_cmd形式命名的变量中的sh(1)表达式,该argument对应着脚本命令行中所特别指定的参数。我们稍后将看到rc.subr(8)是如何为标准参数提供默认方法的。注注注意意意:为了让rc.d中的代码更加统一,常见的是在任何适合的地方都使用$name形式。这样一来,可以轻松地将一些代码从一个脚本拷贝到另一个中使用。我们应谨记rc.subr(8)为标准参数提供了默认的方法。因此,如果希望它什么都不做的话,我们必须使用无操作的sh(1)表达式来改写标准的方法。比较复杂的方法主体可以用函数来实现。在能够保证函数名有意义的情况下,这是个很不错的想法。重重重要要要:强烈推荐给我们脚本中所定义的所有函数名都添加类似$name这样的前缀,以使它们永远不会和rc.subr(8)或其它公用包含文件中的函数冲突。这是在请求rc.subr(8)载入rc.conf(5)变量。尽管我们这个脚本中使用的变量并没有被其它地方使用,但由于rc.subr(8)自身所控制着的rc.conf(5)变量存在的原因,仍然推荐脚本去装载rc.conf(5)。通常这是rc.d脚本的最后一个命令。它调用rc.subr(8)体系使用我们脚本所提供的变量和方法来执行相应的请求动作。4 可可可配配配置置置的的的虚虚虚拟拟拟脚脚脚本本本现在我们来给我们的虚拟脚本增加一些控制参数吧。正如你所知,rc.d脚本是由rc.conf(5)所控制的。幸运的是,rc.subr(8)隐藏了所有复杂化的东西。下面这个脚本使用rc.conf(5)通过rc.subr(8)来查看它是否在第一个地方被启用,并获取一条信息在启动时显示。事实上这两个任务是相互独立的。一方面,rc.d脚本要能够支持启动和禁用它的服务。另一方面,rc.d脚本必须能具备配置信息变量。我们将通过下面同一脚本来演示这两方面的内容:#!/bin/sh./etc/rc.subr4BSD rc.d脚本编程实战name=dummyrcvar=set_rcvarstart_cmd=$name_startstop_cmd=:load_rc_config$nameeval$rcvar=$rcvar:-NOdummy_msg=$dummy_msg:-Nothing started.dummy_start()echo$dummy_msgrun_rc_command$1在这个样例中改变了什么?变量rcvar指定了ON/OFF 开关变量的名字。包含来自rc.subr(8)中变量名是为了调用不同操作系统所采用的不同规定的set_rcvar。也就是说,FreeBSD 坚持使用$name_enable样式而NetBSD的rc.conf(5)中使用的只是$name变量的形式。例如,我们在FreeBSD 中使用dummy_enable来控制我们的脚本而在NetBSD 中使用dummy来控制。现在load_rc_config在任何rc.conf(5)变量被访问之前就在脚本中被预先调用。注注注意意意:检查rc.d脚本时,切记sh(1)会把函数延迟到其被调用时才对其中的表达式进行求值运算。因此尽可能晚地在run_rc_command之前调用load_rc_config,以及仍然访问从方法函数输出到run_rc_command的rc.conf(5)变量并不是一个错误。这是因为方法函数将在load_rc_config之之之后后后,被调用的run_rc_command调用。如果自身设置了rcvar,但指示开关变量却未被设置,那么run_rc_command将发出一个警告。如果你的rc.d脚本是为基本系统所用的,你应当在/etc/defaults/rc.conf中给开关变量添加一个默认的设置并将其标注在rc.conf(5)中。否则的话你的脚本应该给开关变量提供一个默认设置。范例中演示了一个可移植接近于后者情况的案例。注注注意意意:你可以通过将开关变量设置为ON 来使rc.subr(8)有效,使用one或force为脚本的参数加前缀,如onestart或forcestop这样,会忽略其当前的设置。切记force在我们下面要提到的情况下有额外的危险后果,那就是当用one改写了ON/OFF 开关变量。例如,假定dummy_enable是OFF的,而下面的命令将忽略系统设置而强行运行start方法:#/etc/rc.d/dummy onestart现在启动时显示的信息不再是硬编码在脚本中的了。它是由一个命名为dummy_msg的rc.conf(5)变量所指定的。这就是rc.conf(5)变量如何来控制rc.d脚本的一个小例子。重重重要要要:我们的脚本所独占使用的所有rc.conf(5)变量名,都必须具有同样的前缀:$name。例如:dummy_mode,dummy_state_file,等等。5BSD rc.d脚本编程实战注注注意意意:当可以内部使用一个简短的名字时,如msg这样,为我们的脚本所引进的全部的共用名添加唯一的前缀$name,能够避免我们与rc.subr(8)命名空间冲突的可能。只要一个rc.conf(5)变量与其内部等同值是相同的,我们就能够使用一个更加兼容的表达式来设置默认值::$dummy_msg:=Nothing started.尽管目前的风格是使用了更详细的形式。通常,基本系统的rc.d脚本不需要为它们的rc.conf(5)变量提供默认值,因为默认值应该是在/etc/defaults/rc.conf设置过了。但另一方面,为ports 所用的rc.d脚本应提供如范例所示的默认设置。这里我们使用dummy_msg来实际地控制我们的脚本,即,发一个变量信息。5 启启启动动动并并并停停停止止止简简简单单单守守守护护护进进进程程程我们早先说过rc.subr(8)是能够提供默认方法的。显然,这些默认方法并不是太通用的。它们都是适用于大多数情况下来启动和停止一个简单的守护进程况。我们来假设现在需要为一个叫做mumbled的守护进程编写一个rc.d脚本,在这里:/para#!/bin/sh./etc/rc.subrname=mumbledrcvar=set_rcvarcommand=/usr/sbin/$nameload_rc_config$namerun_rc_command$1感到很简单吧,不是么?我们来检查下我们这个小脚本。只需要注意下面的这些新知识点:这个command变量对于rc.subr(8)来说是有意义的。当它被设置时,rc.subr(8)将根据提供传统守护进程的情形而生效。特别是,将为这些参数提供默认的方法:start,stop,restart,poll,以及status。该守护进程将会由运行中的$command配合由$mumbled_flags所指定的命令行标帜来启动。因此,对默认的start方法来说,所有的输入数据在我们脚本变量集合中都可用。与start不同的是,其他方法可能需要与进程启动相关的额外信息。举个例子,stop必须知道进程的PID 号来终结进程。在目前的情况中,rc.subr(8)将扫描全部进程的清单,查找一个名字等同于$procname的进程。后者是另一个对rc.subr(8)有意义的变量,并且默认它的值跟command一样。换而言之,当我们给command设置值后,procname实际上也设置了同样的值。这启动我们的脚本来杀死守护进程并检查它是否正在第一个位置运行。注注注意意意:某些程序实际上是可执行的脚本。系统启动脚本的解释器以传递脚本名为命令行参数的形式来运行脚本。然后被映射到进程列表中,这会使rc.subr(8)迷惑。因此,当$command是一个脚本的时,你应该额外地设置command_interpreter来让rc.subr(8)知晓进程的实际名字。6BSD rc.d脚本编程实战对每个rc.d脚本而言,有一个可选的rc.conf(5)变量给command指示其优先级。它的名字是下面这样的形式:$name_program,name是我们之前 讨论过的必须性变量。如,在这个案例中它应该命名为emumbled_program。这其实是rc.subr(8)分配$name_program来改写command的。当然,即使command未被设置,sh(1)也将允许你从rc.conf(5)或自身来设置$name_program。在那种情况下,$name_program的特定属性丢失了,并且它成为了一个能供你的脚本用于其自身目的的普通变量。然而,单独使用$name_program是并不是我们所寄望的,因为同时使用它和command已成为了rc.d脚本编程的一个惯用的约定。关于默认方法的更详细的信息,请参考rc.subr(8)。6 启启启动动动并并并停停停止止止高高高级级级守守守护护护进进进程程程我们来给之前的“骨架”脚本加点“血肉”,并让它更复杂更富有特性吧。默认的方法已能够为我们做很好的工作了,但是我们可能会需要它们一些方面的调整。现在我们将学习如何调整默认方法来符合我们的需要。#!/bin/sh./etc/rc.subrname=mumbledrcvar=set_rcvarcommand=/usr/sbin/$namecommand_args=mock arguments /dev/null 2&1pidfile=/var/run/$name.pidrequired_files=/etc/$name.conf/usr/share/misc/$name.rulessig_reload=USR1start_precmd=$name_prestartstop_postcmd=echo Bye-byeextra_commands=reload plugh xyzzyplugh_cmd=mumbled_plughxyzzy_cmd=echo Nothing happens.mumbled_prestart()if checkyesno mumbled_smart;thenrc_flags=-o smart$rc_flags(10)ficase$mumbled_mode infoo)rc_flags=-frotz$rc_flags;7BSD rc.d脚本编程实战bar)rc_flags=-baz$rc_flags;*)warn Invalid value for mumbled_mode(11)return 1(12);esacrun_rc_command xyzzy(13)return 0mumbled_plugh()(14)echo A hollow voice says plugh.load_rc_config$namerun_rc_command$1附加给$command的参数在command_args中进行传递。它们在$mumbled_flags之后将被添加到命令行。其实际的执行便是此后最终的命令行传递给eval运算,输入和输出以及重定向都可以在command_args中指定。注注注意意意:永永永远远远不不不要要要 在command_args包含破折号选项,类似-X或-foo这样的。command_args的内容将出现在最终命令行的末尾,因此它们可能是紧接在$name_flags中所列出的参数后面;但大多的命令将不能识别出普通参数后的破折号选项。更好的传递附加给$command的选项的方式是添加它们到$name_flags的起始处。另一种方法是像后文所示的那样来修改rc_flags。一个得体的守护进程会创建一个pidfile 进程文件,以使其进程能够更容易更可靠地被找到。如果设置了pidfile变量,告诉rc.subr(8)哪里能找到供其默认方法所使用的pidfile进程文件。注注注意意意:事实上,rc.subr(8)在启动一个守护进程前还会使用pidfile 进程文件来查看它是否已经在运行。使用了faststart参数可以跳过这个检查步骤。如果守护进程只有在确定的文件存在的情况下才可以运行,那就将它们列到required_files中,而rc.subr(8)将在启动守护进程之前检查那些文件是否存在。还有相关的分别用来检查目录和环境变量的required_dirs和required_vars可供使用。它们都在rc.subr(8)中有详细的说明。注注注意意意:来自rc.subr(8)的默认方法,通过使用forcestart作为脚本的参数,可以强制性地跳过预先需要的检查。我们可以在守护进程有异常的时候,自定义发送给守护进程的信号。特别是,sig_reload指定了使守护进程重新装载其配置的信号;默认情况也就是SIGHUP 信号。另一个信号是发送给守护进程以停止该进程;默认情况下是SIGTERM 信号,但这是可以通过设置sig_stop来进行适当更改的。8BSD rc.d脚本编程实战注注注意意意:信号名称应当以不包含SIG前缀的形式指定给rc.subr(8),就如范例中所示的那样。FreeBSD 版本的kill(1)程序能够识别出SIG前缀,不过其它系统版本的就不一定了。在默认的方法前面或后面执行附加任务是很容易的。对于我们脚本所支持的每条命令参数而言,我们可以定义argument_precmd和argument_postcmd来完成。这些sh(1)命令分别在它们各自的方法前后被调用,显然,从它们各自的名字就能看出来。注注注意意意:如果我们需要的话,用自定义的argument_cmd改写默认的方法,并不妨碍我们仍然使用argument_precmd和argument_postcmd。特别是,前者便于检查自定义的方法,以及执行自身命令之前所遇到更严密的条件。于是,将argument_precmd和argument_cmd一起使用,使我们合理地将检查从动作中独立了出来。别忘了你可以将任意的有效的sh(1)表达式插入到方法和你定义的pre-与post-commands 命令中。在大部分情况下,调用函数使实际任务有好的风格,但千万不要让风格限制了你对其幕后到底是怎么回事的思考。如果我们愿意实现一些自定义参数,这些参数也可被认作为我们脚本的命命命令令令,我们需要在extra_commands中将它们列出并提供方法以处理它们。注注注意意意:reload是个特别的命令。一方面,它有一个在rc.subr(8)中预置的方法。另一方面,reload命令默认是不被提供的。理由是并非所有的守护进程都使用同样的重载方法,并且有些守护进程根本没有任何东西可重载的。所以显而易见,我们需要去询问都提供了哪些的内建功能。我们可以通过extra_commands来这样做。我们从reload的默认方法得到了什么呢?守护进程常常在收到一个信号后重新载入它们的配置一般来说,也就是SIGHUP 信号。因此rc.subr(8)尝试发送一个信号给守护进程来重载它。该信号一般预设为SIGHUP,但是如果必要的话可以通过sig_reload变量来自定义它。(14)我们的脚本提供了两个非标准的命令,plugh和xyzzy。我们看到它们在extra_commands中被列出来了,并且现在是时候给它们提供方法了。xyzzy的方法是内联的而plugh的是以mumbled_plugh形式完成的函数。非标准命令在启动或停止的时候不被调用。通常它们是为了系统管理员的方便。它们还能被其它的子系统所使用,例如,devd(8),前提是devd.conf(5)中已经指定了。全部可用命令的列表,当脚本不加参数地调用时,在rc.subr(8)打印出的使用方法中能够找到。例如,这就是供学习的脚本用法的内容:#/etc/rc.d/mumbledUsage:/etc/rc.d/mumbled fast|force|one(start|stop|restart|rcvar|reload|plugh|xyzzy|status|poll)(13)如果脚本需要的话,它可以调用自己的标准或非标准的命令。这可能看起来有点像函数的调用,但我们知道,命令和shell 函数并非一直都是同样的东西。举个例子,xyzzy在这里不是以函数来实现的。另外,还有应该被有序调用的pre-command 预置命令和post-command 后置命令。所以脚本运行自己命令的合适方式就是利用rc.subr(8),就像范例中展示的那样。rc.subr(8)提供了一个方便的函数叫做checkyesno。它以一个变量名作为参数并返回一个为零的退出值,当且仅当该变量设置为YES,或TRUE,或ON,或1,区分大小写;否则返回一个非零的退出值。在第二种情况中,函数测试变量的设置为NO,FALSE,OFF,或0,区分大小写;如果变量包含别的内容的话它打印一条警告信息,例如,垃圾。9BSD rc.d脚本编程实战切记对sh(1)而言零值意味着真而非零值意味着假。重重重要要要:checkyesno函数使用一个变变变量量量名名名。不要扩大含义将变量的值值值 传递给它;否则它不会如你预期那样的工作。下面是checkyesno的合理使用范围:if checkyesno mumbled_enable;thenfoofi相反地,以下面的方式调用checkyesno是不会工作的-至少是不会如你预期的那样:if checkyesno$mumbled_enable;thenfoofi(10)我们可以通过修改$start_precmd中的rc_flags来影响传递到$command的标帜。(11)某种情况下我们可能需要发出一条重要的信息,那样的话syslog 可以很好地记录日志。这可以使用下列rc.subr(8)函数来轻松完成:debug,info,warn,以及err。后者以指定的代码值退出脚本。(12)方法的退出值和它们的pre-commands 预命令不只是默认被忽略掉。如果argument_precmd返回了一个非零退出值,主方法将不会被执行。依次地是,argument_postcmd将不会被调用,除非主方法返回的是一个为零的退出值。注注注意意意:然而,当给一个参数使用force前缀的时候,如forcestart,rc.subr(8)会听从命令行指示而忽略那些退出值最后仍然调用所有的命令。7 链链链接接接脚脚脚本本本到到到rc.d 框框框架架架当编写好了一个脚本,它需要被整合到rc.d中去。一个重要的步骤就是安装脚本到/etc/rc.d(对基本系统而言)或/usr/local/etc/rc.d(对ports而言)中去。在 和 中都为此提供了方便的接口,通常你不必担心适当的所有权限和模式。系统脚本应当是通过可以在src/etc/rc.d找到的Makefile安装的。Port 脚本可以像Porters Handbook(http:/www.FreeBSD.org/doc/en_US.ISO8859-1/books/porters-handbook/rc-scripts.html)中描述那样通过使用USE_RC_SUBR来被安装。不过,我们应该预先考虑到我们脚本在系统启动顺序中的位置。我们的脚本所处理的服务可能依赖于其它的服务。举个例子,没有网络接口和路由选择的启用运行的话,一个网络守护进程是不起作用的。即使一个服务看似什么都不需要,在基本文件系统检查挂载完毕之前也很难启动。之前我们曾提到过rcorder(8)。现在是时候来密切地关注下它了。笼统地说,rcorder(8)处理一组文件,检验它们的内容,并从文件集合打印一个文件列表的依赖顺序到stdout标准输出。这点是用于保持文件内部的依赖信息,而每个文件只能说明自己的依赖。一个文件可以指定如下信息:它提提提供供供 的“条件”的名字(意味着我们服务的名字);它需需需求求求 的“条件”的名字;应该先先先 运行的文件的“条件”的名字;10BSD rc.d脚本编程实战能用于从全部文件集合中选择一个子集的额外关关关键键键字字字(rcorder(8)可通过选项而被指定来包括或省去由特殊关键字所列出的文件。)并不奇怪的是,rcorder(8)只能处理接近sh(1)语法的文本文件。rcorder(8)所解读的特殊行看起来类似sh(1)的注释。这种特殊文本行的语法相当严格地简化了其处理。请查阅rcorder(8)以获取更详细的信息。除使用rcorder(8)的特殊行以外,脚本可以坚持将其依赖的其它服务强制性启动。当其它服务是可选的,并因系统管理员错误地在rc.conf(5)中禁用掉该服务而使其不能自行启动时,会需要这一点。将这些谨记在心,我们来考虑下简单结合了依赖信息增强的守护进程脚本:#!/bin/sh#PROVIDE:mumbled oldmumble#REQUIRE:DAEMON cleanvar frotz#BEFORE:LOGIN#KEYWORD:nojail shutdown./etc/rc.subrname=mumbledrcvar=set_rcvarcommand=/usr/sbin/$namestart_precmd=$name_prestartmumbled_prestart()if!checkyesno frotz_enable&!/etc/rc.d/frotz forcestatus 1/dev/null 2&1;thenforce_depend frotz|return 1fireturn 0load_rc_config$namerun_rc_command$1跟前面一样,做如下详细分析:该行声明了我们脚本所提供的“条件”的名字。现在其它脚本可以用那些名字来标明我们脚本的依赖。注注注意意意:通常脚本指定一个单独的已提供的条件。然而,并没有什么妨碍我们从列出的那些条件中指定,例如,为了兼容性的目的。在其它情况,主要的名称,或者说唯一的,PROVIDE:条件应该与$name相同。因此我们的脚本指示了其依赖于别的脚本所提供的“条件”。根据这些行的信息,脚本请示rcorder(8)以将其放在一个或多个提供DAEMON和cleanvar的脚本后面,但在提供LOGIN的脚本前面。注注注意意意:BEFORE:这一行不可以在其它脚本不完整的依赖关系列表中滥用。适合使用BEFORE:的情况是当其它脚本不关心我们的脚本,但是我们的脚本如果在另一个之前运行的话能够更好地执行任务。一个典型的实例是11BSD rc.d脚本编程实战网络接口和防火墙:虽然接口不依赖防火墙来完成自己的工作,但是系统安全将因一切网络流量之前启动的防火墙而受益。除了条件相对应的每个单独服务,脚本使用元条件和它们的“占位符”来保证某个操作组在其它之前被执行。这些是由UPPERCASE大写名字所表示的。它们的列表和用法可以在rc(8)中找到。切记将一个服务名称放进REQUIRE:行不能保证实际的服务会在我们的脚本启动的时候运行。所需求的服务可能会启动失败或在rc.conf(5)中被禁掉了。显然,rcorder(8)是无法追踪这些细节的,并且rc(8)也不会去追踪。所以,脚本启动的应用程序应当能够应付任何所需求的服务的不可用情况。某些情况下,我们可以用下面 所讨论的方式来协助脚本。如我们从上述文字所记起的,rcorder(8)关键字可以用来选择或省略某些脚本。即任何rcorder(8)用户可以通过指定-k和-s选项来分别指定“保留清单(keep list)”和“跳过清单(skip list)”。从全部文件到按依赖关系排列的清单,rcorder(8)将只是挑出保留清单(除非是空的)中那些带关键字的以及从跳过清单中挑出不带关键字的文件。在FreeBSD 中,rcorder(8)被/etc/rc和/etc/rc.shutdown所使用。这两个脚本定义了FreeBSD中rc.d关键字以及它们的意义的标准列表如下:nojail该服务不适用于jail(8)环境。如果是在jail 的内部的话,自动启动和关闭程序将忽略该脚本。nostart该服务只能手动启动否则将不会启动。自动启动程序将忽略此脚本。结合shutdown关键字的话,这可以用来编写只在系统关闭时执行一些任务的脚本。shutdown这个关键字明明明确确确 地列出了需要在系统关闭前停止的服务。注注注意意意:当系统即将关闭的时候,/etc/rc.shutdown在运行。它假定认为大部分的rc.d脚本在那刻什么都不做。因此,/etc/rc.shutdown选择性地调用带有shutdown关键字的rc.d脚本,有效地忽略其余的脚本。为了更快的关闭,/etc/rc.shutdown传递faststop命令给其运行的脚本,以跳过预置的检查,例如,进程文件pidfile 的检查。正如依赖性服务应该在其所依赖的服务之前停止,/etc/rc.shutdown以相反的依赖次序来运行这些脚本。如果写一个真正的rc.d脚本的话,你应当考虑到其是否与系统关闭时有关系。例如,如果你的脚本只通过响应start命令来运行任务,那么你不需要包含这个关键字。然而,如果你的脚本管理着一个服务,那么,在系统进入halt(8)中所描述的其本身关闭顺序的最终阶段之前停止该脚本,可能是个不错的主意。特别是,你显然是应该关闭一个需要相当长时间,或需要特定的动作才能干净地关闭的服务。数据库引擎就是这样一个典型的例子。以force_depend起始的行应被用于更谨慎的情况。通常,用于修正相互关联的rc.d脚本分层结构的配置文件时会更加稳妥。如果你仍不能完成不含force_depend的脚本,范例提供了一个如何有条件地调用它的习惯用法。在范例中,我们的mumbled守护进程需求另一个以高级方式启动的进程,frotz。但frotz也是可选的;而且rcorder(8)对这些信息是一无所知的。幸运的是,我们的脚本已访问到全部的rc.conf(5)变量。如果frotz_enable为真,我们希望的最好结果是依靠rc.d已经启动了frotz。否则我们强制检查frotz12BSD rc.d脚本编程
展开阅读全文