<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://ocr.michiyo.me/w/index.php?action=history&amp;feed=atom&amp;title=Tutorial%2Fautorun_options%2Fzh</id>
	<title>Tutorial/autorun options/zh - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://ocr.michiyo.me/w/index.php?action=history&amp;feed=atom&amp;title=Tutorial%2Fautorun_options%2Fzh"/>
	<link rel="alternate" type="text/html" href="https://ocr.michiyo.me/w/index.php?title=Tutorial/autorun_options/zh&amp;action=history"/>
	<updated>2026-08-24T15:16:55Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.43.9</generator>
	<entry>
		<id>https://ocr.michiyo.me/w/index.php?title=Tutorial/autorun_options/zh&amp;diff=1357&amp;oldid=prev</id>
		<title>imported&gt;OCDoc Import: Imported from legacy OpenComputers documentation at ocdoc.cil.li</title>
		<link rel="alternate" type="text/html" href="https://ocr.michiyo.me/w/index.php?title=Tutorial/autorun_options/zh&amp;diff=1357&amp;oldid=prev"/>
		<updated>2026-08-24T08:08:27Z</updated>

		<summary type="html">&lt;p&gt;Imported from legacy OpenComputers documentation at ocdoc.cil.li&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;= 自定义自启动程序 =&lt;br /&gt;
