赞
踩
LuCI 架构采用了MVC(Model-View-Controller)设计模式,各个目录的作用如下:
LuCI 将网页中的每一个菜单视作一个节点,当用户通过浏览器点击节点,向路由器发起请求,LuCI 会从 controller 目录下的 index.lua 开始组织这些节点。index.lua 中定义了根节点 root,其他所有的节点都挂在这个根节点上。
通俗来讲,可以将 LuCI 管理界面看作一棵树状结构的菜单系统。管理路由器设置的网页就像一棵大树,每一层菜单就是一个节点。
当点击网页上的任何一个菜单选项时,就像是在触摸大树上的一片叶子或枝干。这时,浏览器会发送一个请求给路由器,告诉它想访问哪个菜单节点。
LuCI 框架接收到这个请求后,会从一个叫做 controller 目录下的 index.lua 文件开始处理。这个 index.lua 文件就好比是整棵树的根部,它定义了整个菜单系统的基础结构和访问规则。
在这个根节点之下,LuCI 会根据在 index.lua 中定义的 entry 函数逐步组织起其他的子菜单节点。也就是说,每当你在 index.lua 中添加一个类似于 entry({"admin", "example", "first"}, call("first_action"), "First") 的语句时,你就相当于在菜单树上挂了一个新的子节点——"First"。
当用户点击网页上的 "First" 菜单时,LuCI 就会调用与之关联的 first_action 函数,呈现相应的页面内容或者执行相应的操作。就这样,通过一步步递归展开,整个复杂的菜单系统就被建立起来了,并能够在用户交互时动态响应用户的请求。
entry(path, callback, title, order):用于定义 LuCI 管理界面的菜单项及其关联的操作。
path 指定该节点的位置(例如 node1.node2.node3)
target 指定当该节点被调度(即用户点击)时的行为,主要有三种:call、template 和 cbi。
title:标题,即我们在网页中看到的菜单
order:同一级节点之间的顺序,越小越靠前,反之越靠后(可选)
Map (config, title, description)
config:配置文件的层级名称,比如 example 对应 /etc/config/exampl
title:配置界面的标题,即我们在网页中看到的菜单
description:配置界面的描述信息
s = m:section(TypedSection, "example_instance", "Section Title", "Section Description")
TypedSection 或 SimpleSection:Section 的类型,TypedSection 可以为区段分配类型,并支持默认配置。
example_instance:区段实例的名称,用于区分配置文件中的不同区段实例。
Section Title:区段在界面上显示的标题。
Section Description(可选):区段的描述信息。
o = s:option(Value, "option_name", "Option Label", "Option Description")
Value 或其他选项类型(如 ListValue、Flag 等):选项的数据类型。
option_name:选项在配置文件中的键名。
Option Label:选项在界面上显示的标签文本。
Option Description(可选):选项的描述信息
进入 /usr/lib/lua/luci/controller/ 目录下,创建 lua 脚本文件 example.lua,其内容如下
- --[[module 是 LuCI 自定义的函数,用于定义一个新的模块。这里的模块名为 "luci.controller.example",
- 表示这是一个 LuCI 控制器模块,主要用于定义路由和处理用户请求。
- package.seeall 是 Lua 标准库中的一个函数,它用于打开模块内的全局访问权限。
- 这意味着在这个模块内部定义的所有函数和变量都将被视为全局的,可以从外部访问]]
-
- module("luci.controller.example", package.seeall)
-
- --[[定义了 LuCI 控制器的入口点(entry points)。
- entry({"admin", "example"}, firstchild(), "Example", 60) 表示注册一个路由。
- 当用户在 Web 管理界面访问 /admin/example 时,LuCI 将会调用 firstchild() 函数来决定下一步跳转的位置。
- 这里 firstchild() 通常是指导航菜单的第一个子页面,即指向 "admin", "example", "first" 的路由。
- '模板', 60 分别表示在菜单中显示的标题("模板")和菜单排序优先级(60)。]]
- --[[这里的 firstchild() 是一种特殊的回调函数引用。
- 当LuCI接收到匹配到该路由 "admin", "example" 的请求时,它不会直接执行某个特定的动作函数,
- 而是查找该路由下第一个有效的子节点(即 "admin", "example", * 中的 * 部分),
- 并将控制权传递给这个子节点对应的处理函数]]
- --
- function index()
- entry({"admin", "example"}, firstchild(),"模板", 60)
- entry({"admin", "example", "first"}, call("first_action"), "第一")
- entry({"admin", "example", "second"}, call("second_action"), "第二")
- end
-
- --[[当用户访问 /admin/example/first 时,LuCI 将调用 first_action() 函数进行处理。
- luci.template.render("header") 表示渲染一个名为 "header" 的模板文件,
- 通常这个模板文件包含了页面的头部元素,如导航栏、样式表引用等。
- 这个模板文件位于 /usr/lib/lua/luci/view,如果在其他目录下则更改参数为header.htm路径
- luci.http.write("<h1>Hello World</h1>") 用于直接向客户端发送 HTML 数据,
- 这里是发送一个 <h1> 标签,显示 "Hello World",即页面的主要内容。]]
-
- function first_action()
- luci.template.render("header")
- luci.http.write("<h1> 一级标题 hello</h1>")
- luci.http.write("<h2> 二级标题 hello</h2>")
- end
-
- function second_action()
- luci.template.render("header")
- luci.http.write("<h1> 一级标题 hello</h1>")
- luci.http.write("<h2> 二级标题 hello</h2>")
- end
刷新网页
更改example.lua文件
- module("luci.controller.example", package.seeall)
-
- --[[更改call为template 在view目录下直接调用html页面
- 添加order参数为子菜单排序
- order参数加不加引号系统都会识别为数字]]
- function index()
- entry({"admin", "example"}, firstchild(),"模板", 60)
- entry({ "admin", "example", "third" }, template("example/third"), "第三","30")
- entry({ "admin", "example", "fourth" }, template("example/fourth"), "第四","35")
- entry({"admin", "example", "first"}, call("first_action"), "第一","10")
- entry({"admin", "example", "second"}, call("second_action"), "第二","20")
-
- end
- function first_action()
- luci.template.render("header")
- luci.http.write("<h1> 一级标题 hello</h1>")
- luci.http.write("<h2> 二级标题 hello</h2>")
- end
-
- function second_action()
- luci.template.render("header")
- luci.http.write("<h1> 一级标题 hello</h1>")
- luci.http.write("<h2> 二级标题 hello</h2>")
- end
创建模板目录 /usr/lib/lua/luci/view/example/在模板目录下创建文件 third.htm/fourth.htm其内容如下
- --third
- <%+header%>
- <h1>Hello World</h1>
- --fourth
- <%+header%>
- <h1>Hello World</h1>
刷新网页
更改example.lua文件
- module("luci.controller.example", package.seeall)
-
- function index()
- entry({ "admin", "example" }, firstchild(), "模板", 60)
- entry({ "admin", "example", "third" }, template("example/third"), "第三", "30")
- entry({ "admin", "example", "fourth" }, template("example/fourth"), "第四", "35")
- entry({ "admin", "example", "first" }, call("first_action"), "第一", "10")
- entry({ "admin", "example", "second" }, call("second_action"), "第二", "20")
- --[[
- 如果配置文件/etc/config/example 存在,则创建 Example 的子节点,
- 当节点被调度时,LuCI 会将
- /usr/lib/lua/luci/model/cbi/example/fifth.lua 这个脚本转换成 html 页面
- 发给客户端。
- --]]
- if nixio.fs.access("/etc/config/example")
- then
- entry({ "admin", "example", "fifth" }, cbi("example/fifth"), "第五", 40)
- end
- end
-
- function first_action()
- luci.template.render("header")
- luci.http.write("<h1> 一级标题 hello</h1>")
- luci.http.write("<h2> 二级标题 hello</h2>")
- end
-
- function second_action()
- luci.template.render("header")
- luci.http.write("<h1> 一级标题 hello</h1>")
- luci.http.write("<h2> 二级标题 hello</h2>")
- end
创建配置文件/etc/config/example
新建 lua 脚本文件:/usr/lib/lua/luci/model/cbi/example/fifth.lua,内容如下
- --[[
- Map (config, title, description)
- ]]
- m = Map("example", "cbi示例", "这是cbi的一个非常简单的例子")
- return m
刷新网页
修改/usr/lib/lua/luci/model/cbi/example/fifth.lua
- m = Map("example", "cbi示例", "这是cbi的一个非常简单的例子")
- --[[
- 在 Map 对象 m 中创建一个名为 s 的 Section(配置区段),类型为 TypedSection。
- 第一个参数 TypedSection 表示这是一个带有类型属性的配置区段;
- 第二个参数 是区段的名称,这也将成为 /etc/config/example 文件中区段的标识;
- 第三个参数 是区段的标题;
- 第四个参数 是区段的描述信息。
- ]]
- s = m:section(TypedSection, "example", "模板", "此部分为模板")
-
- --[[
- 设置 Section 对象 s 允许用户在界面上添加和移除区段实例。
- ]]
- s.addremove = true
-
- --[[
- 设置 Section 对象 s 不是匿名区段,意味着每个实例都需要在配置文件中拥有一个唯一的标识(id)。
- ]]
- s.anonymous = false
-
- --[[
- 在 Section 对象 s 中创建一个名为 n 的 Option(选项),类型为 Value(值类型)。
- 第一个参数 Value 表示这是一个可以输入任意值的选项;
- 第二个参数 "num" 是选项在配置文件中的键名;
- 第三个参数 "Number" 是选项在界面上显示的标签文本。
- ]]
- n = s:option(Value, "num", "Number")
-
- --[[
- 设置 Option 对象 n 允许用户清空其值,如果用户在界面上删除了输入值,保存时也会将配置文件中的相应值清空。
- ]]
- n.rmempty = true
- return m
刷新网页
在文本框中输入 first,然后单击 Add,如下所示
在 Number 后面随便输入一个数字,比如 12,然后单击 Save & Apply,如下所示
在路由器开发板上查看一些配置文件
n.rmempty = true表示当用户对该选项的输入值为空值时,LuCI 会将该选项从配置文件中移除。
将 Number 的值删除,再单击 Save & Apply
现在再来查看配置文件
在 OpenWrt系统中,LuCI 作为 Web 管理界面,允许用户通过网页图形界面编辑系统的各项配置。当用户在 LuCI 中修改了配置,并单击“Save & Apply”按钮后,会发生以下过程:
LuCI 会首先将用户在网页上所做的更改保存到对应的配置文件中。例如,网络相关的配置会保存到 /etc/config/network 文件。(上述保存到创建的配置文件/etc/config/example)
保存完成后,LuCI 通常会调用相应的 UCI(Unified Configuration Interface)命令行工具(如 ubus 或 uci)来通知系统配置已更改,并触发重新加载配置
系统收到配置更改通知后,会根据配置文件的变化情况,调用相应的启动脚本执行配置更新操作。这些启动脚本通常位于 /etc/init.d/ 目录下,例如对于网络配置,对应的启动脚本就是 /etc/init.d/network,如果为上述示例在/etc/init.d/目录下新建启动脚本文件,当示例的配置文件变化后,在/etc/init.d/目录下新建的启动脚本也会运行。
启动脚本接收到诸如 reload 的参数后,会停止当前的相关服务,应用新的配置,然后再启动这些服务,从而使更改生效。
为配置文件 example 创建一个启动脚本/etc/init.d/example,同时为其添加可执行权限。其内容如下:
- #!/bin/sh /etc/rc.common
- START=50
- start()
- {
- echo "start example" > /dev/ttyS0
- }
- reload()
- {
- echo "reload example" > /dev/ttyS0
- }
LuCI 通过以配置文件名作为参数调用/sbin/luci-reload 来使配置生效,而 luci-reload 会解析另一个配置文件 /etc/config/ucitrack,需要将 example 添加进去。用vi打开/etc/config/ucitrack,在最后添加如下内容:
config example
option init example
当用户在 LuCI 管理界面单击 "Save & Apply" 保存并应用配置变更后,LuCI 通常会执行 /sbin/luci-reload 命令,并传入对应的配置模块名称(即执行/sbin/luci-reload example)。luci-reload 会识别出与该模块相关的启动脚本,并调用其 reload 函数来重新加载配置并应用更改。
这里,example表示要跟踪的配置文件,init 选项指定了与该配置文件关联的启动脚本名称(即 /etc/init.d/example)
当 /etc/config/example 文件发生变化时,ucitrack 会监测到变化并调用 /etc/init.d/example 脚本的 reload 函数(如果存在),以重新加载并应用新的配置。这样就能确保当用户通过 LuCI 管理界面单击 "Save & Apply" 后,配置变更能够被系统及时识别并应用。
为了确保 /etc/init.d/example 脚本在系统启动时被自动运行,以及能够响应 luci-reload 命令,需要执行以下命令启用该服务:
- chmod +x /etc/init.d/example #首先确保有执行权限 否则报错Permission denied
- root@OpenWrt:/# /etc/init.d/example enable
这条命令会创建一个符号链接,将 /etc/init.d/example 链接到 /etc/rc.d/S50example(这里的数字 50 可能根据实际的 START 变量值有所不同),这样每次启动或执行 reload 时,系统都会自动运行 /etc/init.d/example 脚本中的相应函数。
当使用 reload 参数调用启动脚本时,其目的是让系统在不完全重启服务的情况下,仅重新加载并应用新的配置。这对于许多服务来说是十分重要的,因为它可以在不影响服务整体运行的前提下实现配置的热更新。例如,在网络配置变更时,调用 /etc/init.d/network reload 就可以让系统在不关闭网络连接的前提下,重新读取 /etc/config/network 配置文件并按照新配置调整网络设置。这样做的好处是可以避免因服务重启带来的短暂网络中断。相比之下,如果是使用其他参数(如 start 或 stop),它们分别代表启动或停止服务,这会导致服务状态的显著改变,很可能会影响到依赖于该服务的其他功能。而 reload 参数提供了更平滑的配置过渡方式,更适合于实时生效的配置更新场景。
现在单击网页中的 Save & Apply,打开串口助手可以看到开发板中输出了如下内容:(配置更改才会执行,即填入不同数字才会输出reload example )
reload example
说明确实执行了/etc/init.d/example 中的 reload 函数。
dtu文件夹下Makefile
- include $(TOPDIR)/rules.mk
- include $(INCLUDE_DIR)/package.mk #引入全局变量规则和包构建规则
-
- PKG_NAME:=dtu
- PKG_VERSION:=1.0
- define Package/$(PKG_NAME)
- CATEGORY:=My Package
- #DEPENDS:= 如果需要依赖其他软件包,在这里添加
- TITLE:=DTU program
- endef #定义包的基本信息,包括名称 版本号 类别 标题
-
- #编译前执行
- define Build/Prepare
- mkdir -p $(PKG_BUILD_DIR)
- $(CP) ./src/* $(PKG_BUILD_DIR)/
- endef #在编译前运行 将src源文件复制到编译目录PKG_BUILD_DIR下
-
- #安装时执行
- define Package/$(PKG_NAME)/install
- $(INSTALL_DIR) $(1)/bin
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/dtu $(1)/bin/
- endef #创建bin目录,将PKG_BUILD_DIR下可执行文件安装到/bin下
-
-
- $(eval $(call BuildPackage,dtu))
将源文件放入src文件并在src下创建Makefile文件
- all:
- $(CC) dtu.c -o dtu
make menuconfig勾选dtu,M生成单独软件包,*包含进固件
运行 make ./package/dtu/compile V=s后在/bin/packages/mipsel_24kc/base下生成dtu_1.0_mipsel_24kc.ipk 运行scp dtu_1.0_mipsel_24kc.ipk root@192.168.1.1:/root 将软件包拷贝到root目录下
运行opkg install dtu_1.0_mipsel_24kc.ipk安装软件包 在/bin下生成dtu可执行程序
在/etc/init.d下创建脚本server_init
- #!/bin/sh /etc/rc.common
- START=99
- STOP=10
- start()
- {
- /bin/dtu &
- }
- stop()
- {
- killall dtu
- }
添加执行权限
chmod +x server_init
在/etc/rc.d/rc*.d下创建链接 系统开机后会按照预定的优先级依次启动
./server_init enable
reboot重启ps查看
在package/dtu下创建files文件夹储存启动脚本文件server_init
在dtu目录下的Makefile文件中添加代码,将files下的启动脚本文件拷贝到/etc/init.d下
并启动开机启动
- #安装时执行
- define Package/$(PKG_NAME)/install
- $(INSTALL_DIR) $(1)/bin $(1)/etc/init.d/
- $(INSTALL_BIN) $(PKG_BUILD_DIR)/dtu $(1)/bin/
- $(INSTALL_BIN) ./files/server_init $(1)/etc/init.d/
- endef
- #创建bin目录和/etc/init.d/目录,将PKG_BUILD_DIR下可执行文件安装到/bin下
- #将server_init脚本安装到/etc/init.d/下
-
- #安装后执行
- define Package/$(PKG_NAME)/postinst
- #!/bin/sh
- # check if we are on real system
- if [ -z "$${IPKG_INSTROOT}" ]; then
- echo "Enabling rc.d symlink for dtu"
- if [ -e /etc/rc.d/S??dtu ];then
- rm /etc/rc.d/S??dtu
- fi
- if [ -e /etc/rc.d/K??dtu ];then
- rm /etc/rc.d/K??dtu
- fi
- /etc/init.d/server_init enable
- fi
- exit 0
- endef #设置开机自启动,检查是否存在S启动脚本和K关闭脚本
- #存在则关闭,并创建新的服务
-
- #卸载前执行
- define Package/$(PKG_NAME)/prerm
- #!/bin/sh
- # check if we are on real system
- if [ -z "$${IPKG_INSTROOT}" ]; then
- echo "Removing rc.d symlink for dtu"
- /etc/init.d/server_init disable
- fi
- exit 0
- endef #判断是否为真实的系统环境(非临时安装环境)
- #并禁用server_init服务
hostname():获取主机名。
loadavg():获取系统负载平均值。
luci.model.uci模块:
cursor():创建一个UCI数据库游标。
changes():获取最近的UCI更改。
apply():应用UCI配置更改。
游标对象的 get、set、add、delete 等方法用于操作UCI配置。
luci.template模块:
render(template, ...):渲染指定的Lua模板文件。
process(template, context):处理模板并输出内容。
luci.util模块:
split(str, sep):分割字符串。
trim(s):去除字符串两侧的空白字符。
ip.IPv4(ipstr):IP地址解析。
ip.IPv6(ipstr):IPv6地址解析。
软件包:
PKG_NAME - 用于指定软件包的名称,通常是唯一标识该软件包的关键字符串。
PKG_VERSION - 表示软件包的版本号,这是构建系统用来区分不同版本软件的重要信息。
PKG_RELEASE - 这是编译发布的版本信息,可能反映了软件包在同一个版本基础上的不同编译版本或修订版本,比如补丁级别。
PKG_BUILD_DIR - 指定编译该软件包的工作目录,默认情况下,会在构建系统的临时目录(如$(BUILD_DIR))下为每个软件包创建一个单独的子目录,子目录名由软件包名和版本号组成。
PKG_SOURCE - 指定要下载的源代码包的文件名,构建系统会根据这个信息从指定位置下载源代码。
PKG_SOURCE_URL - 提供源代码包的下载地址,构建系统会从这个URL下载指定的源码包。
PKG_MD5SUM - 源代码包的MD5校验和,用于验证下载的源代码包是否完整无误。
PKG_CAT - 指定解压源代码包的方式,比如使用zcat解压.gz文件,bzcat解压.bz2文件,或者unzip解压.zip文件。
PKG_BUILD_DEPENDS - 指定该软件包在编译过程中依赖的其他软件包,这些依赖的软件包必须先于当前软件包被成功编译。这通常用于编译时依赖关系,与运行时依赖(DEPENDS)有所区别,尽管两者语法可能相似。
SECTION - 软件包分类,目前未使用,未来可能会引入分类功能。
CATEGORY - 指定软件包在menuconfig菜单中的位置,如果这个类别之前未被使用过,menuconfig会自动创建一个新的菜单来容纳该类别的软件包。
TITLE - 软件包的简短描述,用于在menuconfig中显示软件包的基本信息。
URL - 提供软件包源代码的官方网站或仓库地址,方便开发者获取更多信息。
MAINTAINER - 软件包的维护者联系信息,便于用户报告问题或寻求帮助。
DEPENDS - 定义软件包在编译和安装时所需的依赖关系。具体语法包括:
- DEPENDS:=+foo:当前软件包和foo软件包一起联动,一荣俱荣,一损俱损,如果当前软件包被选中或取消选中,foo软件包的状态也会随之同步改变。
- DEPENDS:=foo:当前软件包依赖于foo软件包,只有当foo软件包被选中时,当前软件包才会出现在menuconfig中。
- DEPENDS:=@FOO:当前软件包依赖于全局配置项CONFIG_FOO,只有当CONFIG_FOO被启用时,当前软件包才会出现在menuconfig中。
- DEPENDS:=+FOO:bar:当前软件包和bar软件包联动,同时取决于全局配置项CONFIG_FOO,当CONFIG_FOO被启用时,当前软件包依赖于bar软件包。
- DEPENDS:=@FOO:bar:当前软件包是否依赖bar软件包取决于全局配置项CONFIG_FOO,只有当CONFIG_FOO被启用且bar软件包被选中时,当前软件包才会出现在menuconfig中。
o:value("wan","WAN")
在 OpenWrt/LEDE LuCI 的上下文中,o:value("wan","WAN") 这行代码的作用是在表单选项 o 中添加一个选项值。这里的 o 是一个 Value 类型的表单选项对象。
固件的etc的文件受源代码哪些文件的影响?
OpenWrt固件中/etc目录下的文件受到源代码树中多个位置的影响:
feeds是软件包仓库的目录,那package是什么
在OpenWRT环境中,feeds 和 package 目录都与软件包管理有关,它们的作用有所不同:
简而言之,在OpenWRT编译流程中,feeds是软件包仓库列表和软件包元数据的来源,而package则是实际存放编译时所需软件包源代码的地方。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。