赞
踩
接下来几篇文章首先讲述 UCI 配置及配置接口,接着讲述系统内核设置,最后还会讲述一些非 UCI 系统配置,这些配置通常不提供用户修改接口,但在系统运行时也是非常重要的。
一、UCI简介
MVC(Model-View-Control)模式是经典的Web开发编程模式,OpenWrt也采用该设 计模式。该设计模式为分层设计,模型层负责数据的持久化操作。OpenWrt 的模型层采用 统一配置接口(Unified Configuration Interface,UCI)。
统一配置接口(Unified Configuration Interface,UCI),是OpenWrt成功的关键技 术之一,已经移植支持数千个软件。它采用纯文本文件来保存配置,并提供命令 行和 C 语言编程调用接口进行管理。
UCI的目的在于集中OpenWrt系统的配置。这样每一个开发人员只需学习一次即可, 减少了学习成本。这是 OpenWrt 成功的关键原因之一。它成功地应用于 OpenWrt 的 WhiteRussian 之后的系列版本。
UCI 可以看作OpenWrt系统中最重要系统设置的主要配置接口。通常情况下这些设置对设备的功能运转至关重要,例如网络接口的配置、DHCP 和防火墙设置等。
二、UCI配置文件格式
配置节(section),是UCI配置的一个独立配置单元。UCI配置文件是由一个或多个配置节组成。配置节有一个配置类型属性,是以“config”开头,并且有一个可选名称。配置节包含一个或多个配置选项语句。
格式:配置文件由配置节(section)组成,配置节由多个“name/values”选项对(option)组成。
注意事项:
每一个配置节(section)都需要有一个类型标识(type),但不一定需要名称(name)。
每一个选项对(option)都有名称(name)和值(value),写在其所属于的配置节中。
UCI标识符和配置文件的名称只能包含字母 a~z、0~9 和_。例如连字符(-)是不允许的。
选项的值可以包含任何字符,但需要将它们正确地加上引号。
没有名称标识的配置节称为匿名配置节。
类型(type)和选项(option)的含义均由应用程序来决定,类型一般用于应用程序决定如何处理配置节包含的配置选项。
如果一个选项(type)是不存在的并且是必需的,那应用程序通常会触发一个异常或者记录一个异常的日志,然后程序退出。
通常选项(option)在配置文件中都是使用空格或制表符缩进来标识,但这个并非是语法要求,仅仅是为了增加配置文件的可读性。
option和list用来提高配置文件的可读性,并且在语法上也要求使用关键字来表示配置选项的开始。
引号的使用
通常不需要使用引号引上类型标识符(type)或值(value),引号只在封闭的值包含空格或制表符的情况下需要。它可以合法地使用双引号和单引号。
下面是合法的UCI文件选项语法示例:
下面的例子是错误的UCI文件语法:
演示案例
下面是OpenWrt中的一个实际配置文件(/etc/config/system)。
“config system”语句:定义了一个配置节的开始,配置类型为“system”,但没有名称。
选项“option hostname OpenWrt”和“option timezone UTC”两行:定义了“config system”配置节的两个简单配置。
“config timeserver ntp”语句:定义了另外一个配置节的开始,类型为“timeserver”,名称为“ntp”。
在开始带有list关键字的选项,表示有多个值被定义,所有的语句有同一个选项名称“server”。在这个的例子中均为NTP服务器地址,组合为相同顺序的单链表来 处理。
三、UCI配置文件与UCI统一配置原理
OpenWrt有很多独立的第三方应用程序,大多数应用程序的软件包维护者已经制作了UCI兼容的配置文件,启动时由UCI配置文件转换为软件包的原始配置文件。这是在运行初始化脚本/etc/init.d/中执行的。
因此,当开始执行一 个UCI兼容的守护进程初始化脚本时,你应该意识到程序的原始配置文件被覆盖了。例如,在DNS代理服务器 dnsmasq进程启动的情况下,文件/var/etc/dnsmasq.conf 是从 UCI 配置文件/etc/config/dhcp生成并覆盖的,是运行/etc/init.d/dnsmasq脚本进行配置文件转换的。
配置文件的存储:因为应用程序的配置文件是启动时通过UCI转换生成的,因此它不需要存储在非易失性存储器中,通常存储在内存中而不是在闪存中,而var目录为其内容在正常运行时不断变化的目录,因此将var目录创建为/tmp目录的一个链接。
OpenWrt的大多数配置都基于UCI文件,如果你想在软件原始的配置文件调整设置,可以通过禁用UCI方法来实现。
UCI配置文件的修改:OpenWrt系统的核心配置分成很多个文件,并且都位于/etc/config目录下。每个文件涉及系统配置的某一部分。你可以用一个文本编辑器修改,或用命令行实用程序UCI编辑配置文件。UCI的配置文件也可通过各种编程 API(如 shell、Lua和C等)来修改,这也是 Web 接口例如LuCI修改UCI文件的方式。
UCI配置文件修改后的重启:无论是通过一个文本编辑器还是命令行工具修改配置文件,在改变一个 UCI 的配置文 件后,受影响的服务或可执行程序必须由 init.d 进行重启,这样更新 UCI 配置才会真正生 效软件特定的配置文件。
init.d对配置文件的转换:许多程序是通过它们的初始化脚本与UCI配置兼容的。init.d将UCI配置转换为它们。init.d首先在该软件预期的位置生成这样的一个配置文件,它通过 重新启动可执行程序再次读入配置。注意:如果只是直接启动可执行文件,没有通过 init.d 调用,将不会将一个 UCI 配置文件更新到特定程序相应的配置文件位置,在/etc/config/的 修改将不会对现有进程有任何影响。
所有的UCI配置文件均默认保存在“/etc/config”目录下:
文 件 路 径 含 义
/etc/config/dhcp Dnsmasq 软件包配置,包含 DHCP 和 DNS 设置
/etc/config/dropbear SSH 服务器选项
/etc/config/firewall 防火墙配置,包含网络地址转换、包过滤和端口转发等
/etc/config/network 网络配置,包含桥接、接口和路由配置
/etc/config/system 系统设置,包含主机名称,网络时间同步等
/etc/config/timeserver rdate 的时间服务列表
/etc/config/luci 基本的 LuCI 配置
/etc/config/wireless 无线设置和 Wi-Fi 网络定义
/etc/config/uhttpd Web 服务器选项配置
/etc/config/upnpd miniupnpd UPnP 服务设置
/etc/config/qos 网络服务质量的配置文件定义
演示案例
例如,在修改UCI配置文件时,如果你想将局域网网关IP地址从默认地址192.168.1.1修改为192.168.6.1,可以使用vi编辑器直接修改/etc/config/network。但是此处我们使用uci命令来修改。
uci set network.lan.ipaddr=192.168.6.1
uci commit network
下一步通过运行以下命令使修改生效。
/etc/init.d/network restart
在这种情况下,你需要使用新的IP地址来登录路由器设备。路由器常用功能配置文件。
四、UCI工具介绍
在开发调整配置时,可以直接使用vi编辑器修改UCI配置文件。但是UCI统一配置文件的目的就是所有 OpenWrt 配置可以通过统一接口读取和修改。对于开发人员而言,如 果使用 awk 和 grep 工具来解析将是非常低效的,UCI实用工具提供了修改和分析UCI文件的脚本编程开发接口。
备注(重点)
当使用UCI工具写入配置文件时,配置文件都是整个重写并且不需要确认命令。这意味着在文件中任何多余的注释行和空行均会被删除。
如果你有 UCI 类型的配置文件,想保存自己的注释和空行,那就不应该使用UCI命令行工具来编辑文件。
五、命令格式与选项
命令格式:
uci [<options>] <command> [<arguments>]
options如下:
-c <path>:set the search path for config files (default: /etc/config)
-d <str>:set the delimiter for list values in uci show
-f <file>:use <file> as input instead of stdin
-m:when importing, merge data into an existing package
-n:name unnamed sections on export (default)
-N:don't name unnamed sections
-p <path>:add a search path for config change files
-P <path>:add a search path for config change files and use as default
-q:quiet mode (don't print error messages)
-s:force strict mode (stop on parser errors, default)
-S:disable strict mode
-X:do not use extended syntax on 'show'
command如下:
命 令 含 义
add 增加指定配置文件的类型为 section-type 的匿名区段。
add_list 对已存在的 list 选项增加字符串。
commit 对给定的配置文件写入修改,如果没有指定参数则将所有的配置文件写入文件系统。所 有的“uci set”“uci add”“uci rename”和“uci delete”命令将配置写入一个临时位置, 在运行“uci commit”时写入实际的存储位置。
export 导出一个机器可读格式的配置。它是作为操作配置文件的 shell 脚本而在内部使用,导 出配置内容时会在前面加“package”和文件名。
import 以 UCI 语法导入配置文件。
changes 列出配置文件分阶段修改的内容,即未使用“uci commit”提交的修改。如果没有指定 配置文件,则指所有的配置文件的修改部分。
show 显示指定的选项、配置节或配置文件。以精简的方式输出,即 key=value 的方式输出。
get 获取指定区段选项的值。
set 设置指定配置节选项的值,或者是增加一个配置节,类型设置为指定的值。
delete 删除指定的配置节或选项。
rename 对指定的选项或配置节重命名为指定的名字。
revert 恢复指定的选项,配置节或配置文件。
六、-P选项(/var/state文件)
有些运行中的状态值没有保存在/etc/config目录下,而是保存在/var/state下,这时可 以使用“-P”参数来查询当前状态值。
七、演示案例(修改IP地址)
网络配置的相关信息存放在/etc/config/network文件中。
下面我们修改“lan”这个网络接口的IP地址(修改完在下面图中可以看到“lan”这个网络接口的IP地址为我们设置的值)。
uci set network.lan.ipaddr=192.168.0.50
uci commit network
/etc/init.d/network restart
删除上面我们设置的IP地址。
uci delete network.lan.ipaddr
uci commit network
/etc/init.d/network restart
八、演示案例(修改多个配置节类型或匿名配置节)
概念:当有多个配置节类型相同或者为匿名配置节时,UCI使用数组数字引用它们。
规则如下:
①例如:OpenWrt 系统默认有 3 个网卡接口,可以通过network.@interface[0]来引用第一个,通过network.@ interface[1]来引用第二个,通过network.@interface[2]来引用第三个。
②也可以使用负索引, 例如network.@interface[−1],其中“−1”指的是最后一个,“−2”指的是倒数第二个,以此类推。
演示案例(获取各个网卡名称)
我这个OpenWrt系统中有两个网卡
uci get network.@interface[0].ifname //获取第一个
uci get network.@interface[1].ifname //获取第二个
uci get network.loopback.ifname //获取第一个
uci get network.lan.ifname //获取第二个
uci get network.@interface[-2].ifname //获取第一个
uci get network.@interface[-1].ifname //获取第二个
九、演示案例(更改链表配置)
当一个配置文件中的选项(option)为链表时,操作方法有所不同。
演示案例
例如我们的/etc/config/system配置文件中就有“list”形式的链表选项。
添加到链表中一个配置项:
uci add_list system.ntp.server='ntp.dongshao.net'
uci commit system
删除链表中的一个配置项。
uci del_list system.ntp.server='ntp.dongshao.net'
uci commit system
删除链表中的所有配置项。
uci delete system.ntp.server
uci commit system
十、自定义配置文件及修改
下面我们创建一个helloRoute的配置。
第一步:先创建一个“hello”的配置文件
touch /etc/config/hello
第二步:首先通过命令行创建配置文件。像上面的配置一样,如果你想增加一个配置节,大多数人都会想到使用“uci add”命令,但实际上“uci add”仅可以创建匿名配置节,不能完 成创建命名配置的目标,要使用“uci set”命令来完成。
uci set hello.globe=system
第三步:设置配置节的3个选项。
uci set hello.globe.agent=dongshao //用户代理属性
uci set hello.globe.url='https://blog.csdn.net/qq_41453285' //访问URL
uci set hello.globe.delay=100 //启动延迟时间
uci commit //提交配置修改
查看配置文件内容。
十一、/lib/config/uci.sh
脚本介绍:UCI模块提供了一个 shell 脚本(/lib/config/uci.sh)并封装了UCI命令行工具的功能, 这样方便了其他软件包在将UCI配置文件转换为自己格式的配置文件时使用。
主要的函数如下所示:需要这些函数需要导入/lib/config/uci.sh。
函 数 名 称 含 义
uci_load 从UCI文件中加载配置并设置到环境变量中,可以通过env命令来查看。该命令需要和 functions.sh 中的定义共同使用。
uci_get 从配置文件中获取值。至少需要一个参数,指明要获取的配置信息。例如获 取系统主机名称调用:uci_get system.@system[0].hostname。
uci_get_state 指定从/var/state中获取状态值。
uci_load函数的注意事项
在单独导入uci.sh时,uci_load函数并不能执行成功,因为 uci_load函数引用了/lib/functions.sh的一些函数定义,因此在使用 uci_load 函数时需要先导入functions.sh的函数定义。
调用uci_load函数将配置设置到环境变量中时,uci_load函数又调用了 functions.sh 定义的 config()、option()、list()等函数。
十二、/lib/functions.sh
脚本介绍:functions.sh的主要原理是将配置文件中的配置选项设置到环境变量中,然后提供接口函数在环境变量中获取。
调用uci_load函数将配置设置到环境变量中时,uci_load函数又调用了 functions.sh 定义的 config()、option()、list()等函数。
在使用这些函数时,以点开头来将这些函数加载到执行空间中,注意点和执行文件中间有 一个空格。例如:
. /lib/functions.sh //装载函数
主要的函数如下所示:
函 数 名 称 含 义
config 供uci.sh调用,将配置节设置到环境变量中。
option 供uci.sh调用,将配置节中的选项设置到环境变量中。
list 供uci.sh调用,将配置节中的链表配置设置到环境变量中。
config_load 调用uci_load函数来从配置文件中读取配置选项,并设置到环境变量中。
config_get 从当前设置环境变量中获取配置值。
config_get_bool 从当前设置的环境变量中获取布尔值,并将它进行格式转换,如果为真,转换为1,否则转换为0。因为 UCI 的布尔值有多种类型均支持。on、true、enabled 和 1 表示真,off、false、disable 和0表示假。
config_set 将变量设置到环境变量中以便后续读取。注意:仅设置到环境变量中并没有设置到配置文件中。
config_foreach 对于未命名的配置进行遍历调用函数。共两个参数,第一个参数为回调函数,第 二个参数为配置节类型,这个函数适用于匿名配置节的转换处理。
配置文件选项的写入与读取
我们知道functions.sh的主要原理是将配置文件中的配置选项设置到环境变量中,然后提供接口函数在环境变量中获取,下面我们以config_load与config_get函数为例,介绍一下执行过程。
①首先通过调用config_load函数将UCI配置读入当前环境变量中,默认从/etc/config目录下读取配置,并设置到环境变量中。
②然后使用config_get等函数进行读取和转换配置。
③我们以config_get函数为例来说明执行流程。config_get函数从环境变量中读取配置值并赋值给变量。该函数至少要3个参数。
第1个参数为存储返回值的变量。
第2个参数为所要读取的配置节的名称。
第3个参数是所有读取的选项名称。
第4个参数是为默认值,如果配置文件没有该选项则返回该默认值,是一个可选的参数。
十三、“uci_”与“config_”函数的区别
“uci_”开头的函数是/lib/config/uci.sh脚本提供的,“config_”开头得函数是/lib/functions.sh脚本提供的。
相同点
以“uci_”开头的函数和以“config_”开头的函数大多数功能完全相同。
不同点
“uci_get”等函数直接从文件中获取,而“config_get”函数从环境变量中读取。
性能差异:“config_get”函数使用“config_load”一次从配置文件中读取设置到环境变量中,以后均不再进行磁盘操作。而“uci_get”每次均从文件中读取。
如果调用多次,两者性能差距就会显现,实际测试中两者相差10倍以上。因此在OpenWrt中大多使用以“config_”开头的“config_get”等函数进行配置文件转换。
十四、演示案例
下图所示为OpenWrt 12.09的一个实际代码,在启动时,从配置文件中获取主机名称,并设置到内核中。
十五、UCI API编程接口概述
上面我们介绍了UCI的脚本提供的命令接口,UCI还提供了C语言调用接口。下面在操作系统Ubuntu下来说明API的使用。
首先准备UCI编程接口的使用环境:UCI软件依赖Libubox,因此首先编译Libubox,Libubox库编译好之后,就可以再去编译UCI库了。
等到UCI库编译好之后,我们就可以在编写C语言程序时使用UCI库了。
十六、Libubox库
Libubox库介绍:Libubox是OpenWrt的一个必备的基础库,包含大小端转换、链表、MD5等实用工具基础库,采用Cmake来编译。
安装Cmake
。Cmake是跨平台的产生Makefile的命令行工具,它应用于在脚本文件中配置工程
-D选项:设置工程选项。
-i选项:打开交互提示来进行设置。
CmakeLists.txt:Cmake是一个跨平台的编译系统生成工具。通过平台独立Cmake的listfiles文件来指定构建过程。这个文件在每一个源码目录树目录下均有一个,文件名为CmakeLists.txt。
安装Cmake命令如下:
sudo apt-get install cmake
编译、安装Libubox
第一步:下载Libubox的软件包。
got clone https://github.com/yubo/libubox.git
第二步:使用cmake生成Makefile。
命令最后的一个点,代表当前路径。
设置了两个编译开关(BUILD_LUA:BOOL、BUILD_EXAMPLES:BOLL)为 OFF,这两个分别是lua和使用示例,我们不进行编译,因此把编译选项关闭。
cmake -D BUILD_LUA:BOOL=OFF -D BUILD_EXAMPLES:BOLL=OFF .
第三步: 进行编译(make)、安装(make install)。
在进行编译时,编译过程中会输出编译进度百分比。
编译完成之后进行安装,安装到系统目录中,需要使用管理员权限并输入密码,因此会加上sudo命令。
make;
sudo make install;
安装内容包含头文件和动态链接库文件。头文件默认安装在/usr/local/include/libubox目录下,动态链接库libubox.so和 libubox.a安装在/usr/local/lib/目录下。
十七、UCI库
Libubox安装完成后就可以编译安装UCI软件了。
编译、安装UCI库
第一步:下载UCI软件包。
git clone https://github.com/jkjuopperi/uci.git
第二步:使用cmake生成Makefile。
cmake -D BUILD_LUA:BOOL=OFF .
第三步:编译、安装UCI。
make
sudo make install
UCI库的头文件安装在/usr/local/include目录下,动态链接库安装在/usr/local/lib/ libuci.so,可执行程序为/usr/local/bin/uci。
第四步:因为系统还不知道动态链接库已经安装,运行该命令会告诉系统重新加载动态链接库,这样UCI动态链接库就可以使用了。
UCI库编译好之后,就可以在C语言程序中使用UCI库了(-luci),例如下面使用gcc编译一个带有与UCI库的C程序。
gcc test.c -o test -luci
十八、UCI API接口
UCI接口命名非常规范,统一以小写的uci开头并放在uci.h头文件中。
大多数函数的第一个参数均为uci_context的指针变量。这个变量在程序初始化时调用uci_alloc_context函数分配空间并设置初始值。在程序执行结束时调用uci_free_context函数释放空间。
UCI接口有设置函数uci_set,但没有相应的获取函数uci_get,UCI使用uci_lookup_ptr来提供查询功能,如果查到则通过获取ptr变量的值来获取配置的值。
函 数 含 义
uci_alloc_context 分配UCI上下文环境对象。
uci_free_context 释放UCI上下文环境对象。
uci_load 解析UCI配置文件,并存储到UCI对象中。@name:配置文件名,相对于配置目录。@package:在这个变量中存储装载的配置包。
uci_unload 从UCI上下文环境对象中unload配置文件。
uci_lookup_ptr 分割字符串并查找。@ptr:查找的结果。@str:待查找的字符串,但 str 不能为常量, 因为将被修改赋值,在 ptr 变量内部会被使用到,因此 str 的寿命必须至少和 ptr 一样长。@extended 是否允许扩展查找。
uci_set 设置元素值,如果必要将创建一个元素。更新或创建的元素将存储在ptr-> last中。
uci_delete 删除一个元素,配置节或选项。
uci_save 保存一个package修改的delta。
uci_commit 提交更改package,提交将重新加载整个uci_package。
uci_set_confdir 修改UCI配置文件的存储位置,默认为/etc/config。
十九、演示案例
UCI API的使用案例,见文章:https://blog.csdn.net/qq_41453285/article/details/102545618。
我是小董,V公众点击"笔记白嫖"解锁更多OpenWrt资料内容。
————————————————
版权声明:本文为CSDN博主「董哥的黑板报」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41453285/article/details/102527800
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。