-
AppImage 是 Linux 系统中一种新型的软件包格式,它与 rpm、deb 这些软件包格式相比最大的不同便是:(1)无需安装,即用即删。(2)只需打包一次,便可到处运行。完美的解决了不同 Linux 发行版(Ubuntu/Debian/Fedora/CentOS)之间软件包不统一的问题。它的工作原理便是将程序运行所需的文件全部打包在一个文件中,待程序运行时再将这些文件提取在 /tmp/.mount_xxxxxxx/ 目录中,然后执行 AppRun 脚本启动程序以进行资源的调用。以下便是一个 AppImage 文件内部包含的目录树结构:AppDir/ ├── AppRun ├── 应用图标.png ├── 程序名.desktop ├── usr/ ├── bin/ ├── lib/ ├── share/它本质上就是一个 squashfs 文件系统 + runtime 执行器。特别注意:(1)要实现跨平台运行,待打包的程序最好是在 CentOS 7 系统上进行编译 ,然后再进行打包。【注:编译 C/C++ 程序所使用的系统库 glibc 在 Linux 系统上几乎肯定存在,而该库有着良好的向后兼容性,因此使用旧版本的 glibc 库编译出来的程序几乎可以完美的运行在新版本的 glibc 系统上。而在 CentOS 7 上的 glibc 版本是 2.17,该版本较旧且兼容性较好,因此在其系统上编译出来的 C 程序通常也可以在大部分的 Linux 发行版系统中使用。】(2)待打包程序依赖的 lib 文件中最好只包含其专属的库文件即可,不要包含类似 glibc 这样的系统库文件。【注:这是因为在 A 系统中的 glibc 文件通常并不可以在 B 系统中使用,因此为了避免 AppImage 程序运行错误,请勿这样去做。再者,glibc 在 Linux 系统中是肯定会存在的,因此也并不需要额外去包含这样的依赖文件。】手动打包 - appimagetool linuxdeploy是由 AppImage 官方制作的打包工具,在使用它进行打包时,必须要先分析待打包程序的动态库依赖情况,然后再完成对 AppDir 目录的装填,最后才能使用 appimagetool 完成对程序的打包。由于分析程序的依赖情况是个很复杂的问题,因此该工具在使用上体验并不太好。接下来,我将演示如何对一个简单的 C 程序完成打包过程:(1)文件准备:hello.c。// 主文件 hello.c#include <stdio.h>int main() { printf("Hello Appimage\n"); return 0;}(2)编译并打包#(1)编译及检验运行gcc -o hello hello.c./hello#(2)制作 AppDir 目录树mkdir -p AppDir/usr/bin/cp ./hello AppDir/usr/bin/wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O AppDir/hello.png #任意图片文件即可nano AppDir/hello.desktop #文件内容见下方nano AppDir/AppRun #脚本内容见下方#(3)开始制作 AppImage 程序/root/appimagetool-x86_64.AppImage AppDir/附注:hello.desktop 文件如下:[Desktop Entry]Name=helloExec=helloIcon=helloType=ApplicationCategories=Utility;Terminal=trueAppRun 脚本如下:#!/bin/shAPPDIR="$(dirname "$(readlink -f "$0")")"# 添加库目录if [ -d "$APPDIR/lib64" ]; then export LD_LIBRARY_PATH="$APPDIR/lib64:$LD_LIBRARY_PATH"fiif [ -d "$APPDIR/usr/lib" ]; then export LD_LIBRARY_PATH="$APPDIR/usr/lib:$LD_LIBRARY_PATH"fi# 启动主程序 //注意:不同应用主程序路径需要修改exec "$APPDIR/usr/bin/hello" "$@"AppDir 目录树结构如下:AppDir/├── AppRun //启动程序,可以是简单的脚本,也可以是 ELF,只要保证运行该脚本主程序能被启动即可。├── hello.desktop //注意 EXEC 的值,它对应的是/usr/bin/目录中的程序,而 Icon 对应的是当前目录├── hello.png //也支持 svg 格式└── usr └── bin └── hello自动打包 - linuxdeploylinuxdeploy是一个由第三方制作的 AppImage 打包工具,与 appimagetool 不同的是,它可以对待打包程序自动进行依赖分析,并自动将所需的依赖及资源文件按照 AppDir 的目录格式给装填完毕,用户只需将模版化的 desktop 文件和 icon 文件准备好即可,使用起来简直美滋滋。【示例一】:接下来,我将演示如何对一个需要依赖的简单 C 程序完成打包过程:(1)文件准备:mylib.h、mylib.c、main.c、Makefile。// 动态库头文件 mylib.h#ifndef MYLIB_H#define MYLIB_Hint add(int a, int b);void hello();#endif// 动态库源码 mylib.c#include <stdio.h>#include "mylib.h"int add(int a, int b) { return a + b;}void hello() { printf("Hello from my dynamic library!\n");}// 主程序 main.c#include <stdio.h>#include "mylib.h"int main() { hello(); int result = add(3, 5); printf("3 + 5 = %d\n", result); return 0;}# Makefile 文件CC=gccCFLAGS=-fPIC -WallLDFLAGS=-sharedTARGET_LIB=libmylib.soTARGET_MAIN=mainall: $(TARGET_LIB) $(TARGET_MAIN)$(TARGET_LIB): mylib.o $(CC) $(LDFLAGS) -o $(TARGET_LIB) mylib.omylib.o: mylib.c mylib.h $(CC) $(CFLAGS) -c mylib.c$(TARGET_MAIN): main.o $(TARGET_LIB) $(CC) main.o -L. -lmylib -o $(TARGET_MAIN)main.o: main.c mylib.h $(CC) -c main.cclean: rm -f *.o $(TARGET_MAIN) $(TARGET_LIB)(2)编译并打包#(1)编译及检验运行cd myappmakemv libmylib.so /lib64/libmylib.so./main#(2)制作的 main.desktop 文件内容cat main.desktop[Desktop Entry]Name=mainExec=mainIcon=mainType=ApplicationCategories=Utility;Terminal=true#(3)获取一个 Icon 文件wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O main.png#(4)开始制作 AppImage 程序/root/linuxdeploy-x86_64.AppImage --appdir /root/myapp --output appimage --icon-file main.png --desktop-file main.desktop -e mainls -l main*.AppImage 【示例二】:最后,我再演示如何对一个系统命令 find 完成打包过程:#(1)制作的 find.desktop 文件内容cat find.desktop[Desktop Entry]Name=findExec=findIcon=findType=ApplicationCategories=Utility;Terminal=true#(2)获取一个 Icon 文件wget https://github.com/boolean-world/appimage-resources/blob/master/hello-world-appimage/hello-world-icon.png -O find.png#(3)开始制作 AppImage 程序cd $(dirname $(which find))/root/linuxdeploy-x86_64.AppImage --appdir /root/find --output appimage --icon-file find.png --desktop-file find.desktop -e findls -l find*.AppImage 注意:(1)建议将 icon 和 desktop 文件放置在 find 命令根目录下,这样在打包的时候能够避免很多问题。(2)由于 linuxdeploy 在打包环节调用的是 appimagetool,而 appimagetool 在打包的时候会在 github 上拉取 runtime 文件,因此在使用前建议设置全局代理以确保 github 可访问。(*)全局代理设置export http_proxy=http://192.168.56.1:7890export https_proxy=http://192.168.56.1:7890export no_proxy=192.168.56.1,localhostexport HTTP_PROXY=http://192.168.56.1:7890export HTTPS_PROXY=http://192.168.56.1:7890export NO_PROXY=192.168.56.1,localhost文章来源:https://www.cnblogs.com/kqdssheng/p/19269888
-
在软件开发的演进长河中,我们始终在追求一个理想状态:一次编写,随处运行。从 Java 的“Write Once, Run Anywhere”到 .NET 的跨平台战略,再到容器化与云原生的兴起,这一目标不断被重新定义。而今天,一项名为 WebAssembly(简称 Wasm) 的技术正以惊人的速度重塑软件分发、执行与安全模型——它不仅让高性能应用在浏览器中成为可能,更正在成为操作系统之上的通用轻量级运行时,悄然开启“后容器时代”的序幕。一、什么是 WebAssembly?不只是浏览器的“加速器”WebAssembly 是一种低级的、可移植的字节码格式,设计目标是在现代 Web 浏览器中以接近原生的速度执行代码。它于 2017 年由 W3C 联合 Mozilla、Google、Microsoft 和 Apple 正式标准化,如今已被所有主流浏览器支持。但 WebAssembly 的意义远不止于此:Wasm 不是 JavaScript 的替代品,而是其高性能补充;更重要的是,它正在脱离浏览器,成为通用的沙箱化运行时。核心特性特性说明接近原生性能预编译为紧凑字节码,JIT 编译后执行效率达 C/C++ 的 90%+语言无关支持 Rust、C/C++、Go、Python(实验性)、TypeScript 等编译为 Wasm安全沙箱默认无文件/网络访问权限,能力通过“导入”显式授予快速启动毫秒级冷启动,远优于 JVM 或容器跨平台同一字节码可在 Windows、Linux、macOS、甚至嵌入式设备运行二、从浏览器到全栈:Wasm 的三大演进阶段阶段 1:浏览器中的高性能模块(2017–2020)典型应用:Figma(设计工具)、AutoCAD Web、视频编辑器价值:将计算密集型任务(如图像处理、物理引擎)从 JS 迁移至 Wasm,提升流畅度// Rust 示例:计算斐波那契数列(编译为 Wasm)#[no_mangle]pub extern "C" fn fibonacci(n: u32) -> u32 { if n <= 1 { return n; } fibonacci(n - 1) + fibonacci(n - 2)}阶段 2:服务端 Wasm(2020–2023)随着 WASI(WebAssembly System Interface) 的提出,Wasm 获得了访问文件、网络、时间等系统能力的标准接口,从而走出浏览器。代表项目:Wasmtime(Bytecode Alliance):独立 Wasm 运行时WasmEdge:面向边缘和 Serverless 优化Deno:内置 Wasm 支持的 JS/TS 运行时阶段 3:通用插件与函数运行时(2023–至今)Wasm 正成为安全、轻量、跨语言插件系统的事实标准:场景案例数据库扩展SingleStore、FerretDB 允许用 Wasm 编写 UDFAPI 网关插件Envoy Proxy 支持 Wasm 过滤器Serverless 函数Shopify Functions、Cloudflare Workers区块链智能合约CosmWasm(Cosmos 生态)CLI 工具插件HashiCorp Vault、Terraform 插件实验三、实战:用 Rust 编写一个 Wasm Serverless 函数我们将创建一个简单的 HTTP 处理函数,部署到 WasmEdge 运行时。1. 初始化项目(使用 cargo-wasi)cargo new --lib hello-wasmcd hello-wasm2. 修改 Cargo.toml[package]name = "hello-wasm"version = "0.1.0"edition = "2021"[lib]crate-type = ["cdylib"] # 编译为动态库(Wasm 模块)[dependencies]wasmedge-bindgen = "0.4"wasmedge-macro = "0.4"3. 编写函数逻辑// src/lib.rsuse wasmedge_bindgen::*;use wasmedge_macro::*;#[async_func]pub fn handle_request(request: Vec<u8>) -> Vec<u8> { let name = std::str::from_utf8(&request).unwrap_or("Guest"); format!("Hello, {}! Time: {}", name, chrono::Utc::now()).into_bytes()}4. 编译为 Wasmcargo build --target wasm32-wasi --release# 输出: target/wasm32-wasi/release/hello_wasm.wasm5. 在 WasmEdge 中运行# 安装 WasmEdgecurl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/master/utils/install.sh | bash# 启动 HTTP 服务wasmedge --http-addr=0.0.0.0:3000 hello_wasm.wasm现在访问 http://localhost:3000/Alice,将返回:Hello, Alice! Time: 2025-11-17 08:30:45 UTC优势:冷启动 < 10ms内存占用 < 5MB自动沙箱隔离,无法访问主机文件系统四、Wasm vs 容器:为何它是“后容器时代”的答案?维度Docker 容器WebAssembly启动速度秒级毫秒级内存开销100MB+1–10MB镜像大小100MB–1GB100KB–10MB安全模型基于 Linux Namespace/cgroups基于能力的安全(Capability-based)多语言支持需打包完整运行时共享统一运行时冷启动成本高(Serverless 痛点)极低趋势:在边缘计算、FaaS(Function as a Service)、微服务插件等场景,Wasm 正逐步替代轻量级容器。 结语:Wasm 不是未来,而是现在WebAssembly 已从“浏览器性能补丁”蜕变为新一代软件交付与执行的通用基座。它以极致的轻量、安全与跨平台能力,解决了云原生时代的关键痛点:如何在保障安全的前提下,实现极致的资源效率与启动速度。💡 给开发者的行动建议:前端工程师:尝试用 Wasm 优化图像/音视频处理后端工程师:评估 WasmEdge/Wasmtime 作为 FaaS 运行时系统架构师:在插件系统、边缘节点中引入 Wasm 沙箱区块链开发者:关注 CosmWasm、Substrate Wasm 智能合约正如当年 JavaScript 将浏览器变成应用平台,WebAssembly 正在将整个计算世界变成一个安全、高效、可组合的函数网络。这一次,舞台不再局限于浏览器——而是无处不在。
-
JFR(Java Flight Recorder)是 JDK 内置的高性能诊断工具,以极低开销记录 JVM 和应用运行时的关键事件。然而,不当配置可能导致录制开销升高、文件过大或分析困难。尤其在高并发、长时间运行的生产环境(如鲲鹏 ARM64 服务器)中,合理优化 JFR 的使用策略至关重要。本文将从 录制配置、资源控制、事件筛选、分析效率 四个维度,系统讲解如何优化 JFR 的性能表现。一、核心原则:平衡“数据完整性”与“运行开销”JFR 的默认配置(profile.jfc)已针对通用场景做了权衡,但实际业务需根据目标调整:目标推荐策略长期监控(7×24)仅启用关键事件(GC、线程、CPU 采样),降低采样频率故障复现启用详细事件(方法、异常、I/O),短时间高保真录制性能压测对比使用统一配置,确保数据可比性二、优化录制阶段的性能1. 选择合适的预设模板JDK 提供两个内置模板:default.jfc:基础事件(低开销,适合长期运行)profile.jfc:包含方法采样、锁竞争等(中等开销,适合性能分析)建议:# 长期监控用 default-XX:StartFlightRecording=settings=default,duration=1h,filename=/data/jfr/low.jfr# 深度分析用 profile-XX:StartFlightRecording=settings=profile,duration=5m,filename=/data/jfr/high.jfr 毕昇 JDK 还提供 kunpeng-optimized.jfc(如有),可进一步适配 ARM64。2. 自定义 .jfc 配置文件(推荐)复制并修改模板,关闭非必要事件:<!-- custom-low-overhead.jfc --><configuration ...> <event name="jdk.MethodSampling"> <setting name="enabled">false</setting> <!-- 关闭方法采样 --> </event> <event name="jdk.JavaMonitorEnter"> <setting name="enabled">true</setting> <setting name="threshold">10ms</setting> <!-- 仅记录 >10ms 的锁等待 --> </event> <event name="jdk.GCPhasePause"> <setting name="enabled">true</setting> </event></configuration>启动时指定:-XX:StartFlightRecording=settings=/path/to/custom-low-overhead.jfc,...3. 控制录制时长与文件大小避免无限制录制导致磁盘爆满:# 方式1:固定时长duration=10m# 方式2:循环录制(保留最近数据)maxsize=500MB,maxage=1h# 方式3:条件触发(JDK 17+ 支持)-XX:FlightRecorderOptions:repository=/tmp/jfr-cache生产建议:单文件 ≤ 1GB总录制时长 ≤ 30 分钟(除非明确需要长期趋势)4. 调整采样频率(降低 CPU 开销)关键参数:jdk.ThreadCPULoad:线程 CPU 采样间隔(默认 1s)jdk.ExecutionSample:方法栈采样间隔(默认 10ms)在自定义 .jfc 中调整:<event name="jdk.ExecutionSample"> <setting name="period">100ms</setting> <!-- 从 10ms 放宽到 100ms --></event> 注意:采样间隔越长,热点方法识别精度越低。三、减少 I/O 与内存开销1. 使用高速存储路径将 .jfr 文件写入 SSD 或内存盘:filename=/dev/shm/app.jfr # 写入 tmpfs(内存文件系统) 优势:避免磁盘 I/O 成为瓶颈 风险:重启丢失,需及时备份2. 启用压缩(JDK 17+)-XX:FlightRecorderOptions=compress=true可减少 30%~50% 文件体积。3. 避免多进程同时写同一目录每个 Java 进程应使用独立子目录,防止文件锁竞争。四、优化分析阶段的效率1. 使用命令行快速筛查(避免 GUI 开销)# 查看 GC 暂停总时间jfr print --events GCPhasePause app.jfr | grep "duration"# 统计最耗时的 10 个方法jfr summary app.jfr --category "Code" | head -n 102. 在分析机而非生产机运行 JMC将 .jfr 文件拷贝至开发机或专用分析服务器;避免在生产环境启动图形界面工具。3. 使用脚本自动化分析结合 jfr 命令 + Shell/Python 脚本,实现:自动提取关键指标生成性能报告触发告警(如 GC 暂停 > 100ms)示例脚本片段:MAX_PAUSE=$(jfr print --events GCPhasePause app.jfr | awk '/duration/ {print $2}' | sort -nr | head -1)if [ "$MAX_PAUSE" -gt 100000000 ]; then # 100ms in nanoseconds echo "ALERT: Max GC pause exceeds 100ms!"fi五、鲲鹏 ARM64 环境下的特别建议优先使用毕昇 JDK其 JFR 实现针对鲲鹏处理器的缓存、NUMA 架构优化,事件采集效率更高。关注 ARM64 特有事件如:jdk.CPULoad:ARM64 大小核调度可能影响 CPU 利用率jdk.NativeLibrary:验证 native 库是否为 ARM64 编译对比 x86 基线在相同负载下,分别录制 x86 与鲲鹏的 JFR 数据,使用 JMC 的 Compare 功能 定位架构差异点。
-
理解文件⽂件在磁盘⾥,磁盘是永久性存储介质,因此⽂件在磁盘上的存储是永久性的Linux 下⼀切皆⽂件(键盘、显⽰器、⽹卡、磁盘……)0KB 的空⽂件也是占⽤磁盘空间的⽂件是⽂件属性(元数据)和⽂件内容的集合(⽂件 = 属性(元数据)+ 内容)所有的⽂件操作本质是⽂件内容操作和⽂件属性操作对⽂件的操作本质是进程对⽂件的操作, 磁盘的管理者是操作系统⽂件的读写本质不是通过 C 语⾔ / C++ 的库函数来操作的(这些库函数只是为⽤⼾提供⽅便),⽽是通过⽂件相关的系统调⽤接⼝来实现的理解硬盘机械磁盘是计算机中唯⼀的⼀个机械设备磁盘是外设,特点:慢,容量⼤,价格便宜磁盘的物理结构:磁盘的存储结构:扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节。磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同圆盘(platter)数:就是盘⽚的数量磁盘容量=磁头数 × 磁道(柱面)数 × 每道扇区数 × 每扇区字节数细节:传动臂上的磁头是共进退的通过柱⾯(cylinder),磁头(head),扇区(sector),就可以定位数据了,这就是数据定位(寻址)⽅式之⼀,CHS寻址⽅式。磁盘的逻辑结构磁带逻辑结构在理解磁盘的逻辑结构之前我们先来了解一下磁带的逻辑结构: 磁带上面存取数据,当我们把磁带拉直就形成了线性结构 虽然磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于: 磁盘逻辑结构机械臂上的磁头是共进退的 柱⾯是⼀个逻辑上的概念,其实就是每⼀⾯上,相同半径的磁道逻辑上构成柱⾯。所以,磁盘物理上分了很多⾯,但是在我们看来,逻辑上,磁盘整体是由“柱⾯”卷起来的 这样看来一个柱面就是一个二维数组.整盘 故整盘就是多张二维的扇区数组表 “三维数组” .这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做 LBA.CHS && LBA地址CHS转成LBA:磁头数*每磁道扇区数 = 单个柱⾯的扇区总数LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1LBA转成CHS:柱⾯号C = LBA // (磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数扇区号S = (LBA % 每磁道扇区数) + 1在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰转换。故磁盘是⼀个 元素为扇区 的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了。引入文件系统“块” 的概念操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个”块”(block)硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的,并且不可以更改,最常⻅的是4KB,即连续⼋个扇区组成⼀个 ”块”。(每个扇区512B)文件系统和存储管理中,“块(Block)” 是磁盘(或其他存储设备)进行数据读写的最小单位,也是文件系统管理存储空间的基础单元。“分区” 的概念其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你可能会有⼀块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?柱⾯是分区的最⼩单位,我们可以利⽤参考柱⾯号码的⽅式来进⾏分区,其本质就是设置每个区的起始柱⾯和结束柱⾯号码。 柱⾯⼤⼩⼀致,扇区个位⼀致,那么其实只要知道每个分区的起始和结束柱⾯号,知道每⼀个柱⾯多少个扇区,那么该分区多⼤,其实和解释LBA是多少也就清楚了. inode我们知道 ⽂件=内容+属性 ,我们使⽤ ls -l 的时候看到的除了看到⽂件名,还能看到⽂件元数据(属性)每⾏的7列分别代表:模式硬链接数⽂件所有者组⼤⼩最后修改时间⽂件名这个信息除了通过ls来读取,还有⼀个stat命令能够看到更多信息。 我们知道⽂件数据都储存在”块”中,那么很显然,我们还必须找到⼀个地⽅储存⽂件的元信息(属性信息),⽐如⽂件的创建者、⽂件的创建⽇期、⽂件的⼤⼩等等。这种储存⽂件元信息的区域就叫做inode,中⽂译名为”索引节点”。每⼀个⽂件都有对应的inode,⾥⾯包含了与该⽂件有关的⼀些信息。为了能解释清楚inode,我们需要是深⼊了解⼀下⽂件系统。注意:Linux下⽂件的存储是属性和内容分离存储的Linux下,保存⽂件属性的集合叫做inode,⼀个⽂件,⼀个inode,inode内有⼀个唯⼀的标识符,叫做inode号⽂件名属性并未纳⼊到inode数据结构内部inode的⼤⼩⼀般是128字节或者256任何⽂件的内容⼤⼩可以不同,但是属性⼤⼩⼀定是相同的ext2 ⽂件系统认识文件系统所有的准备⼯作都已经做完,是时候认识下⽂件系统了。我们想要在硬盘上储⽂件,必须先把硬盘格式化为某种格式的⽂件系统,才能存储⽂件。⽂件系统的⽬的就是组织和管理硬盘中的⽂件。在 Linux 系统中,最常⻅的是 ext2 系列的⽂件系统。其早期版本为 ext2,后来⼜发展出 ext3 和 ext4。ext3 和 ext4 虽然对 ext2 进⾏了增强,但是其核⼼设计并没有发⽣变化。ext2⽂件系统将整个分区划分成若⼲个同样⼤⼩的块组 (Block Group),如下图所⽰。只要能管理⼀个分区就能管理所有分区,也就能管理所有磁盘⽂件。上图中启动块(Boot Sector)的⼤⼩是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信息和启动信息,任何⽂件系统都不能修改启动块。启动块之后才是ext2⽂件系统的开始。Block GroupBlock Group(块组) 是文件系统管理磁盘空间的核心 “模块化单元”,每ext2⽂件系统会根据分区的⼤⼩划分为数个Block Group。⽽每个Block Group都有着相同的结构组成。每个Block Group自主管理资源,以提升效率和容错性。块组内部构成超级块(Super Block)存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:bolck 和 inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了。GDT(Group Descriptor Table)块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组 的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。块位图(Block Bitmap)Block Bitmap以位图的形式记录着Data Block中哪个数据块已经被占⽤,哪个数据块没有被占⽤。inode位图(Inode Bitmap)每个bit表⽰⼀个inode是否空闲可⽤。(位值为0表示可用,为1不可用)i节点表(Inode Table)存放⽂件属性 如 ⽂件⼤⼩,所有者,最近修改时间等当前分组所有Inode属性的集合inode编号以分区为单位,整体划分,不可跨分区Data Block数据区:存放⽂件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下⼏种情况:对于普通⽂件,⽂件的数据存储在数据块中。对于⽬录,该⽬录下的所有⽂件名和⽬录名存储在所在⽬录的数据块中,除了⽂件名外,ls -l命令看到的其它信息保存在该⽂件的inode中。Block 号按照分区划分,不可跨分区,只能在自己分区内有效不可跨分区inode和datablock映射 12 个直接块指针(直接翻到章节):直接指向存储文件数据的 “普通数据块”(小文件可通过这些指针直接找到所有数据块,因此小文件存储非常高效)。间接块索引指针一级间接块索引指针(“先查目录,再翻章节”):该指针指向了一级数据块索引表(4KB),该索引表中储存了多个普通数据块的编号(每个编号4字节),则索引表中管理了1024个数据块。则可以在直接块的基础上多管理(1024 * 4KB = 4MB)4MB的文件。二级间接块索引指针(“查目录的目录,再翻章节”):该指针指向了数据块索引表,该数据块索引表又可以指向1024个数据块索引表,每个索引块可以管理1024个数据块。则可以在前面的基础上多管理(1024 * 1024 * 4KB = 4GB)4GB的文件。三级间接块索引指针(“查目录的目录的目录,再翻章节”):以此类推,则可以在前面的基础上多管理(1024 * 1024 * 1024 * 4KB = 4TB)4TB的文件。结论:分区之后的格式化操作,就是对分区进⾏分组,在每个分组中写⼊SB、GDT、Block、Bitmap、Inode Bitmap等管理信息,这些管理信息统称: ⽂件系统只要知道⽂件的inode号,就能在指定分区中确定是哪⼀个分组,进⽽在哪⼀个分组确定是哪⼀个inode拿到inode⽂件属性和内容就全部都有了[root@localhost linux]# touch abc[root@localhost linux]# ls -i abc263466 abc 如上图可知创建⼀个新⽂件主要有以下4个操作:1. 存储属性内核先找到⼀个空闲的i节点(这⾥是263466)。内核把⽂件信息记录到其中。2. 存储数据该⽂件需要存储在三个磁盘块,内核找到了三个空闲块:300 ,500,800。将内核缓冲区的第⼀块数据复制到300,下⼀块复制到500,以此类推。3. 记录分配情况⽂件内容按顺序300,500,800存放。内核在inode上的磁盘分布区记录了上述块列表。4. 添加⽂件名到⽬录新的⽂件名abc。linux如何在当前的⽬录中记录这个⽂件?内核将⼊⼝(263466,abc)添加到⽬录⽂件。⽂件名和inode之间的对应关系将⽂件名和⽂件的内容及属性连接起来。目录与文件名我们发现平常访问⽂件,⽤的是⽂件名,并没⽤inode号,那它是怎么访问的呢?答:目录是文件,磁盘上是没有目录这一概念的,目录的内容保存的是当前目录下的文件名和inode的映射关系。所以,访问⽂件,必须打开当前⽬录,根据⽂件名,获得对应的inode号,然后进⾏⽂件访问。所以,访问⽂件必须要知道当前⼯作⽬录,本质是必须能打开当前⼯作⽬录⽂件,查看⽬录⽂件的内容!路径解析我们知道访问文件时需要访问目录文件,可是目录我们也只知道文件名,要访问当前目录也要知道他的inode。故需要一直向上访问上级目录,类似于“递归”,需要把路径中所有的⽬录全部解析,出⼝是"/"根⽬录(根⽬录固定⽂件名,inode号,⽆需查找,系统开机之后就必须知道)。实际上,任何⽂件,都有路径,访问⽬标⽂件,⽐如: /home/whb/code/test/test/test.c 都要从根⽬录开始,依次打开每⼀个⽬录,根据⽬录名,依次访问每个⽬录下指定的⽬录,直到访问到test.c。这个过程叫做Linux路径解析。路径谁提供?你访问⽂件,都是指令/⼯具访问,本质是进程访问,进程有CWD!进程提供路径。你open⽂件,提供了路径.软硬链接创建软硬连接观察,对比它们的inode: 硬链接abc和def的链接状态完全相同,他们被称为指向⽂件的硬链接。内核记录了这个连接数,inode:68511212 的硬连接数为2。硬连接是新文件名与和目标文件inode编号的映射关系;创建硬连接,就是建立映射关系,并且把inode的链接数增加;我们在删除⽂件时⼲了两件事情:在⽬录中将对应的记录删除,将硬连接数-1,如果为0,则将对应的磁盘释放。如上图我们可以发现当前目录下的 . 和下级目录下的 .. 都连接着该目录故他的连接数是3软连接那么软连接是什么?软连接是一个独立的文件,文件内容是目标文件的路径。有点类似于快捷方式.一个目录被创建时,会产生三个连接。其中两个“目录名”和"."指向自己,还有一个".."指向上一级目录。目录可以创建软连接,但是不能创建硬连接!否则会形成环路问题,除非是系统自己建立。————————————————版权声明:本文为CSDN博主「new null」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/2301_80468112/article/details/154292952
-
一、基本框架iptables 是 Linux 系统中常用的防火墙工具,工作在用户空间,用来编写规则,基于内核的 netfilter 框架实现数据包的过滤、转换和修改。其核心框架由 “四表五链” 构成,规则的匹配和执行遵循特定顺序,同时支持自定义链以灵活管理规则。二、iptables 中的链“链” 是数据包流转过程中经过的检查点,每条链对应数据包处理的特定阶段,由内核自动创建,即内置链。对应内核中的每一个勾子函数 (INPUT,OUTPUT,FORWARD,PREROUTING,POSTROUTING)。除了内置链外,用户还可创建自定义链,用于对内置链进行扩展或补充,可实现更灵活的规则组织管理机制;只有钩子函数调用自定义链时,才会生效,即需通过内置链的规则引用自定义链。1、PREROUTING 链数据包进入本机后,路由选择之前经过的链。用于提前修改数据包(如 DNAT),或标记数据包。即主机接收到数据是否是给我们的,是否要修改(ip或port),相当于进站安检。2、INPUT 链当路由判断数据包的目标地址是本机时,经过此链。用于控制哪些外部数据包可进入本机(如允许 SSH 连接)。即接收到的数据经过路由分析,是本机处理,相当与上车。3、FORWARD 链当路由判断数据包需要经过本机转发(本机既非源也非目标)时,经过此链。用于控制转发行为(如网关服务器的转发规则)。即接收到的数据经过路由分析,不是本机处理,本机只做转发,相当于转车。4、OUTPUT 链本机产生的数据包(如应用程序发送的请求)在离开本机前经过此链。用于控制本机对外发送的数据包。即本机处理完的数据经过路由分析,直接传输出去,相当于下车。5、POSTROUTING 链数据包经过路由选择后,离开本机前的最后一条链。用于修改源地址(如 SNAT,将内网地址转换为外网地址)。即数据传输出去的时候,是否需要修改(ip或port),相当于出站安检。三、iptables 中的表iptables 的表用于分类管理不同功能的规则,每个表关联特定的链,主要包括以下四类:filter、nat、mangle、raw。优先级从高到低依次是:raw > mangle > nat > filter。1、filter 表(过滤规则表)最常用的表,也是默认表,根据预定义的规则过滤符合条件的数据包,是防火墙的核心功能。仅关联 3 条链:INPUT、OUTPUT、FORWARD。2、nat 表(地址转换表)用于实现网络地址转换(NAT),包括源地址转换(SNAT)、目标地址转换(DNAT)等。关联 3 条链:PREROUTING(路由前修改目标地址)、POSTROUTING(路由后修改源地址)、OUTPUT(本机产生的数据包的地址转换)。3、mangle 表(修改表)修改数据标记位规则表,修改数据报文(修改数据包的元数据,如 TTL、服务类型、标记等),可辅助路由或过滤。关联所有 5 条链(INPUT、OUTPUT、FORWARD、PREROUTING、POSTROUTING)。4、raw 表(原始表)用于关闭 nat 表启用的连接跟踪机制,减少性能消耗,加快封包穿越防火墙速度,仅处理不需要追踪的数据包。关联 2 条链:PREROUTING、OUTPUT。四、链表对应关系表 可支持的链raw PREROUTING, OUTPUTmangle PREROUTING, POSTROUTING, INPUT, OUTPUT, FORWARDnat PREROUTING, POSTROUTING, INPUT, OUTPUTfilter INPUT, FORWARD, OUTPUT五、数据包流转顺序当一个数据包进入网卡时,数据包首先进入PREROUTING链,内核根据数据包目的IP判断是否需要传送出去;如果数据包是进入本机的,则会进入INPUT链,然后交由本机的应用程序处理;如果数据包是要转发的,且内核允许转发,则数据包在PREROUTING链之后到达FORWARD链,再经由POSTROUTING链输出本机的应用程序往外发送数据包,会先进入OUTPUT链,然后到达POSTROUTING链输出数据包的走向数据流入,本机接收的数据包(目标是本机):PREROUTING 链(raw→mangle→nat)→ 路由判断(目标是本机)→ INPUT 链(mangle→filter)→ 进入本机应用程序。数据流出,本机发出的数据包(源是本机):本机应用程序 → OUTPUT 链(raw→mangle→nat→filter)→ 路由选择 → POSTROUTING 链(mangle→nat)→ 离开本机。数据转发,转发的数据包(经过本机转发):外部数据包 → PREROUTING 链(raw→mangle→nat)→ 路由判断(需要转发)→ FORWARD 链(mangle→filter)→ 路由选择 → POSTROUTING 链(mangle→nat)→ 离开本机。 六、规则匹配顺序iptables 中,每条链内的规则按从上到下的顺序依次匹配:当数据包匹配到某条规则时,会执行该规则的 “目标”(如 ACCEPT、DROP、跳转至其他链等),并停止后续规则的匹配(除非目标是 RETURN,会返回原链继续匹配)。若数据包不匹配链中任何规则,则执行该链的 “默认策略”。七、策略的设置方式1、iptables命令组成iptables 完整命令由以下部份组成:iptables [-t Table] -子命令 <链> <规则策略> [动作]即通过 iptables 命令,在某个表的某个链上设置某条过滤规则字段说明iptables:iptables命令Table:具体要操作的表,用 -t 指定,raw|mangle|nat|filter,默认 filterChain:具体要操作的链,PREROUTING|INPUT|FORWARD|OUTPUT|POSTROUTINGRule:具体规则,由匹配条件和目标组成,如果满足条件,就执行目标中的规则,目标用 -j 指定动作:基本动作ACCEPT|DROP|RETURN2、命令格式指定表-t|--table table #指定表 raw|mangle|nat|filter,如果不显式指定,默认是filter操作链-N|--new-chain chain #添加自定义新链-X|--delete-chain [chain] #删除自定义链(要求链中没有规则)-P|--policy chain target #设置默认策略,对filter表中的链而言,其默认策略有ACCEPT|DROP-E|--rename-chain old-chain new-chain #重命名自定义链,引用计数不为0的自定义链不能被重命名-L|--list [chain] #列出链上的所有规则-S|--list-rules [chain] #列出链上的的有规则-F|--flush [chain] #清空链上的所有规则,默认是所有链-Z|--zero [chain [rulenum]] #置0,清空计数器,默认操作所有链上的所有规则操作具体规则-A|--append chain rule-specification #往链上追加规则-I|--insert chain [rulenum] rule-specification #往链上插入规则,可以指定编号,默认插入到最前面-C|--check chain rule-specification #检查链上的规则是否正确-D|--delete chain rule-specification #删除链上的规则-D|--delete chain rulenum #根据编号删除链上的规则-R|--replace chain rulenum rule-specification #根据链上的规则编号,使用新的规则替换原有规则其它选项-h|--help #显示帮助-V|--version #显示版本-v|--verbose #显示详细信息-n|--numeric #以数字形式显示IP和端口,默认显示主机名和协议名,否则容易遭受hosts解析影响--line-numbers #显示每条规则编号-j|--jump # 决定了数据包在满足特定条件后的命运。ACCEPT:允许数据包通过。DROP:丢弃数据包,不给出任何回应。等客户端测试多次后,主动放弃。REJECT:直接拒绝数据包,并向发送方发送一个错误响应。查看规则选项-L #显示规则条目,后面可以接需要查看的链,不写的话表示所有链。如果规则不为空,单独 -L 选项显示时有可能会很慢,这是因为需要对主机名和服务名进行反解导致的,-n #选项查看规则的时候 主机名和端口不做解析,介于此,可以实现规避上面的问题。-v #显示详细信息--line-numbers #显示规则的标号-S #打印规则,编写命令给我们打印出来,方便我们去学习-t #指定查看的表3、默认策略默认策略(Policy)是当数据包不匹配链中任何规则时的默认处理行为,仅适用于内置链(自定义链无默认策略)。设置命令iptables -P 链名 策略AI写代码bash常用策略:ACCEPT(允许通过)、DROP(直接丢弃,不返回任何信息)、REJECT(拒绝并返回错误信息)。示例# 设置 INPUT 链默认拒绝所有未匹配的数据包iptables -P INPUT DROPAI写代码bash注:默认规则(iptables -P)是 ACCEPT,不建议修改,容易出现 “自杀” 现象。4、常见策略 设置命令# 在第1行插入一条规则iptables -t 表名 -I 链名 策略 # 在原来规则后面去追加iptables -t 表名 -A 链名 策略AI写代码bash常用策略:ACCEPT(允许通过)、DROP(直接丢弃,不返回任何信息)、REJECT(拒绝并返回错误信息)。示例# 在INPUT 链的 filter 表上设置过滤规则,将来自 10.0.0.112 的数据包丢弃掉iptables -t filter -A INPUT -s 10.0.0.12 -j DROPAI写代码bash八、自定义链配置1、创建自定义链创建命令iptables -N 自定义链名AI写代码bash示例# 创建一个名为NGINX_RULES的自定义链iptables -N NGINX_RULESAI写代码bash2、使用自定义链需通过内置链的规则引用自定义链(目标为自定义链名),数据包匹配到该规则时会跳转到自定义链处理。示例# 示例:在 INPUT 链中添加规则,将所有 TCP 80 端口的数据包跳转至 NGINX_RULES 链处理iptables -A INPUT -p tcp --dport 80 -j NGINX_RULESAI写代码bash注:自定义链中若规则匹配,按目标处理;若不匹配,会返回原链继续匹配后续规则。3、删除自定义链删除前需满足以下两个条件:自定义链中无任何规则(需先清空);无其他链的规则引用该自定义链(需先删除引用)。示例# 清空自定义链的规则iptables -F NGINX_RULES # 删除引用该链的规则(如 INPUT 链中指向 NGINX_RULES 的规则)iptables -D INPUT -p tcp --dport 80 -j NGINX_RULES # 删除自定义链iptables -X NGINX_RULES————————————————版权声明:本文为CSDN博主「siriuuus」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/qq_38024995/article/details/154353503
-
一、Linux账号基础1.1 账号类型Linux系统中的账号主要分为三类,不同类型账号的权限和用途差异显著:管理员账号(root):UID(用户标识符)固定为0,是系统中权限最高的账号,拥有对系统的完全控制权限,可执行创建/删除账号、修改系统配置、安装软件等所有操作。需特别注意,root权限过大,日常操作应避免频繁使用,以防误操作引发风险。系统账号:用于运行系统服务和进程的账号,如bin、daemon、ftp、mysql等,其UID范围通常为1-999(不同Linux发行版可能略有差异)。这类账号一般不允许登录系统,仅为保障系统服务正常运行而存在。例如,mysql账号专门用于运行MySQL数据库服务。普通用户账号:由管理员创建,供用户日常使用,UID范围通常为1000及以上。普通用户的权限受严格限制,仅能在自身家目录(/home/用户名)下执行创建、修改、删除文件等操作,若需执行超出权限的操作,需通过sudo命令临时获取管理员权限。1.2 关键文件Linux系统通过多个核心文件存储账号相关信息,修改这些文件需谨慎操作:/etc/passwd:系统用户账号信息的核心文件,所有用户都可读取。该文件每行对应一个用户账号,格式为“用户名:密码占位符:UID:GID:用户说明:家目录:登录Shell”。例如,“zhangsan:x:1001:1001:Zhang San:/home/zhangsan:/bin/bash”,其中“x”表示密码存储在/etc/shadow文件中。/etc/shadow:存储用户密码的加密文件,仅root用户可读取和修改,极大提升了密码安全性。每行格式为“用户名:加密密码:密码最后修改时间:密码最小有效期:密码最大有效期:密码过期前警告天数:密码过期后宽限天数:账号失效时间:保留字段”。/etc/group:存储用户组信息的文件,所有用户可读取。每行格式为“组名:密码占位符:GID:组内成员”,其中组密码占位符若为“x”,表示组密码存储在/etc/gshadow文件中。/etc/gshadow:存储用户组密码的加密文件,仅root用户可读取和修改,用于管理组的访问权限。/etc/default/useradd:用户账号创建的默认配置文件,定义了创建新用户时的默认家目录(如/home)、默认登录Shell(如/bin/bash)、默认用户组等信息。管理员可修改此文件定制默认创建规则。/etc/skel/:存放新用户家目录默认模板文件的目录,当创建新用户时,该目录下的文件(如.bashrc、.bash_profile、.bash_logout等)会自动复制到新用户的家目录中,确保新用户拥有基本的环境配置。二、用户账号管理实操2.1 账号创建:useradduseradd命令用于创建新的Linux用户账号,语法格式为:useradd [选项] 用户名常用选项:-u:指定用户的UID,需确保UID未被占用,例如“useradd -u 1002 lisi”创建UID为1002的用户lisi。-g:指定用户的初始组(主组),可通过组名或GID指定,初始组信息会记录在/etc/passwd文件中,如“useradd -g 1001 lisi”将lisi的主组设为GID为1001的组。-G:指定用户的附加组(次要组),用户可属于多个附加组,多个组之间用逗号分隔,如“useradd -G sudo,wheel lisi”将lisi加入sudo和wheel附加组。-d:指定用户的家目录,默认家目录为“/home/用户名”,若需自定义,可使用“useradd -d /opt/zhangsan zhangsan”将zhangsan的家目录设为/opt/zhangsan。-s:指定用户的登录Shell,默认Shell为/bin/bash,若要创建无法登录的用户(如系统服务账号),可指定Shell为/sbin/nologin,例如“useradd -s /sbin/nologin mysql”。-c:添加用户的备注信息,如“useradd -c "Company Employee" wangwu”备注wangwu为公司员工。-m:强制创建用户的家目录,即使/etc/default/useradd中默认不创建,也会生成家目录并复制/etc/skel/下的模板文件,如“useradd -m zhaoliu”。-r:创建系统账号,系统账号的UID会在1-999范围内自动分配,且默认不创建家目录,如“useradd -r nginx”创建运行Nginx服务的系统账号。示例:创建一个普通用户zhangsan,UID为1003,主组为users(GID为100),附加组为sudo,家目录为/home/zhangsan,备注为“Ordinary User”,登录Shell为/bin/bash:useradd -u 1003 -g users -G sudo -d /home/zhangsan -c "Ordinary User" -s /bin/bash zhangsanAI写代码bash2.2 密码管理:passwdpasswd命令用于修改用户密码,语法格式为:passwd [选项] 用户名,普通用户仅可修改自身密码,root用户可修改所有用户密码。常用选项:-d:清空用户密码,清空后用户无需密码即可登录,存在安全风险,需谨慎使用,如“passwd -d zhangsan”。-l:锁定用户账号,锁定后用户无法登录系统,通过在/etc/shadow文件的密码字段前添加“!”实现,如“passwd -l zhangsan”。-u:解锁被锁定的用户账号,移除/etc/shadow文件密码字段前的“!”,如“passwd -u zhangsan”。-e:强制用户在下次登录时修改密码,登录后系统会提示用户重新设置密码,如“passwd -e zhangsan”。-n 天数:设置密码的最小有效期,即密码修改后至少经过指定天数才能再次修改,如“passwd -n 7 zhangsan”设置zhangsan的密码7天内不可修改。-x 天数:设置密码的最大有效期,超过指定天数后密码失效,用户需重新修改,如“passwd -x 90 zhangsan”设置密码90天有效期。-w 天数:设置密码过期前的警告天数,到期前指定天数系统会提醒用户修改密码,如“passwd -w 7 zhangsan”提前7天警告。-i 天数:设置密码过期后的宽限天数,宽限期内用户仍可登录但需修改密码,超过宽限期账号锁定,如“passwd -i 3 zhangsan”宽限3天。示例:root用户为zhangsan设置密码:输入“passwd zhangsan”,按提示输入两次密码(密码输入时不显示),密码需符合复杂度要求(如长度≥8位,包含字母、数字、特殊符号)。passwd -l zhangsan #锁定zhangsan账号passwd -e zhangsan #强制zhangsan下次登录修改密码AI写代码bash2.3 账号修改:usermodusermod命令用于修改已存在用户的账号信息,语法格式为:usermod [选项] 用户名,其选项与useradd命令类似,可修改UID、用户组、家目录等。常用选项:-u:修改用户的UID,如“usermod -u 1004 zhangsan”将zhangsan的UID改为1004。-g:修改用户的主组,如“usermod -g admin zhangsan”将zhangsan的主组改为admin组。-G:修改用户的附加组,使用“-G”时会覆盖原附加组,若要保留原附加组并添加新组,需结合“-a”选项(append),如“usermod -aG dev zhangsan”将zhangsan添加到dev附加组,同时保留原有附加组。-d:修改用户的家目录,若需将原家目录内容移动到新目录,需结合“-m”选项,如“usermod -d /home/zs -m zhangsan”将zhangsan的家目录改为/home/zs,并迁移原有文件。-s:修改用户的登录Shell,如“usermod -s /bin/sh zhangsan”将zhangsan的登录Shell改为/bin/sh。-c:修改用户的备注信息,如“usermod -c "Technical Support" zhangsan”更新zhangsan的备注为技术支持。-l:修改用户名,如“usermod -l zs zhangsan”将用户名从zhangsan改为zs,需注意修改后家目录名不会自动变更,需手动修改。-L:锁定用户账号,与passwd -l功能类似,如“usermod -L zhangsan”。-U:解锁用户账号,与passwd -u功能类似,如“usermod -U zhangsan”。示例:将zhangsan的附加组添加dev组,修改备注为“Developer”,登录Shell改为/bin/bash:usermod -aG dev -c "Developer" -s /bin/bash zhangsanAI写代码bash2.4 账号删除:userdeluserdel命令用于删除Linux用户账号,语法格式为:userdel [选项] 用户名常用选项:-r:删除用户的同时删除其家目录和邮件目录,若不使用此选项,仅删除账号信息,家目录会保留,如“userdel -r zhangsan”彻底删除zhangsan账号及相关文件。注意事项:删除用户前,需确保该用户未处于登录状态,若用户正在登录,删除可能失败或导致系统异常,可通过“who”或“w”命令查看当前登录用户,使用“kill”命令终止其进程后再删除。若用户拥有正在运行的服务进程,删除前需先停止相关服务,避免进程残留引发问题。示例:userdel -r lisi #彻底删除lisi账号及其家目录AI写代码bash三、用户组管理实操3.1 组的概念与分类用户组是将多个用户集合在一起的逻辑单位,通过组管理可批量分配权限,简化权限管理操作。Linux用户组分为两类:主组(初始组):用户创建时默认所属的组,每个用户仅能有一个主组,主组信息存储在/etc/passwd文件的GID字段中。若创建用户时未指定主组,系统会自动创建一个与用户名同名的组作为主组。附加组(次要组):用户可加入的额外组,一个用户可属于多个附加组,附加组信息存储在/etc/group文件中。通过附加组,用户可获得多个不同的权限集合。3.2 组创建:groupaddgroupadd命令用于创建新的用户组,语法格式为:groupadd [选项] 组名常用选项:-g:指定组的GID,确保GID未被占用,如“groupadd -g 1005 dev”创建GID为1005的dev组。-r:创建系统组,系统组的GID范围通常为1-999,如“groupadd -r nginx”创建Nginx服务的系统组。示例:groupadd -g 1006 testgroup #创建一个名为testgroup的普通用户组,GID为1006AI写代码bash3.3 组修改:groupmodgroupmod命令用于修改已存在用户组的信息,语法格式为:groupmod [选项] 组名常用选项:-g:修改组的GID,如“groupmod -g 1007 dev”将dev组的GID改为1007。-n:修改组的名称,如“groupmod -n newdev dev”将dev组的名称改为newdev。示例:groupmod -g 1008 -n newtest testgroup #将testgroup组的名称改为newtest,GID改为1008AI写代码bash3.4 组删除:groupdelgroupdel命令用于删除用户组,语法格式为:groupdel 组名注意事项:不能删除用户的主组,若要删除某组,需先将所有以该组为主组的用户修改主组或删除用户。确保组内无用户(包括主组和附加组用户)时再删除,避免残留关联信息。示例:groupdel newtest #删除newtest组(需确保该组不是任何用户的主组且无附加组用户)AI写代码bash3.5 用户组归属调整:gpasswdgpasswd命令用于管理用户组的成员,可添加或删除组内用户,语法格式为:gpasswd [选项] 组名常用选项:-a 用户名:将指定用户添加到组中,如“gpasswd -a zhangsan dev”将zhangsan添加到dev组。-d 用户名:将指定用户从组中删除,如“gpasswd -d zhangsan dev”将zhangsan从dev组中移除。-M 用户名1,用户名2,...:批量设置组内成员,会覆盖原组内成员,如“gpasswd -M zhangsan,lisi dev”将dev组的成员设为zhangsan和lisi。-A 用户名1,用户名2,...:指定组的管理员,组管理员可添加/删除组内成员,如“gpasswd -A zhangsan dev”指定zhangsan为dev组管理员。示例:将lisi添加到dev组,将wangwu从dev组删除,指定zhangsan为dev组管理员:gpasswd -a lisi dev gpasswd -d wangwu dev gpasswd -A zhangsan devAI写代码bash四、Linux权限管理核心4.1 权限的基本概念Linux权限用于控制不同用户(所有者、所属组、其他用户)对文件或目录的操作权限,核心分为读(r)、写(w)、执行(x)三种基本权限,不同权限对应不同的操作范围:读权限(r,对应数字4): 文件:允许读取文件内容,如使用cat、more、less等命令查看文件。目录:允许列出目录内的文件和子目录,如使用ls命令查看目录内容。写权限(w,对应数字2): 文件:允许修改文件内容,如使用vi、echo等命令编辑文件,也可删除文件(需目录有写权限)。目录:允许在目录内创建、删除、重命名文件或子目录,如使用touch、mkdir、rm等命令。执行权限(x,对应数字1): 文件:允许执行该文件,若为可执行程序(如Shell脚本、二进制文件),可直接运行,如“./script.sh”。目录:允许进入该目录,如使用cd命令切换到该目录,若没有执行权限,即使有读权限也无法进入目录。权限的归属对象分为三类,依次对应文件的所有者、所属组、其他用户:所有者(Owner):创建文件或目录的用户,对文件拥有最高控制权,可修改权限和归属。所属组(Group):文件或目录所属的用户组,组内用户共享该组的权限。其他用户(Others):除所有者和所属组用户外的其他所有用户,权限受最严格限制。4.2 权限的表示方法4.2.1 符号表示法符号表示法通过“r、w、x”组合表示权限,共9个字符,分为三组,每组3个字符,依次对应所有者、所属组、其他用户的权限。例如,“-rwxr-xr--”的含义解析如下:第一个字符“-”:表示文件类型,“-”为普通文件,“d”为目录,“l”为软链接,“b”为块设备文件,“c”为字符设备文件等。第2-4个字符“rwx”:所有者权限,拥有读、写、执行权限。第5-7个字符“r-x”:所属组权限,拥有读、执行权限,无写权限(“-”表示无对应权限)。第8-10个字符“r--”:其他用户权限,仅拥有读权限,无写和执行权限。4.2.2 数字表示法数字表示法将r、w、x权限分别对应数字4、2、1,通过权限对应的数字相加得到权限值,共三位数字,依次对应所有者、所属组、其他用户的权限值。例如:rwxr-xr--:所有者权限4+2+1=7,所属组权限4+0+1=5,其他用户权限4+0+0=4,因此数字表示为754。rw-r--r--:所有者6(4+2),所属组4,其他用户4,数字表示为644(普通文件默认权限)。rwxrwxr-x:所有者7,所属组7,其他用户5,数字表示为775。drwxr-xr-x:目录默认权限,数字表示为755,第一个字符“d”表示目录。4.3 权限查看:ls -l使用“ls -l”命令可查看文件或目录的详细信息,包括权限、所有者、所属组、大小、修改时间等。例如:ls -l /home/zhangsan/test.txt #-rwxr-xr-- 1 zhangsan dev 1024 Nov 6 10:00 /home/zhangsan/test.txtAI写代码bash各字段含义依次为:权限(-rwxr-xr--)、硬链接数(1)、所有者(zhangsan)、所属组(dev)、文件大小(1024字节)、修改时间(11月6日10:00)、文件路径和名称。若要查看目录的权限,可直接执行“ls -l 目录路径”,如“ls -l /home/zhangsan”。五、权限修改实操5.1 修改权限:chmodchmod命令用于修改文件或目录的权限,语法格式有两种:符号格式和数字格式。5.1.1 符号格式语法:chmod [归属对象][操作符][权限] 文件/目录归属对象:u(所有者)、g(所属组)、o(其他用户)、a(所有用户,默认)。操作符:+(添加权限)、-(移除权限)、=(设置权限,覆盖原有权限)。权限:r(读)、w(写)、x(执行)。示例:chmod u+w test.txt #给test.txt文件的所有者添加写权限chmod g-x test.txt #移除test.txt所属组的执行权限chmod o=rx test.txt #给其他用户设置读和执行权限chmod a+r test.txt 或 chmod +r test.txt #给所有用户添加读权限chmod -R u+x dev_dir#给目录dev_dir及其下所有文件和子目录的所有者添加执行权限(-R递归修改)AI写代码bash5.1.2 数字格式语法:chmod [权限数字] 文件/目录,权限数字为三位,对应所有者、所属组、其他用户的权限值。示例:chmod 754 test.txt #将test.txt权限设置为rwxr-xr--(754) chmod -R 775 dev_dir #将目录dev_dir权限设置为rwxrwxr-x(775),并递归修改子内容 chmod 744 script.sh #将脚本script.sh设置为所有者可执行(其他用户无执行权限)————————————————版权声明:本文为CSDN博主「UCH1HA」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/yzbtom01/article/details/154479323
-
一、核心定位:Vue 实例作为组件的“宇宙中心”在 Vue 中,this 指向当前组件的实例对象。这个实例是 Vue 框架为您创建的、管理组件所有状态和行为的统一上下文环境。您可以将其理解为一个精心设计的“黑匣子”或“宇宙中心”,所有组件相关的操作都通过这个中心的接口(this)进行。设计哲学:通过提供一个统一的 this 上下文,Vue 极大地简化了组件开发的复杂度。开发者不需要关心数据如何绑定、更新如何调度,只需要通过 this 来“声明”你的意图。二、深度原理剖析:this 的绑定魔法Vue 如何确保在方法中访问的 this 总是指向组件实例?这并非 JavaScript 的自然行为,而是 Vue 在组件初始化阶段精心策划的结果。1. 初始化阶段的“代理”与“绑定”当我们执行 new Vue(options) 时,Vue 内部会进行一系列关键操作(以下是高度简化的伪代码):// 伪代码:模拟 Vue 构造函数的核心逻辑function Vue(options) { const vm = this; // vm 代表 Vue 实例 // 1. 将选项混入实例 vm.$options = options; // 2. 初始化数据响应式系统(核心中的核心) initData(vm); // 3. 关键步骤:处理 methods,进行 this 绑定 initMethods(vm, options.methods); // 4. 初始化计算属性、侦听器等 // ...} function initMethods(vm, methods) { for (const key in methods) { // 将每个方法挂载到实例上,并通过 bind 永久绑定 this vm[key] = methods[key].bind(vm); // 魔法发生在这里! // 现在,无论这个方法如何被调用,其内部的 this 都被锁定为 vm }} function initData(vm) { let data = vm.$options.data; data = vm._data = typeof data === 'function' ? data.call(vm) : data || {}; // 代理:使得 this.xxx 能直接访问 this._data.xxx for (const key in data) { proxy(vm, '_data', key); // 通过 Object.defineProperty 进行代理 } // 将 data 变为响应式 observe(data);}AI写代码javascript运行关键解读:**bind 的威力**:methods 中的每个函数都被显式地通过 .bind(vm) 将 this 永久绑定到实例。这是解决 this 指向问题的根本。数据代理:当你访问 this.message 时,Vue 通过代理机制将其转换为访问 this._data.message。这创造了一种“数据直接挂在实例上”的直观体验。2. 箭头函数的“免疫”现象及其原理箭头函数是 ES6 的语法糖,它的核心特性之一是没有自己的 this 绑定,其内部的 this 继承自外部的词法作用域。export default { data() { return { count: 0 }; }, methods: { // ✅ 正确:普通函数,this 被 Vue 绑定到实例 incrementRegular() { this.count++; // this 指向组件实例 }, // ❌ 危险:箭头函数,this 在定义时就被词法绑定,无法被 Vue 重写 incrementArrow: () => { // 这里的 this 不会是组件实例! // 在严格模式下可能是 undefined,非严格模式下可能是 window(浏览器) // 或 global(Node.js) console.log(this); // undefined 或 Window this.count++; // TypeError: Cannot read properties of undefined } }}AI写代码javascript运行export default { created() { // 这个普通函数的 this 可以被 Vue 绑定 const regularFunc = function() { console.log(this); }; // 这个 this 可以被 bind 影响 // 这个箭头函数的 this 在定义时就已确定(继承自 created 函数的作用域) const arrowFunc = () => { console.log(this); }; // 这个 this 在定义时已固定,bind 无效 }}AI写代码javascript运行当你在 methods 中使用箭头函数时:methods: { brokenMethod: () => { // 这个箭头函数在定义时,它的外部作用域是模块作用域(通常是 undefined 或 global)。 // 由于箭头函数的 this 是词法决定的,Vue 的 bind 操作无法覆盖它。 console.log(this); // 不会是组件实例! }}AI写代码javascript运行根本原因:Vue 的 bind 操作只对普通函数有效,而箭头函数对 bind 调用是“免疫”的。Vue 无法改变词法作用域决定的 this。三、this 的能力图谱:实例的完整 API组件实例是一个功能极其丰富的对象。我们可以将其 API 系统化地分为以下几个层面:API 类别 核心成员 作用与说明数据相关 this.$data, this.$props 访问响应式数据和属性(通常直接通过 this.xxx 代理访问)DOM/组件引用 this.$refs 访问模板中标记的 DOM 元素或子组件实例事件通信 this.$emit, this.$on, this.$off 组件间通信的核心机制生命周期 this.$forceUpdate(), this.$nextTick() 强制刷新、等待 DOM 更新应用级依赖 this.$router, this.$store, this.$http 通过插件注入的全局服务(如 Vue Router, Vuex, Axios)高级特性 this.$slots, this.$scopedSlots, this.$attrs, this.$listeners 用于高级组件开发,处理插槽、属性透传等简单举例:1. 访问响应式数据 (data, props)export default { props: ['userName'], // 来自父组件 data() { return { count: 0, message: 'Hello' }; }, methods: { updateData() { // 访问 data 中定义的数据 this.count = 10; this.message = 'Updated'; // 访问 props 中接收的数据 console.log(this.userName); // 注意:不要直接修改 props(除非是对象/数组,但也不推荐) // this.userName = 'New Name'; // ❌ 反模式 } }}AI写代码javascript运行2. 调用方法 (methods) 和获取计算属性 (computed)export default { data() { return { firstName: '张', lastName: '三' }; }, computed: { // 计算属性像数据一样被访问 fullName() { return this.firstName + this.lastName; } }, methods: { greet() { // 调用其他方法 this.formatMessage(); // 访问计算属性 alert(`你好,${this.fullName}!`); }, formatMessage() { // 方法逻辑 } }}AI写代码javascript运行3. 操作 DOM 与子组件 ($refs)export default { methods: { focusInput() { // 访问模板中 ref="myInput" 的元素 this.$refs.myInput.focus(); }, callChildMethod() { // 访问子组件实例并调用其方法 this.$refs.childComponent.someMethod(); } }, mounted() { // 组件挂载后,$refs 才可用 this.focusInput(); }}AI写代码javascript运行4. 事件通信 ($emit, $on, $off)export default { methods: { submitForm() { // 向父组件发送事件 this.$emit('form-submitted', { data: this.formData }); // 全局事件总线(如果已设置) this.$bus.$emit('global-event', data); } }}AI写代码javascript运行5. 应用级全局属性 ($router, $store, $api)当使用了 Vue Router、Vuex 或注入了全局属性时:export default { methods: { navigateToAbout() { // 访问 Vue Router 实例 this.$router.push('/about'); }, updateUser() { // 访问 Vuex Store this.$store.dispatch('user/update', this.userData); }, async fetchData() { // 访问全局注入的 API 实例 const data = await this.$api.get('/data'); } }}AI写代码javascript运行6. $nextTick 的深度理解 这是 Vue 异步更新机制的关键。当您修改了响应式数据后,DOM 并不会立即更新,而是被推入一个队列。this.$nextTick 接收一个回调函数,这个回调会在下一次 DOM 更新循环结束后执行。这是保证能获取到最新 DOM 的唯一方式。export default { methods: { updateMessage() { this.message = 'Updated'; // 数据已变,但 DOM 还未渲染 // 错误:此时 DOM 还是旧的 console.log(this.$el.textContent); // 可能是旧值 this.$nextTick(() => { // 正确:此时 DOM 已更新 console.log(this.$el.textContent); // 'Updated' }); } }}AI写代码javascript运行四、范式转变:组合式 API 中对 this 的“祛魅”Vue 3 的组合式 API 是革命性的,它从根本上改变了我们与组件实例交互的方式。1. 为什么 setup() 中没有 this?import { ref, onMounted, getCurrentInstance } from 'vue'; export default { setup() { // ❌ 这将是 undefined console.log(this); // undefined // ✅ 使用 ref 创建响应式数据 const count = ref(0); // ✅ 使用生命周期钩子函数 onMounted(() => { console.log('组件挂载完毕'); }); // ✅ 在极少数需要访问实例的情况下(不推荐) const instance = getCurrentInstance(); console.log(instance); // 获取当前组件实例的代理对象 return { count // 必须 return 才能在模板中使用 }; }}AI写代码javascript运行2. 新的心智模型:显式优于隐式组合式 API 鼓励“显式”声明依赖,而不是依赖“隐式”的 this。// 选项式 API (Implicit)export default { data() { return { count: 0 } }, methods: { increment() { this.count++; // 隐式地通过 this 访问 count } }} // 组合式 API (Explicit)import { ref } from 'vue';export default { setup() { // 1. 显式地声明响应式数据 const count = ref(0); // 2. 显式地声明函数 const increment = () => { count.value++; // 显式地通过 .value 访问 ref }; // 3. 显式地暴露给模板 return { count, increment }; }}AI写代码javascript运行优势:更好的 TypeScript 支持:类型推导不再需要魔法。逻辑提取与复用:函数和响应式状态可以轻松提取到外部函数。更小的压缩体积:方法名可以被压缩,而 this 上的属性名通常不行。3. 获取实例的应急方案在极少数情况下(如需要访问插件注入的 $router),可以使用 getCurrentInstance,但通常不推荐,因为它破坏了组合式函数的纯粹性。import { getCurrentInstance } from 'vue'; export default { setup() { const instance = getCurrentInstance(); // 获取当前实例代理 // 通过 .proxy 访问实例上的属性(相当于选项式 API 中的 this) console.log(instance.proxy.$router); // 主要用于高级库开发,普通业务代码应避免使用。 }}AI写代码javascript运行五、专家级陷阱与性能优化1. 闭包陷阱export default { data() { return { importantData: 'secret' }; }, methods: { setupEventListener() { // ❌ 陷阱:将包含 this 的方法传递给外部库 externalLib.on('update', this.handleUpdate); }, handleUpdate() { console.log(this.importantData); // 即使组件销毁,这个方法仍可能被调用,this 指向原实例,可能导致内存泄漏。 } }, beforeUnmount() { // ✅ 解决方案:必须清理! externalLib.off('update', this.handleUpdate); }}AI写代码javascript运行2. 方法作为 Prop 的潜在问题将方法作为 prop 传递给子组件时,this 的绑定需要小心。// 父组件methods: { parentMethod() { console.log(this); // 指向父组件实例 }} // 子组件export default { props: ['parentMethod'], created() { // 直接调用,parentMethod 内的 this 仍指向父实例,这是符合预期的。 this.parentMethod(); }}AI写代码javascript运行3. 异步回调中的 this 丢失export default { data() { return { value: '' }; }, methods: { // ❌ 常见错误:异步回调中 this 指向改变 fetchData() { setTimeout(function() { // 普通函数,this 指向 Window(或严格模式下 undefined) this.value = 'New Value'; // 错误! }, 1000); }, // ✅ 解决方案1:使用箭头函数(继承外部 this) fetchDataCorrect() { setTimeout(() => { this.value = 'New Value'; // 正确! }, 1000); }, // ✅ 解决方案2:提前保存 this fetchDataCorrect2() { const self = this; // 或使用 vm setTimeout(function() { self.value = 'New Value'; // 正确! }, 1000); } // ✅ 解决方案3:使用 .bind(this) fetchDataCorrect3() { setTimeout(function() { this.value = 'New Value'; // 正确! }.bind(this), 1000); } }}AI写代码javascript运行总结:Vue 中 this 的演进与哲学维度 选项式 API (Vue 2) 组合式 API (Vue 3)核心心智模型 “面向实例”编程:所有东西都组织在 this 上下文中。 “函数式”编程:通过导入函数和引用与组件交互。this 的角色 组件的统一入口,是框架魔法的体现。 被“祛魅”,在 setup 中不存在,鼓励显式逻辑。优势 结构规整,易于初学者理解单个组件的结构。 逻辑复用性极佳,TypeScript 支持友好,代码组织更灵活。劣势 逻辑关注点分散,大型组件易形成“面条代码”,类型推导困难。 学习曲线更陡峭,需要更好的代码组织能力。作为专家的建议:新手到专家之路:深刻理解选项式 API 中 this 的绑定原理,是解决诡异 Bug 的钥匙。现代开发范式:拥抱组合式 API 的显式思想,理解其“没有 this”的设计是框架的一种进化,旨在解决大规模应用的复杂性。底层原理:无论是哪种 API,Vue 的响应式系统、依赖收集、异步更新队列这些底层机制始终是核心。this 只是访问这些机制的一个(正在演变的)接口。希望这份深入的分析能帮助您彻底掌握 Vue 中 this 的精髓。————————————————版权声明:本文为CSDN博主「safestar2012」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/safestar2012/article/details/154454241
-
1、简介Spring Cloud 来源于 Spring,质量、稳定性、持续性都可以得到保证。Spirng Cloud 天然支持 Spring Boot,更加便于业务落地。相比于其它框架,Spring Cloud 对微服务周边环境的支持力度最大。使用门槛较低核心特性:分布式/版本化配置,服务注册和发现,路由。服务和服务之间的调用,负载均衡。断路器,分布式消息传递2、Eureka:注册中心三个核心角色:服务注册中心(Eureka Server):Eureka的服务端应用,提供服务注册和发现功能服务提供者(Service Provider):将自身的服务注册到Eureka中,从而使消费方能找到服务消费者(Server Consumer):消费方从注册中心获取服务列表,找到消费服务自我保护机制:“好我不如赖活着”:该模式被激活后,它不会从注册列表中剔除因长时间没收到心跳导致租期过期的服务,而是等待修复,直到心跳恢复正常之后,它自动退出自我保护模式。这种模式旨在避免因网络分区故障导致服务不可用的问题。3、CAP原则:强一致性,可用性,分区容错性CAP 原则指的是,这三个要素最多只能同时实现两点,不可能三者兼顾。Eureka与Zookeeper区别:Eureka:保证AP:Eureka 各个节点是平等的,几个节点挂掉不会影响正常工作,只要有一台 Eureka 存在,就可以保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点没有正常的心跳,那么 Eureka 就会认为客户端与注册中心出现了故障,此时会出现以下几种情况: Eureka 不再从注册列表中移出因长时间没收到心跳而应该过期的服务Eureka 仍然能够接受新服务的注册和查询要求,但是不会被同步到其他节点上(即保证当前节点依然可用) 当网络稳定时,当前实例新的注册信息会被同步到其他节点中Zookeeper:保证CP:当向注册中心查询服务列表时,我们可以容忍注册中心返回的是几分钟以前的信息,但不能就收服务直接 down 掉不可用。也就是说服务注册的可用性要高于一致性。Master节点故障 à 选举时间过长 à 服务瘫痪 à 最终恢复,时间过长,不能容忍总结:eureka可以很好的应对网络故障导致部分节点市区联系的情况,而不会想zookeeper那样使整个服务瘫痪。4、Ribbon:负载均衡(常用于微服务或分布式集群)英文名称为Load Balance,其含义就是指将负载(工作任务)进行平衡、分摊到多个操作单元上进行运行置负载均衡实现RestTemplate@LoadBalanced //ribbon在微服务启动的时候去加载我们自定义的ribbon@RibbonClient(name="SPRINGCLOUD-PROVIDER-DEPT",configuration= DuanRule.class)5、Feign:Feign集成了RibbonRibbon+eureka是面向微服务编程,而Feign是面向接口编程。调用微服务访问的两种方法:微服务的名字【Ribbon】,接口和注解【Feign】@EnableFeignClients(basePackages = {"com.duan.springcloud"})6、Hystrix:熔断器服务雪崩:一个服务失败,导致整条链路的服务都失败的情形,我们称之为服务雪崩。服务熔断(服务端:保险丝):@HystrixCommand(fallbackMethod = "hystrixGet")一旦调用的服务抛出错误信息,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法服务降级(客户端):@FeignClient(value = "SPRINGCLOUD-PROVIDER-DEPT",fallbackFactory = DeptClientServiceBackFactory.class)当某个服务熔断或关闭后,服务不再被调用,此时,再客户端准备一个FallBackFactory,返回一个默认值,集体服务下降,但是好歹能用,比挂掉强7、Dashboard流监控:服务监控@ EnableHystrixDashboard开启仪表盘监控注解监控界面输入要监控的微服务(http://localhost:8001/hystrix.stream)一圆:实心圆:健康程度:绿色<黄色<橙色<红色 递减(监控请求流量)一线:曲线:用来记录流量2分钟内的变化,可以用来观察流量的上升下降的趋势图:左三:成功数、熔断数、错误请求数,右三:超时数,线程池拒绝数,失败异常数 右下方:服务请求频率,断路状态8、Zuul:路由网关包含了对请求的路由和过滤两个主要功能:路由负责将外部请求转发到具体的实例上,是实现外部访问统一入口的基础,过滤负责对请求的处理过程进行干预,是实现请求校验,服务聚合等功能的基础。注意:Zuul服务最终还是会注册到Eureka提供:代理+路由+过滤三大功能routes: mydept.serviceId: springcloud-provider-dept mydept.path: /mydept/**ignored-services: springcloud-provider-dept #不能使用这个路径访问 ignored 忽略prefix: /duan #设置共用的前缀9、Config:分客户端和服务端两个部分:集中管理配置文件以及动态调整配置服务端:又称为分布式配置中心,它是一个独立的微服务应用,用来连接配置服务器并未客户端提供配置信息,加密,解密信息等访问接口客户端:通过指定的配置中心管理应用资源以及业务相关配置,配置服务器默认采用git来存储信息,有助于版本管理,可以通过git客户端工具管理配置访问内容Git环境搭建:注册GITEE码云 à 新建仓库 à 克隆/下载SSH à服务端连接Git配置:克隆到本地文件夹:$ git clone git@gitee.com:duan0719/springcloud-config.git提交本地文件到码云:git add . à git status à git commit -m “description” à git push origin masterserver.port: 3344spring. Application.name: springcloud-config-serverspring.cloud.config.server.git.uri: https://gitee.com/duan0719/springcloud-config.gitspring.cloud.config.server.git.force-pull: truespring.cloud.config.server.git.username: duan0719spring.cloud.config.server.git.password: DX19950717客户端连接服务端访问远程:bootstrap.yml系统级别的配置:spring.cloud.config.uri: http://localhost:3344spring.cloud.config.name: config-clientspring.cloud.config.profile: devspring.cloud.config.label: masterapplication.yml用户级别配置:spring.application.name: springcloud-config-client-3355获取config-client.yml中属性值:@Value("${spring.application.name}")private String applicationName;@Value("${eureka.client.service-url.defaultZone}")private String eurekaServer;@Value("${server.port}")private String port;————————————————版权声明:本文为CSDN博主「众俗」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/m0_46359551/article/details/154349780
-
大家好!我是大聪明-PLUS!有一天,我上班时接到一个任务,需要在单板电脑上构建并运行 Linux。作为一名微控制器软件开发人员,我有点不知所措——这个任务显然不能通过安装 IDE 并点击“构建项目”来解决。谷歌帮我找到了一个叫做 Buildroot 的工具。相关资料让整个过程看起来非常简单:下载、配置、运行几个命令,将结果上传到单板电脑,就可以开始了!所以,这个过程不会比在普通电脑上安装 Linux 或 Windows 发行版复杂多少?当然不会。网上有很多信息,但都很零散,而且大多是英文的。要了解各个功能以及在哪里配置,需要翻阅大量的资料。而且,你很难保证所有东西都能在脑子里拼凑起来。因此,在经历了漫长而艰辛的知识积累、系统化和实践应用的旅程后,我决定撰写这一系列文章。在本系列文章中,我将尽可能清晰、透彻地解释嵌入式 Linux 以及 Buildroot 的具体内容。我将描述一个基于嵌入式 Linux (EL)的通用片上系统(SoC)场景,避免涉及具体芯片的具体细节。由于这些文章面向的是 Linux 初学者,我事先并不了解他们的 Linux 水平,因此我可能会对某些要点进行过于详细的解释。 1. 简介 1.1 什么是片上系统?简单来说,SoC就是包含处理器、接口和硬件组件的微芯片。熟悉微控制器的人自然会问:“那有什么区别?微控制器不是一回事吗?”事实上,任何微控制器都具备上述所有特性。但主要区别不在于硬件,而在于编程方法和开发人员使用的抽象层次。为了理解微控制器和SoC的区别,让我们比较一下创建嵌入式解决方案的三种主要方法——从最简单到最复杂:无操作系统的微控制器这是最便宜、最简单的解决方案,非常适合高度专业化的任务,例如控制伺服驱动器或从传感器读取数据。这些系统的特点是功耗极低、响应速度快。这类微控制器的编程几乎总是低级的:直接访问寄存器,即使有封装器,也只需要极少的封装。有时,你唯一可用的就是.h描述寄存器的头文件和数据手册。每个特定的微控制器型号都有自己的一套外设和寄存器,因此代码通常高度依赖于平台。这里的一个例外是,例如 STM 的 HAL,它允许在 STM32 系列内从一个芯片到另一个芯片进行相对轻松的代码传输。带 RTOS 的微控制器下一步是使用实时操作系统 (RTOS),例如 FreeRTOS 或 SYS-BIOS。这将引入多任务处理(不要与多线程混淆)、驱动程序和硬件抽象。这样的系统可以以更少的开销执行“繁重”的任务,因为操作系统的存在允许开发更复杂、更灵活的代码。例如,与传感器的交互可以分解为由任务调度程序管理的不同任务。代码不再局限于特定平台——只要 RTOS 支持,大部分代码都可以在不同的微控制器上复用。然而,局限性仍然存在:资源仍然有限,通常内存空间有限,而且没有完善的文件系统。片上系统在这个层面上,真正的“成人”生活开始了:多核处理器、兆字节的 RAM、千兆字节的存储空间、硬件视频处理模块、各种接口(如 HDMI、蓝牙和 USB)——所有这些都集成在一个芯片中。从外部来看,SoC 可能看起来只是一个“强大的微控制器”,但实际上它已经是一台可以安装嵌入式 Linux 的功能齐全的计算机。从这一点开始,开发人员不再直接使用寄存器,而是通过 Linux 内核、驱动程序、DeviceTree 文件和大量工具(从编译器到包管理器)进行操作。尤其重要的是,代码尽可能地可移植。任何与特定芯片不直接相关的功能(例如 GPIO 控制)都可以在不同的 SoC 之间保持一致。硬件差异在 Linux 内核和设备树层面被抽象化。不可移植的部分通常是制造商的 SDK——一组用于处理特定 SoC 的独特硬件块的特定库。需要澄清的是,我们讨论的是专为运行成熟操作系统而设计的 SoC。当然也有一些更简单的 SoC,例如 ESP32,其本质更接近微控制器,并且通常无需 Linux 即可运行。因此,SoC 不仅仅是一个“高级微控制器”,而是一个完全不同架构的设备类别。它能够解决需要资源、多线程、成熟操作系统和复杂数据处理的任务。主要区别在于与硬件“捆绑”的软件级别。如果微控制器是程序员直接控制硬件的设备,那么 SoC 就是程序员通过成熟的操作系统和驱动程序模型工作的平台。 1.2 什么是嵌入式Linux?EL 不是一个特定的发行版,甚至不是一个产品。它是一个完整的概念:使用 Linux 内核及其相关的嵌入式软件堆栈来管理除传统计算机之外的设备的想法。通常 EL 包括:装载机Linux内核根文件系统库、服务最终结果是一个独立的操作系统,但它是专门针对特定设备的需求而定制的——无论是路由器、工业控制器,还是智能扬声器。一个合乎逻辑的问题出现了:如果可以使用像 Debian 或 Ubuntu 这样的现成发行版,为什么还要手动构建呢?以下是主要区别:硬件平台支持并非所有 SoC 都提供现成的 Linux 镜像。即使你找到了兼容的镜像,也并不意味着它能开箱即用或与你的硬件完美兼容。硬件功能可能不会被披露许多 SoC 都有特定的硬件组件:视频加速器、神经网络协处理器、硬件加密模块等等。在标准发行版中,这些功能通常根本不启用或根本不受支持。冗余和资源强度桌面发行版包含大量的后台服务、通用驱动程序和库。所有这些都会消耗资源——内存、CPU 时间和电量。然而,嵌入式解决方案的资源几乎总是有限的。EL 允许您删除所有不必要的组件,只留下真正必要的组件。因此,嵌入式 Linux 与 Linux 相同,但并非通用发行版,而是为特定任务组装的套件。它是一个一切都在掌控之中的系统:从内核到最后的启动脚本。它专为特定设备打造,运行高效、稳定,并且完全按照设计目标运行——没有任何额外功能。然而,这并不意味着所有 EL 组件都与传统 PC 中使用的组件完全相同。例如,在典型系统中,启动是通过 BIOS 和 GRUB 进行的,而在 SoC 中,启动是通过一组专门的引导加载程序进行的。因此,为了了解这种系统的结构以及其中包含的组件,让我们先来了解一下它的组件以及启动过程的工作原理。 2. 嵌入式Linux的内部结构是怎样的? 2.1. 开机到登录的启动流程也许我会延续比较的传统——这样差异就会变得特别明显。 2.1.1. PC启动流程让我们从许多人都熟悉的 PC 启动过程开始:通电后,基本固件(BIOS,现代平台上称为 UEFI)会启动。它会检查关键组件(例如处理器、RAM 等)是否存在以及功能是否正常。接下来,它会轮询连接到主板的设备。如果在其中一个设备上检测到引导扇区,则该设备会被添加到引导队列中。BIOS(或 UEFI)将引导扇区(例如 MBR)从第一个合适的设备复制到 RAM 中,并将控制权移交给该扇区中包含的代码。这段代码称为第一阶段引导加载程序。该引导加载程序知道其延续位置(通常在/boot),并加载主引导加载程序 — 通常是 GRUB。GRUB 显示一个菜单(如果指定)并将控制权转移到配置中指定的 Linux 内核。内核挂载启动参数中指定的根文件系统(RootFS),并启动init进程。从此时直到登录屏幕出现为止发生的一切都取决于具体的分布和设置:它可能涉及设置网络、启动图形环境和其他任务。 2.1.2. SoC启动过程现在我们来看一下基于SoC的嵌入式系统的启动:通电后,主程序加载器 (PPL)(位于 ROM 中的引导加载程序)启动。它会初始化基本外设,例如静态 RAM (SRAM),并开始搜索下一阶段——辅助程序加载器 (SPL)。如果找到,则将其代码上传到 SRAM,然后 PPL 将控制权移交给它。第一阶段的引导加载程序没有特定的术语。它被称为引导 ROM、ROM 代码、内部引导加载程序和 PPL。不过,从现在开始我将使用 PPL 这个术语。SPL 会初始化更复杂的外设,尤其是 RAM。然后,它会搜索下一阶段——第三程序加载器 (TPL),或者更确切地说,是内核。找到的代码会被加载到 RAM 中,并将控制权移交给内核。TPL(如果存在)会执行额外的硬件初始化并搜索内核。一旦找到,它会将其加载到 RAM 中并转移控制权。内核根据设备树重新配置外设,挂载 RootFS,并启动 Init 进程。与标准 PC 的其他区别在于 RootFS 和 Init 进程中具体包含的内容。需要注意的是,每个阶段并不会触发下一个阶段,而是会转移控制权。无法回退——你只能重启整个设备,重新开始。 2.1.3 相似之处和主要区别乍一看,加载过程很相似,而且似乎同样令人困惑。某种程度上来说,确实如此。但是!在 PC 中,微控制器负责处理许多任务:例如,内存和总线会自动初始化——BIOS 仅负责协调它们的运行。每个系统组件都是独立的,并包含各自的固件。在这种情况下,启动操作系统更多的是组织独立组件之间的交互,而不是手动启动每个组件。SoC 的情况则有所不同。它是一种单片芯片,没有单独的控制器或不必要的空间——一切都必须尽可能紧凑、可靠且直观。唤醒系统的任务完全落在 SoC 的肩上。因此需要级联的引导加载程序,每个程序都执行各自的任务。PPL是 ROM 中最小、高度可靠且不可变的代码。它仅执行基本初始化并启动下一阶段。其紧凑的尺寸使其易于验证且可靠。损坏 PPL 意味着摧毁整个系统:芯片将变砖。SPL 的功能略有增加:特别是它可以初始化 RAM。然而,SRAM 太小,无法容纳复杂的逻辑,因此 SPL 通常仅作为下一步的桥梁。TPL已经在完整的 RAM 中运行,并且可以提供丰富的脚本:例如,在哪里加载操作系统、如何显示启动过程、如何响应连接的设备等。这种架构不会使事情变得复杂——它能提供保护。如果 SPL 或 TPL 级别发生故障,可以拦截启动过程,从备份驱动器启动,并恢复系统。关键在于预见这种可能性。然而,PPL 发生故障则意味着设备完全失效。因此,它是不可改变的,并且极其简单。 2.2. 嵌入式Linux组成现在,我们已经确定了要使用的内容,让我们仔细看看每个组件。 2.2.1. 引导加载程序U-Boot 是一款功能强大且用途广泛的开源引导程序,常用于在 SoC 上加载操作系统。我们将要使用的就是 U-Boot。它的优势如下:支持多种架构和SoC平台允许您从各种来源启动内核和 RootFS:USB、SD 卡、SATA 驱动器、网络、NAND/NOR 内存可以运行脚本:例如,先尝试从 USB 启动,然后从 eMMC 启动,最后才从网络启动支持交互模式(通过控制台)和计时器自动加载它可以通过环境变量进行灵活的配置。您可以从官方存储库下载 U-Boot 。如果 SPL 不适合 SRAM,它可以被“拆分”成多个部分 - 有关这方面的更多信息请参阅 SoC 加载部分 2.2.2. 内核这就是 Linux 本身——控制硬件、抽象资源并确保程序之间交互的内核。您可以从官方存储库或针对特定 SoC 定制的分支下载内核。 2.2.3. 根文件系统RootFS 不仅仅是目录的集合。它是一个完整的环境,包括:实用程序,例如 init、sh、ifconfig配置文件运行程序的依赖项与内核和引导加载程序不同,RootFS 不是以二进制文件的形式构建的,而是以目录结构的形式创建的。您可以手动创建 RootFS(例如,从主机系统复制必要的二进制文件和库),但这需要谨慎操作并透彻理解系统结构。 2.3. 嵌入式 Linux 构建工具和辅助工具至此,我们已经介绍了“代表”。有些人甚至可能已经开始查看源代码了。但我不建议你着急,因为我们还需要掌握构建工具和媒体。 2.3.1. 工具链工具链是一组特定于目标架构的编译器、头文件和实用程序,即:交叉编译器 gcc、g++、ld、as — 构建代码所需的一切Linux 内核头文件库和头文件 libc、libstdc++、libm 和其他系统库调试实用程序、分析器、系统助手工具链的主要要求是符合主机(编译发生的地方)和目标(代码运行的地方)系统的架构。您可以下载现成的解决方案,也可以自行构建(例如,使用 CrossTool-NG)。后者值得另写一篇系列文章来阐述,因此我们假设只能使用现成的解决方案。它们可以从您的设备制造商或第三方工具链制造商的网站下载。嗯,你仍然可以自己安装它......等等,我不是说过我们不会这么做吗?这么说吧,我撒谎了。我们会构建和配置工具链,但不是自己做的;我们会让构建工具来帮我们做。 2.3.2. 装配工具为了避免手动构建所有内容,通常使用专门的构建系统。其中最受欢迎的两个是 Buildroot 和 Yocto 项目。Yocto 项目Yocto 是一个强大且灵活的嵌入式 Linux 构建环境。它允许您:构建内核、引导加载程序、RootFS 和工具链在系统中启用对包管理器(opkg、rpm、dpkg)的支持精确管理版本、补丁和配置然而,Yocto 的学习曲线相当高。它使用自己的配方系统 (bitbake) 以及多层和多个概念。作为交换,您可以获得最大的灵活性和控制力。BuildrootBuildroot 更简单:它由一组 Makefile、Kconfig 和 bash 脚本组成,用于构建一个极简但可立即使用的 Linux 系统。与 Yocto 类似,它允许您构建内核、引导加载程序、RootFS 和工具链。优点:进入门槛低简单的界面(menuconfig)系统受到运行时保护,不会被更改——RootFS 默认为只读。虽然可以毫不费力地嵌入包管理器,但操作极其困难,因此不建议这样做。本指南将使用 Buildroot。您可以从官方仓库下载。至此,我们可以完成介绍部分并从理论转向实践。 结果因此,我们已经介绍了基础知识:嵌入式 Linux 是什么、它与常规发行版有何不同、它由什么组成、如何启动以及可以使用哪些工具来构建它。在下一篇文章中,我们将使用 Docker 准备工作环境,检查 U-Boot、Linux、Buildroot 及其外层的结构,并在我们的板上配置、构建和运行嵌入式 Linux。因此我强烈建议购买某种带有 SoC 的主板。感谢您的时间!再见!————————————————版权声明:本文为CSDN博主「大聪明-PLUS」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。原文链接:https://blog.csdn.net/2501_93209230/article/details/153827065
-
Nginx 是一款高性能的 Web 服务器和反向代理服务器,其配置灵活、性能卓越,被广泛应用于现代 Web 架构中。在 Nginx 的配置文件中,location 指令是实现请求路由的核心机制之一。正确理解和使用 location,对于构建高效、安全、可维护的服务至关重要。一、location 基本语法location 指令用于定义如何处理特定 URI(统一资源标识符)的请求。其基本语法如下:location [modifier] pattern { # 配置指令}其中:modifier 是可选的修饰符,用于指定匹配方式;pattern 是要匹配的 URI 路径或正则表达式;{} 内是具体的配置指令,如 proxy_pass、root、rewrite 等。二、location 的五种匹配类型Nginx 支持以下五种主要的 location 匹配类型,每种有不同的优先级和用途:1. 精确匹配(=)location = /index.html { ...}特点:完全匹配 URI,不进行任何前缀或正则扩展。优先级最高。适用于高频访问且路径固定的资源(如首页),能提升性能。示例:当请求为 /index.html 时匹配,但 /index.html?a=1 也会匹配(Nginx 在匹配时忽略查询字符串)。2. 前缀匹配(无修饰符)location /images/ { ...}特点:匹配以指定字符串开头的 URI。属于“普通前缀匹配”,优先级低于 = 和 ^~。可被正则匹配覆盖(除非使用 ^~)。3. 最长前缀匹配(^~)location ^~ /static/ { ...}特点:也是前缀匹配,但一旦匹配成功,不再检查正则 location。优先级高于普通前缀匹配,但低于 =。适用于静态资源目录,避免不必要的正则计算。4. 正则匹配(~ 和 ~*)location ~ \.php$ { ...}location ~* \.(jpg|jpeg|png)$ { ...}~:区分大小写的正则匹配;~*:不区分大小写的正则匹配。按配置文件中出现的顺序依次匹配,一旦匹配成功即停止。优先级高于普通前缀匹配,但低于 = 和 ^~。5. 通用匹配(/)location / { ...}匹配所有请求,通常作为兜底规则。优先级最低。三、location 匹配优先级总结Nginx 处理请求时,location 的匹配顺序如下:精确匹配(=)最长前缀匹配(^~)正则匹配(~ / ~*) —— 按配置顺序普通前缀匹配 —— 选择最长匹配的规则通用匹配(/) 注意:普通前缀匹配虽然先被扫描,但只有在没有更高优先级规则匹配时才会生效。Nginx 会先找出所有前缀匹配中最长的一个,然后判断是否有 ^~ 或 =,如果没有,则继续尝试正则匹配。四、示例示例 1:静态资源与动态请求分离server { listen 80; server_name example.com; # 精确匹配首页 location = / { root /var/www/html; index index.html; } # 静态资源,使用 ^~ 避免正则检查 location ^~ /static/ { alias /var/www/static/; expires 30d; } # PHP 动态请求 location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi_params; fastcgi_param SCRIPT_FILENAME /var/www/html$fastcgi_script_name; } # 兜底规则 location / { try_files $uri $uri/ /index.html; }}示例 2:API 路由代理location /api/v1/ { proxy_pass http://backend_server; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr;}注意:proxy_pass 结尾是否带 / 会影响 URI 重写行为。proxy_pass http://backend/; → /api/v1/user → 后端收到 /userproxy_pass http://backend; → /api/v1/user → 后端收到 /api/v1/user 掌握 Nginx 的 location 匹配机制,是构建高性能 Web 服务的基础。理解其优先级规则、合理使用修饰符、结合实际业务场景设计路由逻辑,不仅能提升系统性能,还能增强安全性和可维护性。
-
在 Linux 中监控 CPU、内存、磁盘、网络的指令非常丰富,以下是 最常用、实用的监控指令,按 “指标分类 + 指令详解 + 核心用法” 整理,新手也能快速上手:一、CPU 监控(核心:查看负载、使用率、进程占用)1. top(实时监控,最常用)功能:实时显示系统整体 CPU 负载、进程 CPU 占用排名(默认每 3 秒刷新)。核心用法:top # 直接运行,进入实时监控界面 界面关键信息:第一行:Cpu(s): 20.0%us(用户态 CPU 使用率)、5.0%sy(内核态 CPU 使用率)、75.0%id(空闲 CPU 使用率)。第三行:%Cpu0~%CpuN(多核心 CPU 各自使用率)。进程列:%CPU(单个进程占用 CPU 百分比)。常用快捷键:P:按 CPU 使用率排序(默认正序)。1:显示所有 CPU 核心的详细使用率。q:退出监控。2. htop(top 增强版,更直观)功能:比 top 界面更友好,支持鼠标操作、颜色区分进程状态。核心用法: # 先安装(Ubuntu/Debian):sudo apt install htop# 先安装(CentOS/RHEL):sudo yum install htophtop # 运行后直接查看,默认按 CPU 排序 3. mpstat(查看多 CPU 核心详情)功能:专门统计单个 / 所有 CPU 核心的使用率,适合分析 CPU 负载不均衡问题。核心用法mpstat # 查看所有 CPU 平均使用率mpstat -P ALL # 显示每个 CPU 核心的详细数据(0 代表第一个核心,1 代表第二个,以此类推)mpstat 1 5 # 每 1 秒刷新一次,共刷新 5 次(适合持续监控) 4. pidstat(查看单个进程的 CPU 占用)功能:精准定位某个进程的 CPU 使用情况(避免 top 刷屏)。核心用法: pidstat -u 1 3 # 每 1 秒统计一次所有进程的 CPU 使用率,共 3 次pidstat -u -p 1234 1 3 # 只监控 PID=1234 的进程,每 1 秒刷新,共 3 次 二、内存监控(核心:查看总内存、已用 / 空闲内存、进程内存占用)1. free(快速查看内存使用概况)功能:显示物理内存(RAM)和交换分区(Swap)的总容量、已用、空闲数据。核心用法: free -h # -h:以人类可读单位(GB/MB)显示(推荐)free -m # 以 MB 为单位显示 输出示例: total used free shared buff/cache availableMem: 15Gi 2.3Gi 10Gi 342Mi 3.1Gi 12GiSwap: 19Gi 0B 19Gi 关键指标:available(实际可分配给新进程的内存,含空闲 + 缓存可释放部分),比 free 更能反映真实内存状态。2. top/htop(实时查看进程内存占用)核心用法:运行 top 或 htop 后,关注进程列:%MEM:进程占用物理内存的百分比。VIRT:进程虚拟内存大小(含共享库、交换空间)。RES:进程实际占用的物理内存大小(不含共享库,核心指标)。快捷键:M(按内存使用率排序)。3. vmstat(监控内存 + CPU+IO 整体状态)功能:综合监控内存交换、页面调度、CPU 负载,适合排查内存瓶颈。核心用法: vmstat 1 5 # 每 1 秒刷新一次,共 5 次 关键输出:si(Swap in):从交换分区读入内存的数据量(越大说明内存不足,频繁使用 Swap)。so(Swap out):从内存写入交换分区的数据量(同上,si/so 长期非 0 代表内存紧张)。三、磁盘监控(核心:查看磁盘容量、IO 负载、分区使用)1. df(查看磁盘分区容量)功能:显示所有挂载分区的总容量、已用空间、空闲空间、使用率。核心用法: df -h # -h:人类可读单位(GB/MB),推荐df -T # 显示分区文件系统类型(ext4、xfs 等)df -h /home # 只查看 /home 分区的容量情况 2. du(查看目录 / 文件占用磁盘空间)功能:统计单个目录或文件的磁盘占用大小(df 看分区整体,du 看具体文件 / 目录)。核心用法: du -sh /var/log # 统计 /var/log 目录总占用(-s:只显示总和,-h:人类可读)du -h --max-depth=1 /home # 显示 /home 下一级目录的占用情况(不递归子目录)du -h /home/user/*.log # 统计指定类型文件的占用 3. iostat(监控磁盘 IO 负载)功能:查看磁盘的读写速度、IO 等待时间(判断磁盘是否繁忙)。核心用法: iostat -x 1 5 # -x:显示详细 IO 指标,每 1 秒刷新,共 5 次iostat -x -d sda 1 5 # 只监控 sda 磁盘(如 /dev/sda)的 IO 状态 关键指标:%util:磁盘 IO 使用率(接近 100% 说明磁盘繁忙,可能是瓶颈)。tps:每秒 IO 请求数(读 + 写)。rMB/s/wMB/s:每秒读 / 写数据量(MB)。4. iotop(磁盘 IO 进程排名)功能:类似 top,但按磁盘 IO 使用率排序,精准定位 “谁在占用磁盘 IO”。核心用法: # 安装:sudo apt install iotop(Ubuntu)/ sudo yum install iotop(CentOS)iotop # 运行后查看,默认按 IO 使用率排序iotop -o # 只显示正在进行 IO 操作的进程(过滤空闲进程,更清晰) 四、网络监控(核心:查看网络连接、带宽占用、网卡状态)1. ifstat(查看网卡带宽占用)功能:实时显示每个网卡的收发带宽(字节 / 秒、包 / 秒)。核心用法: # 安装:sudo apt install ifstat(Ubuntu)/ sudo yum install ifstat(CentOS)ifstat # 实时监控所有网卡带宽ifstat -i eth0 1 5 # 只监控 eth0 网卡,每 1 秒刷新,共 5 次 输出示例: eth0 wlan0 KB/s in KB/s out KB/s in KB/s out0.00 0.00 0.00 0.001.20 3.40 0.00 0.00 # 实时收发速率 2. iftop(网络带宽进程排名)功能:按进程 / IP 的网络带宽占用排序,直观查看 “谁在占用网络”。核心用法: # 安装:sudo apt install iftop(Ubuntu)/ sudo yum install iftop(CentOS)iftop -i eth0 # 监控 eth0 网卡的带宽占用 界面快捷键:N:显示 IP 地址(默认显示主机名,按 N 切换)。P:显示端口号(如 80、443)。q:退出。3. netstat(查看网络连接状态)功能:列出所有网络连接(TCP/UDP)、监听端口、进程 PID。核心用法(常用组合参数): netstat -tuln # 查看所有监听的 TCP/UDP 端口(-t:TCP,-u:UDP,-l:监听,-n:数字显示端口)netstat -anp # 查看所有网络连接(含 ESTABLISHED 连接)及对应进程 PID(-a:所有连接,-p:显示进程)netstat -anp | grep 80 # 过滤端口 80 的连接(排查 HTTP 服务) 4. ss(netstat 替代版,更快更高效)功能:与 netstat 功能一致,但性能更好(大并发连接下不卡顿),推荐优先使用。核心用法: ss -tuln # 等价于 netstat -tuln(查看监听端口)ss -anp # 等价于 netstat -anp(查看所有连接+进程)ss -anp | grep 443 # 过滤 HTTPS 端口(443)的连接 5. ping(测试网络连通性)功能:测试与目标 IP / 域名的网络连通性(基于 ICMP 协议)。核心用法: ping baidu.com # 持续 ping 百度,测试连通性ping -c 4 baidu.com # 只 ping 4 次(避免持续刷屏) 五、综合监控工具(一次性监控所有指标)如果想同时监控 CPU、内存、磁盘、网络,推荐用以下工具:1. glances(全能监控工具)功能:一站式监控所有系统指标,支持 Web 界面、远程监控。核心用法: # 安装:sudo apt install glances(Ubuntu)/ sudo yum install glances(CentOS)glances # 本地实时监控(界面含 CPU、内存、磁盘、网络、进程排名)glances -w # 启动 Web 服务,浏览器访问 http://服务器IP:61208 查看监控 总结:常用指令速查表监控指标快速查看指令详细分析指令CPUtop / htopmpstat、pidstat -u内存free -htop(按 M 排序)、vmstat磁盘容量df -hdu -sh 目录磁盘 IOiostat -xiotop网络带宽ifstatiftop网络连接ss -tuln / ss -anpnetstat -anp综合监控glances 根据需求选择即可:快速排查用 top+free -h+df -h+ss;精准定位问题用 pidstat+iotop+iftop。
-
在众多编程语言中,Lua(发音为 /ˈluːə/,意为“月亮”)以其轻量、高效、可嵌入性强的特性,在游戏开发、嵌入式系统、Web 服务器扩展、脚本自动化等领域占据着不可替代的地位。它被广泛应用于 《魔兽世界》、《愤怒的小鸟》、Redis、Nginx (OpenResty) 等知名项目中。 什么是 Lua?Lua 是一种用标准 C 语言编写的开源、轻量级、可扩展的脚本语言,由巴西里约热内卢天主教大学(PUC-Rio)的 Roberto Ierusalimschy 等人于 1993 年开发。核心设计理念:轻量:整个解释器核心仅几百 KB,适合嵌入资源受限的设备。高效:执行速度快,接近 Python、JavaScript 等主流脚本语言。可嵌入:设计初衷就是作为“胶水语言”,无缝集成到 C/C++ 等宿主程序中。可扩展:通过 C 扩展模块,轻松调用底层功能。定位:Lua 不是用于构建大型独立应用的“主语言”,而是作为嵌入式脚本语言,为宿主程序提供灵活的逻辑扩展能力。Lua 的核心特性特性说明轻量级解释器体积小(~200KB),内存占用低速度快基于寄存器的虚拟机,执行效率高动态类型变量无需声明类型,运行时确定自动内存管理内置垃圾回收(GC)机制过程式 + 函数式支持函数式编程(函数是一等公民)协程(Coroutine)轻量级线程,支持协作式多任务无内置复杂类型仅提供 表(table) 作为唯一数据结构,但功能强大Lua 基础语法速览1. 变量与数据类型Lua 有 8 种基本类型:nil, boolean, number, string, function, thread, table, userdata。-- 变量声明(无需类型)name = "Alice" -- stringage = 25 -- numberis_active = true -- booleanscore = nil -- nil-- 动态类型x = 10x = "hello" -- 合法,类型在运行时改变2. 表(Table)—— Lua 的“瑞士军刀”table 是 Lua 中唯一的数据结构,可实现数组、字典、对象、模块等多种功能。-- 作为数组(索引从 1 开始!)fruits = {"apple", "banana", "orange"}print(fruits[1]) --> "apple"-- 作为字典(键值对)person = { name = "Bob", age = 30, city = "Shanghai"}print(person["name"]) --> "Bob"print(person.age) --> 30-- 混合使用mixed = {10, "hello", x = 1, y = 2}print(mixed[1]) --> 10print(mixed.x) --> 13. 控制结构-- if 语句if age > 18 then print("Adult")elseif age > 13 then print("Teen")else print("Child")end-- for 循环for i = 1, 5 do print(i)end-- while 循环local i = 1while i <= 3 do print(i) i = i + 1end4. 函数(Function)函数是“一等公民”,可赋值给变量、作为参数传递。-- 定义函数function greet(name) return "Hello, " .. name .. "!"end-- 调用print(greet("Alice")) --> "Hello, Alice!"-- 函数作为变量local sayHi = function(name) return "Hi, " .. nameend-- 闭包(Closure)function makeCounter() local count = 0 return function() count = count + 1 return count endendcounter = makeCounter()print(counter()) --> 1print(counter()) --> 2Lua 的核心优势:可嵌入性Lua 的最大价值在于它可以被轻松嵌入到 C/C++ 程序中,通过 Lua C API 实现双向调用。典型嵌入流程:宿主程序(C/C++) 调用 Lua 脚本。Lua 脚本 调用宿主程序暴露的 C 函数。数据交换:通过 Lua 栈(Stack)传递参数和返回值。C 代码调用 Lua 示例:#include <lua.h>#include <lualib.h>#include <lauxlib.h>int main() { lua_State *L = luaL_newstate(); // 创建 Lua 状态机 luaL_openlibs(L); // 加载标准库 // 执行 Lua 脚本 if (luaL_dofile(L, "script.lua") != 0) { fprintf(stderr, "Error: %s\n", lua_tostring(L, -1)); } lua_close(L); return 0;}Lua 调用 C 函数:C 代码注册函数后,Lua 脚本可直接调用:-- Lua 脚本中result = my_c_function(42, "hello")Lua 的典型应用场景1. 游戏开发《魔兽世界》:UI 界面和插件系统使用 Lua。《愤怒的小鸟》:游戏逻辑由 Lua 驱动。Cocos2d-x:跨平台游戏引擎,支持 Lua 脚本开发。✅ 优势:热更新、快速迭代、非程序员(策划、美术)也能参与逻辑编写。2. Web 服务器扩展OpenResty:基于 Nginx 和 Lua 的高性能 Web 平台。在 Nginx 中直接运行 Lua 脚本,实现 API 网关、限流、鉴权、缓存等。性能远超传统 PHP/Python 后端。location /api { content_by_lua_block { local redis = require("resty.redis") local red = redis:new() red:connect("127.0.0.1", 6379) local data = red:get("user:123") ngx.say(data) }}3. 数据库脚本Redis:使用 Lua 脚本实现原子性操作(EVAL 命令)。多条命令打包执行,避免网络开销。保证操作的原子性。-- Redis Lua 脚本:原子性递增并返回local current = redis.call("GET", KEYS[1])if current then current = tonumber(current) + 1 redis.call("SET", KEYS[1], current) return currentelse return nilend4. 嵌入式系统与 IoT资源受限设备(如路由器、智能家居)中,Lua 用于配置脚本、业务逻辑扩展。NodeMCU:基于 ESP8266 的 IoT 固件,支持 Lua 编程。5. 配置与自动化脚本作为配置文件语言(比 JSON 更灵活)。自动化工具脚本(如 Wireshark 的插件)。 Lua 虽然小众,但其在特定领域的不可替代性使其历久弥新。它证明了“少即是多”的设计哲学——一个简单的 table 和强大的 C API,足以支撑起无数复杂系统的灵活扩展。
-
Nginx(发音为 "engine-x")已成为高并发、高性能 Web 服务的代名词。作为全球最受欢迎的 Web 服务器之一,Nginx 不仅被 Google、Facebook、Netflix、淘宝、京东等大型互联网公司广泛采用,更是微服务、负载均衡、API 网关等现代架构的核心组件。 一、什么是 Nginx?Nginx 是一个开源的 高性能 HTTP 服务器和反向代理服务器,同时也是一个 IMAP/POP3/SMTP 邮件代理服务器。它由俄罗斯程序员 Igor Sysoev 于 2004 年发布,最初为解决 C10K 问题(单机支持 1 万并发连接)而设计。核心特点:特性说明高性能异步非阻塞事件驱动架构,资源消耗低,支持高并发高可靠性进程模型稳定,即使高负载下也不会崩溃热部署支持不停机更新配置、升级版本模块化设计功能通过模块扩展,灵活可定制反向代理与负载均衡支持多种负载均衡算法静态资源服务高效处理 HTML、CSS、JS、图片等静态文件二、Nginx 架构原理:为什么这么快?1. 事件驱动 + 异步非阻塞与传统 Apache 的 多进程/多线程模型(每个连接占用一个进程/线程)不同,Nginx 采用 事件驱动的异步非阻塞 I/O 模型。Master 进程:管理进程,不处理请求。Worker 进程:每个 Worker 采用单线程 + 事件循环(Event Loop)处理成千上万个并发连接。I/O 多路复用:使用 epoll(Linux)、kqueue(BSD)等机制,一个进程可监听多个 socket。✅ 优势:内存占用少,上下文切换开销小,轻松应对数万并发连接。三、Nginx 的核心应用场景1. 静态 Web 服务器Nginx 是服务静态资源的绝佳选择,性能远超应用服务器(如 Tomcat)。server { listen 80; server_name www.example.com; location / { root /var/www/html; # 静态文件目录 index index.html; } # 缓存静态资源 location ~* \.(jpg|jpeg|png|css|js)$ { expires 1y; add_header Cache-Control "public, immutable"; }}2. 反向代理(Reverse Proxy)Nginx 作为“门面”,接收客户端请求,转发给后端应用服务器(如 Java、Python、Node.js),并返回响应。server { listen 80; server_name api.example.com; location / { proxy_pass http://127.0.0.1:8080; # 转发到本地 8080 端口 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; }}✅ 优势:隐藏后端服务器真实 IP统一入口,便于管理提升安全性3. 负载均衡(Load Balancing)Nginx 可将请求分发到多个后端服务器,实现横向扩展与高可用。配置示例:upstream backend { # 负载均衡算法 least_conn; # 最少连接 # round-robin; # 轮询(默认) # ip_hash; # IP 哈希(会话保持) # hash $request_uri; # 一致性哈希 server 192.168.1.10:8080 weight=3; # 权重 3 server 192.168.1.11:8080; server 192.168.1.12:8080 backup; # 备用服务器}server { listen 80; location / { proxy_pass http://backend; }}4. SSL/TLS 加密(HTTPS)Nginx 可作为 SSL 终端,处理 HTTPS 请求并解密后转发给后端 HTTP 服务。server { listen 443 ssl http2; server_name www.example.com; ssl_certificate /path/to/cert.pem; ssl_certificate_key /path/to/private.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512; location / { proxy_pass http://backend; }}✅ 推荐:使用 Let's Encrypt 免费证书 + Certbot 自动续期。5. 缓存加速Nginx 支持反向代理缓存,减少后端压力,提升响应速度。# 定义缓存区proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=60m use_temp_path=off;server { location / { proxy_cache my_cache; proxy_pass http://backend; proxy_cache_valid 200 302 10m; # 缓存 10 分钟 add_header X-Cache-Status $upstream_cache_status; }}缓存命中时,X-Cache-Status 返回 HIT,否则为 MISS。6. URL 重写与重定向# 301 永久重定向rewrite ^/old-page$ /new-page permanent;# 条件重写if ($http_user_agent ~* "bot|spider") { rewrite ^/.*$ /robots.txt break;}# 伪静态rewrite ^/article/(\d+)\.html$ /article.php?id=$1 last;四、常用配置指令详解指令作用listen监听端口和 IPserver_name匹配域名location定义 URL 路由规则root / alias文件路径映射proxy_pass反向代理目标upstream定义后端服务器组try_files尝试多个文件路径(常用于 SPA 路由)gzip启用 Gzip 压缩SPA 应用路由支持(如 Vue、React)location / { root /var/www/app; try_files $uri $uri/ /index.html;}确保前端路由刷新不 404。五、性能优化建议1. Worker 进程优化worker_processes auto; # 通常设置为 CPU 核心数worker_connections 1024; # 每个 Worker 最大连接数worker_rlimit_nofile 65535; # 提升文件描述符限制2. 开启 Gzip 压缩gzip on;gzip_types text/plain text/css application/json application/javascript text/xml application/xml;3. 启用 HTTP/2listen 443 ssl http2;减少延迟,提升加载速度。4. 静态资源缓存location ~* \.(css|js|jpg|png|gif)$ { expires 1y; add_header Cache-Control "public, immutable";}六、安全加固1. 隐藏 Nginx 版本号server_tokens off;2. 防止点击劫持add_header X-Frame-Options SAMEORIGIN;3. 防止 XSS 攻击add_header X-Content-Type-Options nosniff;add_header Content-Security-Policy "default-src 'self'";4. 限制请求频率limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;location /api/ { limit_req zone=api burst=20 nodelay;}
-
前言在现代计算机系统中,并发执行已成为提升资源利用率与系统吞吐量的核心机制。当多个进程/线程共享有限的系统资源(如处理器、内存、I/O 设备等)时,如何协调它们的执行顺序以避免冲突、保证数据一致性,成为操作系统设计的关键挑战 —— 这正是进程同步与互斥问题的核心议题。本文将系统梳理进程同步与互斥的理论基础,深入分析典型问题的内在逻辑,探讨各类解决方案的适用场景与局限性。本文分为6个部分:模拟多线程访问共享资源出错现象;访问共享资源出错原因;互斥锁解决共享资源访问问题;互斥锁的原理;线程同步互斥周边概念;线程同步如何实现。一. 多线程访问出错下面先通过一个实验看一下什么是多线程访问错误:模拟抢票的逻辑,使用5个线程模拟用户,用户要抢500张票。准备工作:设置一个结构体,存储线程的ID和线程的名字,用来打印抢票数据和回收线程:int tickets = 1000;struct ThreadData{ std::string thread_name_; pthread_t thread_id_;};编写抢票逻辑void* Get_Ticket(void* args){ ThreadData* pdata = static_cast<ThreadData*>(args); // 进行抢票 while(tickets > 0) // 还有票,可以抢 { // 打印出线程名以及抢到的票编号 std::cout << pdata->thread_name_ << " is getting a ticket , the numberr of tickets is " << tickets << std::endl; --tickets; usleep(rand() % 5); // 模拟延时 } return nullptr;}编写主线程,创建新线程和回收新线程:int main(){ // 模拟10个人抢票的逻辑 // 即10个线程一起抢票, --tickets表示抢票 std::vector<ThreadData*> threads; for(int i = 0 ; i < 5 ; i++) { ThreadData* pdata = new ThreadData(); pdata->thread_name_ = "Thread-" + std::to_string(i + 1); pthread_create(&pdata->thread_id_, NULL, Get_Ticket, pdata); threads.push_back(pdata); } // 等待所有线程结束 for(int i = 0 ; i < threads.size() ; i++) { pthread_join(threads[i]->thread_id_, NULL); delete threads[i]; } return 0;}输出现象:因为输出太长,此处直接去最后几行:根据上面的现象可以看到,有线程抢到了同一编号的票,并且有线程抢到了标号为负数的票。这就是多线程并发访问导致的数据不一致问题,下面围绕这一现象进行详细解释。二. 访问共享资源出错的原因对于全局变量的++/–操作是否是安全的???在上面代码中多个线程都会对同一个全局变量进行–操作,那么这一过程是否安全。此处需要了解以下,CPU是如何处理加减操作的,具体过程如下:先将tickets的数据存放到CPU的寄存器中;将寄存器中的值-1;将寄存器中的数据拷贝回内存中。具体实现如下:mov eax,tickets // 将tickets加载到eax寄存器dec eax // 将eax寄存器的值减1mov tickets,eax // 将结果存储到变量tickets中我们知道操作系统上的线程是并发运行的,随时有可能被操作系统切换;所以有没有一种情况:线程A执行到dec eax后,突然被切换了,然后将这些上下文数据存储到线程中;线程B在CPU上多跑一会,抢到了很多票,执行了多次--tickets操作,后也被切换了;此时A又被调度了,它下一步要执行mov tickets,eax,但是寄存器eax中的值是多少,是线程B前往票后的值吗?并不是线程A中的值,是线程B还没抢之前的值,所以此时将这个值返回tickets中就会导致,tickets值变大,从而导致多个线程抢到同一张票。同理如果一个线程C在while(tickets > 0)判断完后被切换,其他线程执行将票抢完了,已经是0了,没有票了;此时线程C再次被切换进来,因为之前判断是允许抢票的,所以其已经在循环内了,从而导致线程抢到负数的票。此处引入一个新的概念:原子性(Atomicity): 是指一个操作或一组操作具有 “不可分割” 的特性 —— 要么完整地执行完毕,要么完全不执行,在执行过程中不会被任何外部因素(如其他进程、线程、中断等)打断,也不会向外界暴露中间状态。一般只有一条汇编语句的代码就是具有原子性的。所以也就是说:对tickets的加减是不原子的,所以导致抢到了相同的票;进入循环执行代码也是不原子的,所以导致抢到了负票。如何解决这种数据不一致问题?如何保证对共享数据的访问,任何时候只有一个执行流在访问共享资源???下面介绍互斥锁来解决数据不一致问题。三. 互斥锁在介绍互斥锁之前,先介绍几个概念:临界资源:多线程执行流共享的资源就叫做临界资源;临界区:每个线程内部,访问临界资源的代码,就叫做临界区;互斥:任何时刻,互斥保证有且只有一个执行流进入临界区,访问临界资源,通常对临界资源起保护作用。锁是对临界区资源访问的一种限制操作,其保证在任何一个时间点上只有一个线程在访问临界资源。示意图如下: 锁一共有4个操作,初始化锁,销毁锁,加锁和解锁。初始化锁:int pthread_mutex_init(pthread_mutex_t *restrict mutex , const pthread_mutexattr_t *restrict attr):参数1:需要进行初始化的锁;参数2:定制互斥锁的行为特性,一般不进行定制,使用NULL;返回值:0表示成功,失败返回错误码。销毁锁:int pthread_mutex_destory(pthread_mutex_t *mutex):参数与返回值与上面类似。加锁:int pthread_mutex_lock(pthread_mutex_t* mutex);解锁:int pthread_mutex_unlock(pthread_mutex_t* mutex)。此时就可以对上面抢票代码进行重写,此时因为不能在循环外进行加锁,因此要把判断能否抢票的逻辑放在循环里面:pthread_mutex_t lock;void *Get_Ticket(void *args){ ThreadData *pdata = static_cast<ThreadData *>(args); // 进行抢票 while (1) { // 先上锁 pthread_mutex_lock(&lock); if (tickets > 0) // 还有票,可以抢 { // 打印出线程名以及抢到的票编号 std::cout << pdata->thread_name_ << " is getting a ticket , the numberr of tickets is " << tickets << std::endl; --tickets; pthread_mutex_unlock(&lock); // 解锁 } else { pthread_mutex_unlock(&lock); // 解锁 break; } // 解锁 usleep(getpid() % 100); // 让线程休眠一会,防止一个线程申请锁的能力太强 } return nullptr;}以上代码中解锁后如果没有代码,就会导致刚解锁的进程又会立即拿到锁,就会导致锁的分配不合理,容易导致进程饥饿问题;上面代码逻辑本身也是不对了,在解锁后,肯定还有其他操作要进行,比如保存用户信息,将票号从总票数中移除等,一次上面使用usleep()来模拟该操作。锁本身也是共享资源,所以申请锁和释放锁本身在设计的时候就是原子的。在临界区中,线程可以被切换吗???可以被切换,只不过在切换时会将锁也带走,在此期间其他线程依旧没有办法访问临界区。补充:还有一种初始化锁的方式:pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER,通过该方法进行初始化的锁,必须定义成全局的,并且不需要销毁。四. 互斥锁的原理为了实现互斥锁操作,大多数的CPU体系结构都提供了swap和exchange指令,该指令的作用是把寄存器和内存单元的数据相符交换,由于只有一条指令,保证了原子性。加锁&&解锁示意图如下:在加锁的时候,将寄存器中的al先进行初始化,再将mutex中的值交换过去,相当于将锁给线程,如果mutex中的锁已经被拿走了,就会将线程挂起,直到锁归还。解锁是,再见mmutex置为1,表示规范锁。所以,交换锁的本质就是:把内存中的数据交换到CPU寄存器中,把数据交换到线程的上下文中。mutex只有一个,其值子啊内存和寄存器之间进行交换;将共享的锁,以一条汇编的方式交换到线程的上下文,代表着该线程拿到了锁,其他线程就拿不到了。五. 线程同步互斥周边概念5.1 重入与线程安全重入:对于一个函数来说,当有多个执行流同时在函数内执行,就成该函数为重入函数;重入函数又可以分为:可重入函数和不可重入函数。线程安全:当多个线程并发式的访问同一段代码的时候,不会出现不同的结果,就称为线程安全。我们经常调用的库函数/系统调用基本上都是不可重入的。重入与线程安全的关联:一个函数是可重入的,那么一定是线程安全的;一个函数是不可重入的,可能会出现线程不安全。5.2 死锁死锁:当一个线程占有一把锁A,正在获取另一把锁B时,另一个线程持有锁B,但是同时也在请求锁A,造成两个进程都在阻塞是等待的情况。死锁的必要条件:互斥条件,一个资源每次只能有一个进程访问;请求与保持:执行流因为请求资源——锁而阻塞,对已有资源——锁不进行释放;不掠夺条件:资源——锁不会被抢走;循环等待:拥有锁的双方都在请求对方的锁。根据死锁的必要条件,可以提出对应的解决方案:对代码结构重构,打破互斥条件;在申请锁失败阻塞的时候将自己拥有的锁释放,可以使用int pthread_mutex_trylock()尝试申请锁,如果失败返回-1,此时可以选择将自己的锁释放;并不能抢其他线程的锁,所以无法掠夺;打破循环条件,将两个锁同时进行申请,同时释放。死锁还有些相关算法:1)死锁检测算法;2)银行家算法。六. 线程同步在保证资源安全的前提下,我们希望我买了的线程访问资源具有一定的顺序性。线程同步就是为了解决不同线程竞争资源的能力不同,导致的饥饿问题;简单说就是解决线程排队的优先级,避免一个线程的优先级高并且一直在访问共享资源,导致其他线程都访问不到的问题。线程同步是使用条件变量来实现的:可以将条件变量理解为一个队列,所有要访问临界资源的线程都要想到队列中进行排队;以下是条件变量的接口:初始化一个条件变量:int pthread_cond_init(pthread_cond_t *restrict cond , const pthread_condatte_t *restrictatta):第一个参数要进行初始化的条件变量;第二个参数,可以指定条件变量的一些属性,一般不进行指定,使用NULL;返回值0表示成功,失败返回错误码。条件变量也可以先锁一样定义为全局的形式:pthread_cond_t cond = PTHREAD_COND_INITIALIZER;销毁条件变量:int pthread_cond_destroy(pthread_cond_t *cond)。让一个线程在队列中进行等待:int pthread_cond_wait(pthread_cond_t *cond , pthread_mutex_t *mutex):参数一,要在哪一个队列中等待;参数二,对应的锁;条件变量让进程进行等待是因为,临界资源没有就绪,所以才需要进行等待,但是我们如果想要直到临界资源是否就绪,就需要访问临界资源,因此条件变量的使用要在加锁和解锁之间,当线程在条件变量中等待的时候,会将对应的锁释放。线程在条件变量中等待,就必须能够被唤醒,线程库中也提供了将线程唤醒的接口:int ptherad_cond_signal(pthread_cond_t *cond):唤醒条件变量中的一个线程;int ptherad_cond_broadcast(pthread_cond_t *cond):唤醒条件变量中的所有线程;下面写一个demo代码让主线程负责唤醒一个个新线程:先定义锁和条件变量,以及一个存储线程数据的结构体:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; // 定义锁pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // 定义条件变量struct ThreadData{ std::string thread_name_; pthread_t thread_id_;};定义新线程调用的函数,只需要打印出每次访问临界区的线程名称即可:void *thread_func(void *args){ pthread_detach(pthread_self()); ThreadData *data = static_cast<ThreadData *>(args); while (1) { pthread_mutex_lock(&mutex); pthread_cond_wait(&cond, &mutex); std::cout << data->thread_name_ << " wake up" << std::endl; pthread_mutex_unlock(&mutex); } return nullptr;}编写主线程,负责发送信号唤醒条件队列,以及创建新线程:int main(){ std::vector<ThreadData *> threads; for (int i = 0; i < 5; i++) { ThreadData *pdata = new ThreadData(); pdata->thread_name_ = "Thread-" + std::to_string(i + 1); pthread_create(&pdata->thread_id_, NULL, thread_func, pdata); threads.push_back(pdata); usleep(500); } while(1) { pthread_cond_signal(&cond); sleep(1); } return 0;}运行结果:可以看到线程确实是一次访问临界资源的。综上所述:cond就相当于一个队列,要访问临界资源就需要先排队,而pthread_cond_signal和pthread_cond_broadcast相当于信号,告诉队列可以访问临界资源了。———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2401_87944878/article/details/150649808
-
在 Linux C/C++ 开 发 中,库 是 代 码 复 用 和 工 程 化 的 核 心。不 少 开 发 者 会 遇 到 源 码 泄 露、编 译 “找 不 到 头 文 件 / 库”、动 态 库 运 行 加 载 失 败 等 问 题,本 质 是 对 库 的 流 程 不 熟 悉。本 文 从 背 景 切 入,先 讲 静 态 库 的 原 理、制 作 与 使 用,再 讲 动 态 库 的 实 战 技 巧,帮 你 掌 握 库 的 全 流 程 应 用。 一、静 态 库1、背 景 设 计 一 个 静 态 库 并 将 已 经 写 好 的 代 码 给 别 人 用。有 两 种 方 法: 把 源 文 件 给 他把 源 代 码 打 包 成 库,必 须 提 供 头 文 件。头 文 件 的 本 质 是 库 文 件 的 说 明 书。libxxx.a - - - 静 态 链 接libxxx.so - - - 动 态 链 接 2、原 理 3、静 态 库 的 流 程(1)编 写 者编 写 源 代 码(不 包 括 main 函 数)。mymath.h #pragma once #include<stdio.h> extern int myerrno;int add(int x,int y);int sub(int x,int y);int mul(int x,int y);int div(int x,int y); mymath.c #include"mymath.h" int myerrno = 0;int add(int x,int y){ return x + y;}int sub(int x,int y){ return x - y;}int mul(int x,int y){ return x * y;}int div(int x,int y){ if(y == 0) { myerrno = 1; return -1; } return x / y;} makefile lib=libmymath.a$(lib):mymath.oar -rc $@ $^mymath.o:mymath.cgcc -c $^.PHONY:cleanclean:rm -rf *.o *.a lib.PHONY:outputoutput:mkdir -p lib/includemkdir -p lib/mymathlibcp *.h lib/includecp *.a lib/mymathlib 这 里 不 写 $@ 的 原 因 是 因 为 gcc 可 以 将 mymath.h 编 译 成 和 源 文 件 名 字 相 同 的 .o 文 件。 ar 是 生 成 静 态 库 的 1 个 命 令,可 以 将 所 有 的 .o 文 件 打 包 形 成 .a 文 件,-rc 表 示 将 所 有 的 .o 放 在 目 标 文 件 .a 中,如 果 不 存 在 就 创 建,如 果 存 在 就 替 换。 编 译 生 成 .a 和 .o 文 件。 将 生 成 的 文 件 打 包 进 文 件 夹 中。将 生 成 的 lib 文 件 夹 打 包 压 缩。(2)使 用 者下 载 并 解 压 静 态 库 的 压 缩 包编 写 main 函 数。#include "mymath.h"int main(){ int n = div(10,0); printf("10/0=%d,errno=%d\n",n,myerrno); return 0;} 编 译 代 码没 有 找 到 头 文 件 原 因 编 译 器 会 在 默 认 路 径 和 当 前 目 录 下(和 源 代 码 在 同 一 级 路 径 下) 寻 找 头 文 件。 解 决 方 法方 法 1gcc main.c -I + 目 录:编 译 器 会 去 指 定 目 录 下 寻 找 头 文 件。方 法 2#include "lib/include/mymath.h" 可 以 在 代 码 中 包 含 路 径。这 里 推 荐 使 用 方 法 1。 链 接 错 误(找 不 到 静 态 库) 下 图 中 的 错 误 是 链 接 错 误,原 因 是 因 为 以 .o 结 尾 的 一 般 是 链 接 错 误。 -c:编 译 到 目 标 代 码 gcc -c 编 译 通 过,只 能 说 明 代 码 在 语 法 和 基 本 编 译 规 则 上 没 有 问 题,它 不 检 查 链 接 错 误。 -L 静 态 库 的 存 储 路 径。 没 有 包 含 .a 文 件 -l 找 到 .a 文 件,这 里 是 静 态 库 的 真 实 名 字,建 议 -l 和 库 的 真 实 名 字 相 连 接,二 者 之 间 没 有 空 格。 库 的 真 实 名 字去 掉 前 缀 和 后 缀。 (3)简 化 gcc 编 译 选 项直 接 将 头 文 件 和 静 态 库 放 进 系 统 文 件 夹。这 是 库 的 安 装。这 里 不 建 议 不 要 将 头 文 件 和 静 态 库 放 入 系 统 中。 需 要 指 明 静 态 库 的 真 实 名 字,才 能 编 译 成 功。 使 用 软 链 接 使 用 makefilemain:main.cgcc main.c -I ./lib/include/ -L ./lib/mymathlib/ -lmymath -o main.PHONY:cleanclean:rm -f main (4)myerrno 问 题 上 面 的 errno 不 是 -1 的 原 因 是 因 为 c 语 言 的 实 例 化 是 从 右 向 左 实 例 化 的。即 printf("10/0=%d,errno=%d\n",div(10,0),myerrno); 这 句 代 码 中 先 调 用 myerrno 然 后 使 用 div。调 用 的 顺 序 错 误 正 确 的 顺 序 是 在 调 用 myerrno 时 应 使 用 div,然 后 使 用 myerrno。修 改 后 的 代 码 #include "mymath.h"int main(){ int n = div(10,0); printf("10/0=%d,errno=%d\n",n,myerrno); return 0;} (5)结 论第 3 方 库 使 用 的 时 候 必 须 使 用 -l 选 项 ldd可 以 查 看 是 否 是 动 态 链 接 还 是 静 态 链 接。如 下 图 gcc 采 用 的 是 动 态 链 接,因 为 文 件 是 以 .so 结 尾 的。 gcc 默 认 是 动 态 链 接,如 果 只 提 供 静 态 链 接,gcc 只 能 对 该 库 使 用 静 态 链 接。ldd 的 输 出 反 映 的 是 整 个 程 序 是 否 依 赖 动 态 库,而 非 单 个 库 的 链 接 方 式。上 面 使 用 的 是 静 态 库,gcc 对 静 态 库 采 用 静 态 链 接,这 里 没 有 显 示 静 态 库 的 链 接。但 对 可 执 行 程 序 来 讲,程 序 中 还 有 其 他 库(如 C 标 准 库 libc)使 用 了 动 态 链 接(默 认 行 为),整 个 程 序 仍 然 是 “动 态 链 接 的 可 执 行 文 件”。 当 同 一 库 的 动 态 版 本(.so)和 静 态 版 本(.a)同 时 存 在 时,gcc 默 认 会 优 先 选 择 动 态 链 接 方 式。 若 要 强 制 使 用 静 态 链 接,可 在 编 译 时 添 加 -static 选 项。不 过 需 要 注 意,-static 并 非 绝 对 强 制 的 指 令 - - - 它 的 作 用 是 告 知 gcc 尽 量 采 用 静 态 链 接,但 在 某 些 情 况 下,gcc 可 能 仍 无 法 实 现 完 全 的 静 态 链 接。 二、动 静 态 库1、编 写 者编 写 代 码gccmylog.h #pragma once //防止头文件被重复包含#include<stdio.h>void log(const char*); mylog.c #include"mylog.h"void log(const char* info){ printf("Warning:%s\n",info);} myprint.c #include "myprint.h"void Print(){ printf("hello world!\n"); printf("hello world!\n"); printf("hello world!\n"); printf("hello world!\n");} myprint.h #pragma once //防止头文件被重复包含#include<stdio.h>void Print(); makefile dy-lib=libmymethod.sostatic-lib=libmymath.a # 同时生成动态库和静态库.PHONY:allall: $(dy-lib) $(static-lib) # 生成静态库$(static-lib):mymath.oar -rc $@ $^mymath.o:mymath.cgcc -c $^ # 生成动态库$(dy-lib):mylog.o myprint.ogcc -shared -o $@ $^mylog.o:mylog.cgcc -fPIC -c $^myprint.o:myprint.cgcc -fPIC -c $^ # 清理静态库文件.PHONY:cleanclean:rm -rf *.o *.a *.so mylib # 打包.PHONY:outputoutput:mkdir -p mylib/includemkdir -p mylib/libcp *.h mylib/includecp *.a mylib/lib cp *.so mylib/lib 编 译 代 码,将 .c 文 件 编 译 成 .o 文 件。fPIC:产 生 位 置 无 关 码。 生 成 动 态 库 .so 表 示 动 态 库。 -shared 此 选 项 表 示 不 生 成 可 执 行 程 序,将 尽 量 使 用 动 态 库,所 以 生 成 文 件 比 较 小,但 是 需 要 系 统 由 动 态 库 -O0、-O1、-O2、-O3 编 译 器 的 优 化 选 项 的 4 个 级 别,-O0 表 示 没 有 优 化,-O1 为 缺 省 值,-O3 优 化 级 别 最 高。 2、使 用 者编 译 代 码(包 含 动 静 态 库) 运 行 代 码 有 ldd 和 a.out 可 以 看 出 生 成 的 可 执 行 程 序 为 动 态 链 接 的。 gcc 编 译 时 的 路 径 是 编 译 器,还 需 要 让 系 统(加 载 器) 明 白 动 态 库 在 哪 里。3、加 载 找 不 到 动 态 库将 动 态 库 拷 贝 到 系 统 路 径(/lib64 或 者 /usr/lib64/) 下。实 际 情 况,我 们 使 用 的 库 都 是 别 人 成 熟 的 库,都 采 用 直 接 安 装 到 系 统 的 方 式。 建 立 软 链 接 将 自 己 的 库 所 在 的 路 径 添 加 到 系 统 的 环 境 变 量 中。使 用 echo $LD_LIBRARY_PATH 搜 索 用 户 自 定 义 的 库 路 径。 xshell 每 次 重 启 都 会 重 新 加 载 环 境 变 量,可 以 将 LD_LIBRARY_PATH 这 个 环 境 变 量 添 加 到 vim ~/.bash_profile,来 解 决 这 个 问 题。 在 /etc/ld.so.conf.d 这 是 系 统 维 护 动 态 库 时 放 的 路 径,建 立 自 己 的 动 态 库 路 径 的 配 置 文 件,然 后 使 用 ldconfig 重 新 加 载 即 可。 (1)切 换 到 root 身 份 并 进 入 到 /etc/ld.so.conf.d 这 个 路 径 下,创 建 以 .conf 结 尾 的 文 件 并 添 加 路 径,这 种 方 法 和 xshell 是 否 关 闭 没 有 影 响。 (2)使 用 ldconfig 重 新 加 载 文 件(3)成 功 执 行 可 执 行 程 序 (4)如 果 不 想 使 用 这 种 方 法,可 以 删 除 刚 才 新 建 的 文 件,然 后 使 用 ldconfig 重 新 加 载 文 件 即 可。 三、总 结 本 文 覆 盖 了 静 态 库(.o 打 包、Makefile 构 建、-I/-L/-l 链 接)与 动 态 库(-fPIC/-shared 编 译、4 种 加 载 问 题 解 决 方 案)的 核 心 内 容。静 态 库 嵌 入 程 序、独 立 但 体 积 大,动 态 库 共 享 模 块、轻 量 但 需 依 赖 系 统,需 按 需 选 择。后 续 可 尝 试 封 装 通 用 模 块 为 库,或 研 究 版 本 管 理,进 一 步 提 升 开 发 效 率。———————————————— 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 原文链接:https://blog.csdn.net/2301_78847073/article/details/151329450
推荐直播
-
HDC深度解读系列 - Serverless与MCP融合创新,构建AI应用全新智能中枢2025/08/20 周三 16:30-18:00
张昆鹏 HCDG北京核心组代表
HDC2025期间,华为云展示了Serverless与MCP融合创新的解决方案,本期访谈直播,由华为云开发者专家(HCDE)兼华为云开发者社区组织HCDG北京核心组代表张鹏先生主持,华为云PaaS服务产品部 Serverless总监Ewen为大家深度解读华为云Serverless与MCP如何融合构建AI应用全新智能中枢
回顾中 -
关于RISC-V生态发展的思考2025/09/02 周二 17:00-18:00
中国科学院计算技术研究所副所长包云岗教授
中科院包云岗老师将在本次直播中,探讨处理器生态的关键要素及其联系,分享过去几年推动RISC-V生态建设实践过程中的经验与教训。
回顾中 -
一键搞定华为云万级资源,3步轻松管理企业成本2025/09/09 周二 15:00-16:00
阿言 华为云交易产品经理
本直播重点介绍如何一键续费万级资源,3步轻松管理成本,帮助提升日常管理效率!
回顾中
热门标签