Openwrt系统简述

Openwrt系统简述

  • [1. OpenWrt简介](#1. OpenWrt简介)
    • [1.1 主要特点](#1.1 主要特点)
    • [1.2 开源嵌入式操作系统比较](#1.2 开源嵌入式操作系统比较)
    • [1.3 开发环境及编译分析](#1.3 开发环境及编译分析)
    • [1.4 编译扩展机制feeds](#1.4 编译扩展机制feeds)
    • [1.5 OpenWrt 包管理系统](#1.5 OpenWrt 包管理系统)
      • [1.5.1 OpenWrt 包管理系统](#1.5.1 OpenWrt 包管理系统)
      • [1.5.2 OPKG命令](#1.5.2 OPKG命令)
      • [1.5.3 OPKG配置](#1.5.3 OPKG配置)
  • [2. OpenWrt配置](#2. OpenWrt配置)
    • [2.1 UCI简介](#2.1 UCI简介)
      • [2.1.1 文件语法](#2.1.1 文件语法)
      • [2.1.2 配置原理](#2.1.2 配置原理)
      • [2.1.3 UCI工具](#2.1.3 UCI工具)
    • [2.2 系统内核设置](#2.2 系统内核设置)
      • [2.2.1 sysctl.conf](#2.2.1 sysctl.conf)
      • [2.2.2 sysctl](#2.2.2 sysctl)

1. OpenWrt简介

OpenWrt是一个嵌入式设备的Linux发行版,以GPL许可协议发行。

1.1 主要特点

  1. 代码里不含第三方开源包,只包含开源包地址链接。
  2. 编译时自动下载源代码、打补丁来满足指定平台要求,并编译。还可以修改Makefile来下载最新的软件包。
  3. 使用LuCI作为最终用户管理界面。LuCI以Apache许可协议发布Web管理功能代码。
  4. UCI通用配置管理方法。
  5. 通过脚本来调用iptables来实现防火墙功能,配置保存在UCI文件中。
  6. 开放和可扩展的OPKG格式安装升级包

1.2 开源嵌入式操作系统比较

Android OpenWrt
内核 Linux 内核 Linux 内核
许可协议 Apache2.0 GNU License
使用场景 面向终端用户,手持设备。用户接口采用JAVA 提供图形用户界面GUI 服务器、家庭路由器等,用户接口默认为 UCI 命令行提供,也支持通过 Web 方式来管理
开发主导模式 由谷歌公司主导开发 OpenWrt.org 社区主导,社区由个人组成,更开放

1.3 开发环境及编译分析

  1. 准备一台Ubuntu系统环境
  2. 安装编译工具:
bash 复制代码
sudo apt-get install subversion //版本管理系统
sudo apt-get install g++ flex patch // g++是GNU工程的C/C++编译工具,FLEX(The Fast Lexical Analyzer)一个快速词法分析工具,patch是将diff文件应用到原始文件的工具,用于在程序开发过程中提交代码,是应用差异文件的工具。
sudo apt-get install libncurses5-dev zlib1g-dev // libncurses5-dev用于屏幕终端控制,zlib1g-dev是压缩及解压缩开发。
sudo apt-get install git-core //用于大型工程的分布式版本管理工具
sudo apt-get install libssl-dev // openssl开发库,用于加密解密、计算哈希和数据签名等。
sudo apt-get install gawk // GNU工程实现的AWK语言工具,是文本模式扫描和处理的工具。
sudo apt-get install xz-util // xz格式的压缩工具集,有非常高的压缩比率。
  1. 下载代码:git clone git://git.openwrt.org/15.05/openwrt.git cc
  2. 配置和编译:
    (1)更新、安装所有可选的软件包:
bash 复制代码
./scripts/feeds update 更新最新的包定义 
./scripts/feeds install -a 安装所有的包

(2)编译配置:make defconfig。OpenWrt提供模块化选择编译,每一个模块通常都有3个选项[Y|N|M]可供选择,输入Y该模块将包含在固件中;输入M将作为一个模块来编译,可以后续再进行安装;输入N将不编译该模块。还有一些是单选选项菜单,按空格键进行选择,再次按空格键则取消选择。

(3)编译:make

bash 复制代码
make V=s 可以输出编译过程中每一步的执行动作,出错后显示详细的错误信息。
make -j2 使用2个线程进行并行编译。
注:OpenWrt采用补丁包方式来管理代码,第三方的代码不放在它自己的代码库中,仅在编译前从第三方服务器下载。
单编应用:
(1)在qsdk目录下执行make menuconfig将工具加入config;
(2)make package/xxx /compile V=s

1.4 编译扩展机制feeds

feeds是OpenWrt开发所需要的软件包套件的工具及更新地址集合,这些软件包通过一个统一的接口地址进行访问。这样用户可以不用关心扩展包的存储位置,可以减少扩展软件包和核心代码部分的耦合。它由两部分组成,即扩展包位置配置文件feeds.conf.default和脚本工具feeds。目前在配置文件中保存最重要的扩展软件包集合有以下4个:

(1)"LuCI" OpenWrt默认的Web浏览器图形用户接口。

(2)"routing"一些额外的基础路由器特性软件,包含动态路由Quagga等。

(3)"telephony" IP电话相关的软件包,例如freeswitch和Asterisk等。

(4)"management" TR069等各种管理软件包。

利用feeds提供的接口将OpenWrt所需的全部扩展软件包进行下载并安装:

bash 复制代码
./scripts/feeds update --a 
./scripts/feeds install -a
  1. feeds工具用法如下:
bash 复制代码
./scripts/feeds 
Usage: ./scripts/feeds <command> [options]
Commands: 
list [options]: List feeds, their content and revisions (if installed) 
Options: -n : List of feed names. 
-s : List of feed names and their URL. 
-r <feedname>: List packages of specified feed. 
-d <delimiter>: Use specified delimiter to distinguish rows (default: spaces)
install [options] <package>: Install a package
Options: -a : Install all packages from all feeds or from the specified feed using the -p option. 
-p <feedname>: Prefer this feed when installing packages. 
-d <y|m|n>: Set default for newly installed packages.
-f : Install will be forced even if the package exists in core OpenWrt (override) 
search [options] <substring>: Search for a package 
Options: -r <feedname>: Only search in this feed 
uninstall -a|<package>: Uninstall a package
Options: -a : Uninstalls all packages.
update -a|<feedname(s)>: Update packages and lists of feeds in feeds.conf . 
Options: -a : Update all feeds listed within feeds.conf. Otherwise the specified feeds will be updated.
                -i : Recreate the index only. No feed update from repository is performed. 
clean: Remove downloaded/generated files. 
  • Update:下载在feeds.conf或feeds.conf.default文件中的软件包列表并创建索引。-a表示更新所有的软件包。只有更新后才能进行后面的操作。
  • list:从创建的索引文件"feed.index"中读取列表并显示。只有进行更新之后才能查看列表。
  • install:安装软件包以及它所依赖的软件包,从feeds目录安装到package目录,即在"package/feeds"目录创建软件包的软链接。只有安装之后,在后面执行"make menuconfig"时,才可以对相关软件包是否编译进行选择。
    例如安装luci-app-firewall:./scripts/feeds install luci-app-firewall
  • search:按照给定的字符串来查找软件包,需要传入一个字符串参数。
  • uninstall:卸载软件包,但它没有处理依赖关系,仅仅删除本软件包的软链接。
  • clean:删除update命令下载和生成的索引文件,但不会删除install创建的链接。
  1. feeds代码处理过程
    首先读取并解析feeds.conf配置文件,然后执行相应的命令,例如install时,将安装应用程序包和它所有直接或间接依赖的所有软件包。安装时将创建一个符号链接,从packages/feeds/ f e e d n a m e / feed_name/ feedname/package_name指向feeds/ f e e d n a m e / feed_name/ feedname/package_name, 这样在"make menuconfig"时,feeds的软件包就可以被处理到,就可以选择编译了

1.5 OpenWrt 包管理系统

OPKG(Open/OpenWrt Package)是一个轻量快速的软件包管理系统,是IPKG的克隆,目前已成为开源嵌入式系统领域的事实标准。OPKG常用于路由、交换机等嵌入式设备中,用来管理软件包的下载、安装、升级、卸载和查询等,并处理软件包的依赖关系。功能和桌面Linux操作系统Ubuntu中的apt-get、Redhat中的yum类似。 OPKG 是一个针对根文件系统全功能的软件包管理器。它不仅仅是在独立的目录安装软件,还可以用于安装内核模块和驱动等。OPKG在安装时会自动解决安装软件时的包依赖关系,如果遇见错误,就中止安装。

1.5.1 OpenWrt 包管理系统

当执行"opkg update"命令进行软件列表的更新时,OPKG首先会读取配置文件/ etc/opkg.conf,这个文件保存了OPKG的全局配置信息。紧接着,OPKG会根据配置地址位置下载软件包列表文件Packages.gz到/var/opkg-list目录下,这个文件是软件仓库中所有软件列表及其依赖关系的清单,是使用gzip压缩的文件,这样在网络传输时所占用网络流量比较小。其后任何安装命令均需首先读取这两个文件。

软件安装之后的信息会保存在目录/usr/lib/opkg/下面,这里就相当于Windows操作系统中的注册表。它包含状态文件,OPKG通过访问这个状态文件确定该软件是否已安装、安装的版本,以及依赖关系是否满足等,从而可以确定安装软件的版本、文件路径等信息。

OPKG命令执行会读取以下3部分的信息:配置文件、已安装软件包信息和软件仓库的软件包信息。

  • 配置文件默认位置为/etc/opkg.conf。

  • 已安装软件包状态信息保存在/usr/lib/opkg目录下。

  • 软件仓库的软件包信息保存在/var/opkg-lists目录下。

1.5.2 OPKG命令

OPKG必须带有一个子命令,如果不带有子命令,将输出OPKG的详细使用提示信息。首先是提示必须有一个子命令参数,然后是命令格式提示信息,最后是各个子命令和选项信息含义描述。

bash 复制代码
opkg must have one sub-command argument: 
usage: opkg [options...] sub-command [arguments...] where sub-command is one of: 
Package Manipulation: 
update   Update list of available packages 
upgrade <pkgs>   Upgrade packages 
install <pkgs>   Install package(s) 
configure <pkgs>   Configure unpacked package(s) 
remove <pkgs|regexp>   Remove package(s) 
flag <flag> <pkgs>   Flag package(s) 
<flag>=hold|noprune|user|ok|installed|unpacked (one per invocation) 
Informational Commands:
List   List available packages 
list-installed   List installed packages 
list-upgradable   List installed and upgradable packages 
list-changed-conffiles   List user modified configuration files 
files <pkg>   List files belonging to <pkg> 
search <file|regexp>   List package providing <file> 
find <regexp>   List packages whose name or description matches <regexp> 
info [pkg|regexp]   Display all info for <pkg> 
status [pkg|regexp]   Display all status for <pkg> 
download <pkg>   Download <pkg> to current directory 
compare-versions <v1> <op> <v2>   compare versions using <= < > >= = << >>

OPKG的功能主要分两类,一种是软件包的管理命令,另外一种是软件包的查询命令。另外还有很多可以修饰的选项。

  1. 软件包的管理
    软件包的管理是OPKG最重要的功能,主要包含更新软件包列表、安装、卸载和升级等功能。

(1)opkg update。该命令用于更新可以安装的软件包列表。该命令不需要参数,执行时从服务器地址下载软件包列表文件并存储在/var/opkg-lists/目录下。OPKG在安装或升级时需要读取这个文件,这个文件代表当前仓库中所有可用的软件包。也可以删除该文件来释放存储空间,在安装软件前需要重新获取这个文件。

(2)opkg install。该命令用于安装软件包,需要一个参数,传递一个软件包名称。如果软件包之间有依赖关系,会自动下载所有被依赖的软件包,并依次将所有被依赖的软件包安装上。

(3)opkg remove。该命令用于卸载软件包,需要一个参数,传递一个软件包名称。需要注意的是,在安装时自动安装的软件包并不会删除,需要自己手动删除,或者在卸载软件包的同时增加(--autoremove)参数将不需要的安装包也删除。

(4)opkg upgrade。该命令用于升级软件包。如果软件包没有安装,该命令执行之后和"opkg install"效果相同。如果升级多个软件包,以空格分隔列在命令之后即可。例如使用opkg upgrade ip wget来升级两个软件包。

  1. 查询信息
    OPKG查询命令可以在软件仓库中查询,也可以在运行的系统中查询。OPKG提供了软件包的双向查询功能:正向查询,即从软件包来查询所包含的文件列表;也可以反向查询,从系统中所安装的文件查询所属的软件包。

(1)opkg list。该命令用于列出所有可使用的软件包,列出内容格式为: 软件包名称 -- 版本 -- 描述。

(2)opkg list-installed。该命令用于列出系统中已经安装的软件包。

(3)opkg list-changed-conffiles。该命令用于列出用户修改过的配置文件。

(4)opkg files 。该命令用于列出属于这个软件包()中的所有文件,这个软件包必须已经安装。

(5)opkg search 。该命令用于列出提供的软件包,注意:需要传递文件的绝对路径。

(6)opkg find 。该命令用于列出软件包名称和匹配的软件包。是一个正则表达式,可以精确匹配,也可以使用星号来模糊匹配,例如使用"net"或者" net*",均可以匹配NetCat。

(7)opkg info [pkg]。该命令用于显示已安装[pkg]软件包的信息,包含软件包名称、版本、所依赖的软件包名称、安装状态和安装时间等。如果没有指定参数则输出所有已安装软件包的信息。"opkg status"和这个命令功能完全相同。

(8)opkg download 。该命令用于将软件包下载到当前目录。

(9)opkg print-architecture。该命令用于列出安装包的架构。

(10)opkg whatdepends [-A] [pkg]。该命令用于针对已安装的软件包,输出依赖这个软件包的软件包。示例3-4所示代码用于查询依赖libmagic的软件包。

  1. 常用选项
    OPKG有很多选项可以使用,这里只列出几个最常用的选项。

(1)-A:查询所有的软件包,包含未安装的软件包。

(2)-d <dest_name>:使用<dest_name>作为软件包的安装根目录。<dest_name>是配置文件中定义的目录名称。

(3)-f <conf_file>:指定使用<conf_file>作为opkg的配置文件。如不指定,默认配置文件是/etc/opkg.conf。

(4)--nodeps:不按照依赖来安装,只安装软件包自己。这可能会导致缺少依赖文件,导致程序不能执行。

(5)--autoremove:卸载软件包时自动卸载不再使用的软件包(在安装时依赖会自动安装上)。

(6)--force-reinstall:强制重新安装软件包,在软件包版本未修改时不会再次安装,增加该选项来强制重新安装。

1.5.3 OPKG配置

OPKG需要一个配置文件来保存全局配置,例如软件从哪里下载、安装到哪里等。

  1. 调整软件仓库地址
    OPKG配置文件默认是/etc/opkg.conf。内容参考如下。
bash 复制代码
src/gz attitude_adjustment 
http://192.168.1.106:8080/openwrt 
dest root /
dest ram /tmp 
lists_dir ext /var/opkg-lists 
option overlay_root /overlay
  1. 调整安装目录
    OPKG的一个非常有用的特性,是有能力指定任何安装包的安装目录。安装目录在配置文件/etc/opkg.conf中定义。配置文件中目的地址格式是以dest开头,紧跟着目的地址的名称,最后是目录路径,必须从根目录开始。
bash 复制代码
dest root /
dest ram /tmp 
dest usb /opt

安装目录定义之后,目的地址名称就可以在安装命令中引用了。安装时目的地址名称只能引用在/etc/opkg.conf中定义的地址名称,例如"-d ram"表示软件将安装到临时目录/tmp下。安装命令类似如下格式: opkg install -d <目的地址名称>

  1. 代理设置
    OPKG通过下载软件包来安装,如果你通过HTTP代理服务器来上网,那就不能直接连接到服务器地址,这时就需要设置代理服务器地址。在/etc/opkg.conf中加入以下设置: option http_proxy http://proxy.example.org:3128/
    如果代理服务器需要认证,则需要增加以下认证信息:
bash 复制代码
option proxy_username xxxxxx 
option proxy_password xxxxxx 

如果使用busybox的wget命令,这个工具不支持认证功能,下载时将认证失败。可以改为在URL中传递用户名和密码:

bash 复制代码
option http_proxy http://username:password@proxy.example.org:3128/

2. OpenWrt配置

MVC(Model-View-Control)模式是经典的Web开发编程模式,OpenWrt也采用该设计模式。该设计模式为分层设计,模型层负责数据的持久化操作。OpenWrt的模型层采用统一配置接口(Unified Configuration Interface,UCI)。

2.1 UCI简介

2.1.1 文件语法

配置文件由配置节(section)组成,配置节由多个"name/values"选项对组成。每一个配置节都需要有一个类型标识,但不一定需要名称。每一个选项对都有名称和值,写在其所属于的配置节中。语法如下:

bash 复制代码
config <type> ["<name>"] # Section 
option <name> "<value>" # Option
注:
1.没有名称标识的配置节称为匿名配置节。
2.通常选项在配置文件中都是使用空格或制表符缩进来标识,但这个并非是语法要求,仅仅是为了增加配置文件的可读性。
3.在开始带有list关键字的选项,表示有多个值被定义,所有的语句有同一个选项名称。
4.通常不需要使用引号引上类型标识符或值,引号只在封闭的值包含空格或制表符的情况下需要。如:option 'example' "some value with space"
5. UCI标识符和配置文件的名称只能包含字母a~z、0~9和_。例如连字符(-)是不允许的。选项的值可以包含任何字符,但需要将它们正确地加上引号。
下面是所有的合法UCI文件选项语法示例:
option example value 
option 'example' value 
option example "value" 
option "example" 'value' 
option 'example' "value" 
option 'example' "some value with space"
下面的例子是错误的UCI文件语法:
option 'example" "value' (引号没有配对) 
option example some value with space (带有空格的值缺少引号)

2.1.2 配置原理

可以用一个文本编辑器修改,或用命令行实用程序UCI编辑配置文件。也可通过各种编程API(如shell、Lua和C等)来修改,这也是Web接口例如LuCI修改UCI文件的方式。 无论是通过一个文本编辑器还是命令行工具修改配置文件,在改变一个UCI的配置文件后,受影响的服务或可执行程序必须由init.d进行重启,这样更新UCI配置才会真正生效。init.d将UCI配置转换为它们软件特定的配置文件。init.d首先在该软件预期的位置生成这样的一个配置文件,它通过重新启动可执行程序再次读入配置。注意:如果只是直接启动可执行文件,没有通过init.d调用,将不会将一个UCI配置文件更新到特定程序相应的配置文件位置,在/etc/config/的修改将不会对现有进程有任何影响。

例如,在修改UCI配置文件时,如果你想将局域网网关IP地址从默认地址192.168.1.1修改为192.168.6.1,可以使用vi编辑器修改/etc/config/network。这里我们使用uci命令来修改。

bash 复制代码
 uci set network.lan.ipaddr=192.168.6.1 uci commit network 

下一步通过运行以下命令使修改生效。

bash 复制代码
 /etc/init.d/network restart

2.1.3 UCI工具

UCI工具选项的含义和基本使用方法:

bash 复制代码
uci 
Usage: uci [<options>] <command> [<arguments>]
Commands: 
batch 
export [<config>] //导出一个机器可读格式的配置。它是作为操作配置文件的shell脚本而在内部使用,导出配置内容时会在前面加"package"和文件名
import [<config>] //以UCI语法导入配置文件
changes [<config>] //列出配置文件分阶段修改的内容,即未使用"uci commit"提交的修改。如果没有指定配置文件,则指所有的配置文件的修改部分
commit [<config>] //对给定的配置文件写入修改,如果没有指定参数则将所有的配置文件写入文件系统。所有的"uci set""uci add""uci rename"和"uci delete"命令将配置写入一个临时位置,在运行"uci commit"时写入实际的存储位置
add <config> <section-type> //增加指定配置文件的类型为section-type的匿名区段
add_list <config>.<section>.<option>=<string> //对已存在的list选项增加字符串
del_list <config>.<section>.<option>=<string> 
show [<config>[.<section>[.<option>]]] //显示指定的选项、配置节或配置文件。以精简的方式输出,即key=value的方式输出
get <config>.<section>[.<option>] //获取指定区段选项的值
set <config>.<section>[.<option>]=<value> //设置指定配置节选项的值,或者是增加一个配置节,类型设置为指定的值
delete <config>[.<section>[[.<option>][=<id>]]] //删除指定的配置节或选项
rename <config>.<section>[.<option>]=<name> //对指定的选项或配置节重命名为指定的名字
revert <config>[.<section>[.<option>]] //恢复指定的选项,配置节或配置文件
reorder <config>.<section>=<position> 
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'

例如设置值,如果更改系统局域网的网关地址,从默认值"192.168.1.1"修改为"192.168.56.11":

bash 复制代码
~# uci set network.lan.ipaddr=192.168.56.11 
~# 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"指的是倒数第二个,等等。

有些运行中的状态值没有保存在/etc/config目录下,而是保存在/var/state下,这时可以使用"-P"参数来查询当前状态值。

当为链表配置时,操作方法有所不同:

#增加到链表中一个配置项

bash 复制代码
~#>uci add_list system.ntp.server='ntp.bjbook.net' 
#删除链表中的一个配置项
~#>uci del_list system.ntp.server='ntp.ntp.org' 
#删除链表中的所有配置项
~#>uci delete system.ntp.server

:"uci add"仅可以创建匿名配置节,不能完成创建命名配置的目标,要使用"uci set"命令来完成增加一个配置节。

2.2 系统内核设置

2.2.1 sysctl.conf

这个文件是系统启动预加载的内核配置文件,通过sysctl命令读取和设置到系统当中。配置文件语法格式如下:

bash 复制代码
# comment
; comment token = value 

以"#"和分号开头的行均为注释行,并忽略空白行,配置值以key=value形式进行设置。例如,设置打开报文转发为net.ipv4.ip_forward=1。

2.2.2 sysctl

sysctl是用于修改运行中的内核参数的命令,所有可用的内核参数均在/proc/sys目录下。运行sysctl需要procfs文件系统支持。可以用sysctl读取和修改内核参数数据。参数以key= value形式进行设置。

bash 复制代码
-n:查询时输出配置项的值,但不输出配置项。 
-e:当碰到不认识的配置项时,忽略错误。 
-w:使用这个选项来修改系统设置。 
-p:从指定的配置文件中加载配置,如果没有指定则使用默认的配置文件/etc/sysctl. conf。 
-a:显示当前所有可用的值。

常用命令举例如下:

bash 复制代码
/sbin/sysctl -a,显示所有的内核配置; 
/sbin/sysctl -n kernel.hostname,查询kernel.hostname的值; 
/sbin/sysctl -w kernel.hostname ="host",修改系统主机名称为host; 
/sbin/sysctl -p /etc/sysctl.conf,加载配置。 

内核的参数配置在启动时由sysctl工具加载,默认加载/etc/sysctl.conf。启动之后均可在/proc/sys下查询,例如直接查询是否打开路由转发:cat /proc/sys/net/ipv4/ip_forward

内核参数也可以通过直接修改/proc/sys下的文件来生效。例如打开路由转发设置,可以执行以下命令:echo "1" > /proc/sys/net/ipv4/ip_forward

相关推荐
Charles Ray19 分钟前
C++学习笔记 —— 内存分配 new
c++·笔记·学习
重生之我在20年代敲代码20 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
我要吐泡泡了哦1 小时前
GAMES104:15 游戏引擎的玩法系统基础-学习笔记
笔记·学习·游戏引擎
骑鱼过海的猫1231 小时前
【tomcat】tomcat学习笔记
笔记·学习·tomcat
小安运维日记1 小时前
Linux云计算 |【第四阶段】NOSQL-DAY1
linux·运维·redis·sql·云计算·nosql
贾saisai3 小时前
Xilinx系FPGA学习笔记(九)DDR3学习
笔记·学习·fpga开发
北岛寒沫3 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
烟雨666_java3 小时前
JDBC笔记
笔记
GEEKVIP3 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
CoolTiger、4 小时前
【Vmware16安装教程】
linux·虚拟机·vmware16