&lt;br /&gt;
OpenOS 提供了多种用于自动运行程序的工具。本文档将介绍所有此类工具、说明如何使用它们，并比较它们的优缺点。请注意，这些选择中的一部分可能不可用，也有一部分不如旧版本操作系统的那么耐用。模组的1.7.2开发版本（对所有本模组支持的MC版本而言）对于文档中讲述的内容提供了完整支持。下一个 OC 版本，1.7.3版会包含开发版本的所有修复。&lt;br /&gt;
&lt;br /&gt;
在我们涉及到自动运行程序的访问点之前，本文档会先回顾前台和后台、阻塞式调用、线程，以及事件注册记录（侦听器和定时器）相关内容。&lt;br /&gt;
&lt;br /&gt;
=== 后台程序与前台交互程序 ===&lt;br /&gt;
&lt;br /&gt;
前台交互程序会打断或延迟OpenOS加载 shell。这种模式的进程天生适合需要等待用户输入的程序。对后台程序的开发，OpenOS提供了线程和事件注册记录（侦听器和定时器）功能。设计自启动程序时，用户可以自由选择前台或后台程序，以满足他们的需要。&lt;br /&gt;
&lt;br /&gt;
=== 阻塞式调用 ===&lt;br /&gt;
&lt;br /&gt;
构建后台进程时，你需要知道什么是阻塞式调用，以及调用时会阻塞什么。有两种不同的阻塞式调用：机器阻塞和系统退让。&lt;br /&gt;
&lt;br /&gt;
机器阻塞调用指component(组件) API中的一部分函数，例如文件系统的读入操作，这种调用会让整台OC电脑进入等待状态。这些阻塞式调用不受操作系统控制，也不受我们控制，是真正的阻塞。因此当我们讨论操作是否被阻塞时指的不是机器阻塞。在机器阻塞调用的过程中，系统中的一切都不会运行。&lt;br /&gt;
&lt;br /&gt;
作为对比，系统调用`computer.pullSignal`时会令当前线程yield（退让）（`event.pull`和`os.sleep`也会调用`computer.pullSignal`）。OpenOS会创建一个init（初始）线程，并且将所有进程都放在这个线程之内运行。假设你创建了多个协程并不断循环resume（恢复）它们，只要某一个协程进行了系统退让调用，你的循环会被阻塞在进行调用的协程处。这个协程不会yield回来。无论如何，这个线程中所有的协程都实质上被暂停了。在开发后台应用时，理解这一点是工作流程中的重要一环。如果你需要让你的应用程序等待10秒再继续工作，并且你调用了`os.sleep(10)`，你就同时阻断了前台的应用程序（因为它们运行在同一个线程内）。系统退让方法包括：`term.read`、`os.sleep`还有`event.pull`。&lt;br /&gt;
&lt;br /&gt;
=== 线程 ===&lt;br /&gt;
&lt;br /&gt;
回顾一下线程运行库的[[API/thread/zh|API文档]]。线程适用于开发运行时不太需要考虑系统其他部分的后台式应用。&lt;br /&gt;
&lt;br /&gt;
= ==== OpenOS的线程为非阻塞式进程 =&lt;br /&gt;
&lt;br /&gt;
正如前文所言，进行系统退让调用（任何调用了`computer.pullSignal`的事物）会暂停当前线程中的所有协程，但是你也可以选择创建你自己的线程。其他进行系统退让调用的线程不会阻塞你的线程，你的线程也可以随意进行系统退让调用而不会阻塞其他线程。如果你希望自己的后台进程每秒钟广播一次网络信息，你可以写一个简短的while循环并轻易实现：&lt;br /&gt;
&lt;br /&gt;
```lua thread.create(function()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;while true do&lt;br /&gt;
  modem.broadcast(port, msg)&lt;br /&gt;
  os.sleep(1)&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
end) ```&lt;br /&gt;
&lt;br /&gt;
= ==== OpenOS线程不可重入 =&lt;br /&gt;
&lt;br /&gt;
线程在编写时即完全为单入口点的。这种模式更易于掌握，且允许编程者将代码构建为自己的子系统。当唯一的一个线程函数退出时，线程会死亡，也不能再resume了。还需要注意当你创建自己的线程（如[[API/thread/zh|线程文档]]所述）时它会附着到当前进程上。线程会阻塞其父进程的关闭，直到线程自己关闭。如果你想要运行一个完全后台，不附着于任何进程的线程，请调用`:detach()`。&lt;br /&gt;
&lt;br /&gt;
= ==== OpenOS线程“与事件协作” =&lt;br /&gt;
&lt;br /&gt;
事件信号会被推入队列，然后在你拉取信号时从队列移除。如果你有多个独立的代码片段都在进行`pullSignal`（或`event.pull`）调用，那么一个进程可能会从另一个等待信号的进程那里抢走信号（通过`event.listen`注册的事件处理函数不受此影响）。线程从自身独有的信号队列中（这不会导致额外内存开销，技术层面上讲线程是注册的事件处理函数，不会被抢信号的代码影响）拉取信号，而且在设计运行于OpenOS提供的线程中的后台程序时，你也无需关心系统的其他部分与程序所需信号间的关系。&lt;br /&gt;
&lt;br /&gt;
= ==== OpenOS线程不适用于低内存的系统 =&lt;br /&gt;
&lt;br /&gt;
OpenOS仅需要不到130k的内存即可启动和运行交互式shell。一根1级内存条能提供196k大小的内存，这样还能留给你超过60k的“回旋余地”。如此的低内存状态已经很危险了。然而，引导启动的过程还没有完全加载可用的系统运行库。加载线程运行库还要额外分配大约20k内存，并且每个用户线程也要消耗大约 5k 内存。这些数字都是保守估计，而且老实说，由于Lua虚拟机的特性和它按块分配内存的方式，这个数字不够精确。我承认，线程库可以进行优化以减少内存使用。但是这样做的话线程库的准确性和耐用性会远远不如现在这么好。未来版本可能会减少内存使用。理想情况下，你的系统应该在加载了所需运行库和运行了所需程序后，仍有大于100k的闲置内存。&lt;br /&gt;
&lt;br /&gt;
=== 事件注册记录 ===&lt;br /&gt;
&lt;br /&gt;
事件注册记录分为&amp;#039;&amp;#039;&amp;#039;侦听器&amp;#039;&amp;#039;&amp;#039;与事件&amp;#039;&amp;#039;&amp;#039;定时器&amp;#039;&amp;#039;&amp;#039;两类。&lt;br /&gt;
&lt;br /&gt;
请回顾event(事件) 运行库的[[API/event/zh|API文档]]。事件注册记录是理想的开发后台“响应器”的解决方案。“响应器”指为响应某些预期的事件信号而启动的短期任务。&lt;br /&gt;
&lt;br /&gt;
= ==== 事件注册记录是可重入的 =&lt;br /&gt;
&lt;br /&gt;
事件处理函数是一个回调函数，每当它名下注册的条件被满足时都将被调用。&lt;br /&gt;
&lt;br /&gt;
```lua event.listen(&amp;quot;key_down&amp;quot;, function(...)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;handle_key_down(...)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
end) ```&lt;br /&gt;
&lt;br /&gt;
`handle_key_down()`函数在每次出现`key_down`信号时都会被调用。&lt;br /&gt;
&lt;br /&gt;
```lua&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;event.timer(1, function()&lt;br /&gt;
  onTimeout()&lt;br /&gt;
end, 10)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
`onTimeout()`函数会被每1秒调用一次，总共10次（详见[[API/event/zh|event(事件) api]]）。&lt;br /&gt;
&lt;br /&gt;
回调函数也可以选择返回`false`（`nil`与此不同，必须为`false`）以注销自身。返回其他值或不返回任何值则不会注销回调函数。定时器会在调用次数达到`times`（第三个参数）时自动注销。&lt;br /&gt;
&lt;br /&gt;
这种可重入的行为模式适合基于事件执行小的，重复出现的任务，但是不便于构建长期在后台运行的，需要完成多种不同任务的系统。事件注册记录可能不适合需要保持状态的，或者完成大量工作的，以及与系统事件完全无关的程序。显然，上述内容并不是规定，只是一些未考虑你特殊需求背景的主观意见。&lt;br /&gt;
&lt;br /&gt;
= ==== 事件注册记录是轻量化的 =&lt;br /&gt;
&lt;br /&gt;
不像线程，事件注册系统的性能开销很少。最基础的注册记录`event.listen(&amp;quot;key_down&amp;quot;, function()end)`可以做到只消耗400字节的内存（你可能会觉得这也是很大的开销，那么欢迎进入Lua虚拟机的世界）。OpenOS已经使用了很多事件注册记录，并且系统已经围绕这些注册记录进行了深度优化，以增加可靠性与减少内存开销。&lt;br /&gt;
&lt;br /&gt;
= ==== 事件注册记录是阻塞式的 =&lt;br /&gt;
&lt;br /&gt;
如果你刚才跳过了，那么请阅读前文的[[Tutorial/autorun options/zh#阻塞式调用|阻塞式调用]]部分。如前文所述，OpenOS运行在单个init线程上，并且如果线程中的任何部分进行了系统退让调用`computer.pullSignal`（或者其他调用此函数的方法，例如`event.pull`），此线程中创建的所有事件注册记录都会暂停。因此，只要你还想让你的系统对前台应用（例如shell）作出响应，那么在事件注册记录里面调用`os.sleep`就很不明智。&lt;br /&gt;
&lt;br /&gt;
== 用于自动执行程序的进入点 ==&lt;br /&gt;
&lt;br /&gt;
下列的进入点就像“钩子”，或者脚本位置。通过这些进入点你可以在电脑启动时运行你的后台或前台应用。&lt;br /&gt;
&lt;br /&gt;
=== 交互式Shell启动（.shrc） ===&lt;br /&gt;
&lt;br /&gt;
引导启动的最后一步是加载OpenOS shell。shell会阻塞等待，直到有tty输出可用。这意味着如果没有GPU或屏幕，shell启动将会进行等待。&lt;br /&gt;
&lt;br /&gt;
在用于tty的stdout可用后，shell会完成加载，并且执行`/etc/profile.lua`，此脚本将会加载别名以及设置环境变量。`/etc/profile.lua`脚本做的最后一件事是[https://ss64.com/bash/source.html “source”]（读取并执行）你的`/home/.shrc`文件，此文件默认为空。`source`不会执行Lua代码，而是将文件中的每一行作为shell命令运行。如果你有一个想在shell加载时运行的脚本，那么可以将指向此脚本的路径输入你的`.shrc`文件中。`.shrc`会在每次shell加载时运行，也就是说每次启动可能不止执行一次。因为用户可以输入`exit`、`^d`甚至可以发送硬中断信号来杀死shell程序（然后init进程会加载一个新的shell）。&lt;br /&gt;
&lt;br /&gt;
我更建议编辑`/home/.shrc`而不是`/etc/profile.lua`，仅出于有序性考虑。&lt;br /&gt;
&lt;br /&gt;
=== Runscripts（rc） ===&lt;br /&gt;
&lt;br /&gt;
请回顾[[API/rc/zh|RC文档]]的内容。&lt;br /&gt;
&lt;br /&gt;
`/bin/rc`可被用于启用开机启动脚本。RC脚本甚至在没有shell、没有GPU、没有屏幕、没有键盘的系统上也能启动。&lt;br /&gt;
&lt;br /&gt;
=== 文件系统的Autorun（autorun.lua） ===&lt;br /&gt;
&lt;br /&gt;
在任何文件系统的根目录中你都可以创建名为`autorun.lua`（或者`.autorun.lua`）的文件。当文件系统组件首次被检测到后，OpenOS会自动运行此文件。请注意`/home/autorun.lua`不在&amp;#039;&amp;#039;&amp;#039;rootfs&amp;#039;&amp;#039;&amp;#039;（根文件系统）的根目录中。本段所说的autorun脚本会在文件系统每次被添加到系统时都执行一次（例如，你把带有autorun脚本的软盘拔出再重新插入就会执行一次）。&lt;br /&gt;
&lt;br /&gt;
此特性默认启用，并且可以在安装于rw（可读写）模式文件系统的操作系统中禁用，方式是通过调用`filesystem.setAutorunEnabled(false)`，或者直接修改`/etc/filesystem.cfg`并加上`autorun=false`。&lt;br /&gt;
&lt;br /&gt;
=== 引导启动脚本(/boot/) ===&lt;br /&gt;
&lt;br /&gt;
此选择强烈不推荐使用，将其在此列出只是为了劝说那些有异议的用户放弃自己的想法。&lt;br /&gt;
&lt;br /&gt;
OpenOS会运行`/boot/`中的引导启动脚本（按照文件名排序）供其核心操作使用。虽然可以将自定义的引导启动脚本安装到内核启动脚本中，但我们很不建议这么做。&lt;br /&gt;
&lt;br /&gt;
安装自定义引导启动脚本（到`/boot/`中）会带来风险，你的引导启动脚本可能会在核心运行库还不可用时就开始运行。再当前的OpenOS版本下，我们无法保证哪怕是在你的脚本中调用`require`的安全性，哪怕现在安全，也无法保证在未来的OpenOS更新中是否安全（因为我可能会改动引导启动顺序）。&lt;br /&gt;
&lt;br /&gt;
io功能可能没完全初始化，init进程可能也未完成初始化，甚至Lua原版库都有可能未加载完成。根据你在引导启动脚本中执行的代码不同，你甚至可能无意中绕过事件调度系统，导致系统错过组件更新。是的，引导启动过程需要负责这么多事情。&lt;br /&gt;
&lt;br /&gt;
尽管说了这么多，此处还是给出了一些`/boot`脚本的样例，它们在当下和不远的未来可能会正常工作。请在你的文件名的开头加上`99_`，这样它会在引导启动流程的最后加载。如果有任何事物没能按你的预期工作（例如向stdout输出，或者从stdin读取），不意味着出现了bug而是意味着不支持。换句话说，使用`/boot/`脚本目录的风险自负。如果你需要stdout，你可以等待`term_available`信号。再强调一次，这不是官方支持的选择。&lt;br /&gt;
&lt;br /&gt;
```lua local event = require(&amp;quot;event&amp;quot;) --init信号由引导启动进程发出，代表着系统准备完毕 event.listen(&amp;quot;init&amp;quot;, function()&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;local thread = require(&amp;quot;thread&amp;quot;)&lt;br /&gt;
thread.create(function()&lt;br /&gt;
  --[[&lt;br /&gt;
    此处为你自定义的服务代码，作为后台线程运行&lt;br /&gt;
  ]]--&lt;br /&gt;
end):detach()&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
end) ```&lt;br /&gt;
&lt;br /&gt;
```lua local event = require(&amp;quot;event&amp;quot;) event.listen(&amp;quot;component_added&amp;quot;, function(...)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;--[[&lt;br /&gt;
  此处为你自定义的服务代码，作为后台事件响应程序运行&lt;br /&gt;
]]--&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
end) ```&lt;br /&gt;
&lt;br /&gt;
== 目录 ==&lt;br /&gt;
&lt;br /&gt;
{{:Tutorial/contents/zh}}&lt;br /&gt;
&lt;/div&gt;</summary>
		<author><name>imported&gt;OCDoc Import</name></author>
	</entry>
</feed>