尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Linux系统运维与内核开发实战:从命令到file_operations

Linux系统运维与内核开发实战:从命令到file_operations 今天把自己关在机器前面整整整理了一天把2026年开年这段时间散落在各处终端的Linux知识点统一过了一遍。这篇笔记的日期是2026年1月8日内容基本覆盖了我最近实际动手折腾过的所有方向从最基础的Linux常用命令、用户权限管理到系统服务部署、磁盘文件系统的坑再到内核模块的动态加载和file_operations机制。说句实话这类笔记我写过很多次但每次回看都能发现之前理解不到位的地方所以这次特意把踩过的坑、验证过的操作、还有背后的原理一起记下来一方面方便自己日后查阅另一方面也给同样在啃Linux的朋友一条相对完整的参考路径。这篇内容适合谁我先说清楚。如果你是刚把虚拟机装好、正对着黑乎乎终端发愁的新手可以直接从第一部分命令清单看起如果你已经开始用Linux做开发或运维可以重点关注第三、四部分的文件系统和服务部署如果你对内核驱动、系统调用拦截这些底层机制感兴趣第五部分的字符设备驱动示例应该能给你一个不那么吓人的切入点。不管哪一类我都尽量把“为什么这么做”讲明白而不只是给你一串能跑的命令。1. 命令不是背的从基础语法到组合套路1.1 今天重新梳理的常用命令清单很多新手学Linux最容易犯的毛病就是拿着命令大全从头背到尾背完就忘。坦白说我在学过的前半年也这样后来才发现命令这东西本质上就是工具的说明书你只需要知道“有哪些工具、每个工具解决什么问题”具体参数用的时候查man手册就行。真正值钱的不是记住几百条命令而是面对一个具体任务时能条件反射地选出正确的工具。结合我这些年用得最多的场景整理了一份自己会反复回看的清单场景命令关键参数说明查看目录结构ls-l长格式-a包含隐藏文件-h人性化大小定位文件find-name按名字-type按类型-size按大小-mtime按修改时间文本检索grep-r递归目录-n显示行号-v排除匹配-i忽略大小写文本处理seds/旧/新/替换-i直接改文件d删除行文本分析awk默认按空格拆列$1代表第一列还可以做累加统计文件去重排序sort-n按数字排序-r逆序-u去重查看进程psaux列出所有进程配合grep过滤监控资源top / htophtop操作更友好支持鼠标点选磁盘占用du-sh统计目录总大小--max-depth控制递归层级网络端口ss-lntp查看监听端口和对应进程这里头我特别想说的是du和df的区别。df是查看整个文件系统的剩余空间du是统计目录实际占用的磁盘空间。新手经常混淆比如发现磁盘满了df显示/dev/sda1使用率100%但du命令在根目录看半天找不到哪个目录占了大头。这时候一般是因为有已删除但仍被进程占用的文件后面讲WSL空间问题时会再细说。1.2 管道组合才是Linux的灵魂单条命令再强大也有限Linux真正厉害的地方在于管道的组合能力。简单理解管道就是把前一个命令的输出当作后一个命令的输入像流水线一样处理数据。我来举两个今天实际用到的例子。第一个排查哪个进程占了最多内存ps aux --sort-%mem | head -10这条命令先让ps按照内存占用率倒序排列再用head取前10行一秒钟就能定位到内存大户。要是想更精细一点可以加上awk截取关键列ps aux | sort -k4 -rn | awk {print $1, $2, $4, $11} | head -10这里用sort的-k4指定按第4列排序-rn表示数字倒序然后awk只输出用户、PID、内存百分比和命令名四列。第二个例子统计日志里某个错误出现的次数。比如要统计nginx错误日志里connect()失败的次数grep connect() failed /var/log/nginx/error.log | wc -lgrep过滤出错误行wc -l数行数两段公式搞定。如果再进阶一点想知道每种错误类型各自出现了多少次可以用sort加uniq -cgrep -oE connect\(\) failed[^,]* error.log | sort | uniq -c | sort -rn这里的思路其实就是把“查找—排序—去重计数—再排序”四个环节串起来对于任何日志分析场景都通用。初学者建议多找几份真实的日志文件练习这种组合操作练熟了之后对文本处理的驾驭能力会有一个质的提升。2. 用户、权限与安全基线2.1 新建一个干净的普通用户在日常运维中最忌讳的就是所有操作都在root下进行。root权限太大一条rm -rf写错路径可能就是事故现场。所以每次新装系统第一件事就是建一个日常使用的普通用户只在需要时通过sudo临时提权。新建用户的标准流程我一般这么走# 创建用户同时创建home目录并指定默认shell sudo useradd -m -s /bin/bash -d /home/zhangsan zhsan # 设置密码 sudo passwd zhsan # 将用户加入sudo组获得提权能力 sudo usermod -aG sudo zhsan如果希望用户id固定方便以后管理文件所有权可以在创建时指定uidsudo useradd -u 1050 -m -s /bin/bash zhsan创建完以后用id zhsan验证如果输出里有uid1050和groups里的sudo就说明建好了。这里有个细节很多人忽略useradd和adduser其实是两套不同的命令。Ubuntu里的adduser是一个有交互提示的Perl脚本会一步步引导你设置密码和用户信息而useradd是更底层的命令参数更干净。如果在Ubuntu上不习惯记忆参数用adduser其实更省事。2.2 权限位、ACL与提权思路Linux权限模型可以拆成三层传统的r/w/x权限位、ACL访问控制列表、以及特殊权限位。传统权限位大家都熟用ls -l查看第一列像-rw-r--r--这样四个部分分别是文件类型、属主权限、属组权限、其他人权限。数字表示法下r4w2x1所以常见的644表示属主可读写、其他人只读755表示属主可读写执行、其他人可读执行。ACL是传统权限位不够用时的补充。比如某个文件想让特定用户bob可读但又不想改变属主和属组权限这时候ACL就派上用场了# 给bob用户单独加读权限 setfacl -m u:bob:r /data/project/config.yaml # 查看ACL getfacl /data/project/config.yaml设置ACL之后ls -l的输出末尾会多一个加号比如-rw-r-----这个加号提醒你该文件启用了ACL。我踩过的一个坑是rsync或者tar备份时如果没有加-A参数ACL信息可能丢失恢复后权限就变了。特殊权限位里面SetUIDsuid是最需要警惕的。它的作用是让普通用户执行某个程序时临时获得程序属主的权限。经典的例子是/usr/bin/passwd它需要root权限去修改/etc/shadow但又得允许普通用户调用所以系统给它设置了suid位。查看suid文件可以用find /usr -perm -4000 -ls作为运维排查的重点如果发现某个不认识的程序带了suid位需要立刻查清楚它的来源因为suid程序一旦被植入恶意代码后果就是预留提权后门的入口。2.3 从防御视角聊聊“提权”热词里出现了“Linux提权”很多人第一反应是攻击者的手法。但作为系统维护人员我的习惯是反着学弄懂攻击者是怎么提权的才能真正把系统防线扎紧。所谓提权本质就是普通用户想办法获取更高权限的过程。常规思路无非几条利用配置错误的suid程序、利用内核漏洞、利用sudo配置不当、利用可写的系统目录或服务配置文件。针对这几个方向防御手段也要对号入座。比如定期扫描系统里所有带suid/sgid位的程序检查sudoers配置里每一行是否都是最小授权及时用apt或yum把内核升级到安全版本以及最重要的——不要给普通用户随手写进系统目录的权限。还有一条容易被忽视的规则不要总用root登录桌面环境。很多安全意识不足的用户直接拿root当日常账号用浏览器、文档处理器一旦被攻破系统就直接沦陷了。用普通用户日常干活需要管理权限时sudo瞬间提权这个习惯能在很多场景下救你一次。3. 存储与文件系统乱码、空间和透明加密3.1 中文文件名乱码的三种解法解压文件乱码是Windows和Linux混用的高发问题根源在于文件名编码不一致。Windows简体中文版用的zip压缩包文件名常是GBK编码而Linux系统默认使用UTF-8编码。解压工具按UTF-8去解析GBK编码的文件名自然就出现了一堆乱码字符。我总结过三种处理方式按优先级排列。第一种是源头解决解压时直接指定编码。如果用unzip版本支持的话可以试试-O参数unzip -O GBK 中文文件.zip可惜的是不同发行版打包的unzip版本不完全一样有的不带-O选项。这时候用7z更可靠7z x 中文文件.zip7z对编码的处理更宽容很多情况下自动就能正常显示。如果压缩包已经解压完文件名已经乱码了那就需要转码工具convmv# 先测试不实际执行 convmv -f GBK -t UTF-8 --notest -r 乱码目录/这里-f指定源编码-t指定目标编码-r递归处理目录。我建议先不加--notest跑一遍看输出的预览确认无误后再加--notest真正执行。另外要注意一旦转码完毕文件名就变了如果有脚本或数据库里记录了旧文件名要及时同步更新。3.2 WSL2删除文件后空间不释放WSL2Windows Subsystem for Linux第二版现在用的人很多因为它把整个Linux根文件系统放在一个虚拟磁盘文件里默认是ext4.vhdx。很多WSL用户会遇到一个非常诡异的问题明明在Linux里删了好几个G的文件df一看空间确实释放了但Windows里C盘空间一点没变。原因是WSL2的虚拟磁盘本身是“只增不减”的。删除文件之后ext4文件系统内部标记了这些块为可用但是虚拟硬盘文件ext4.vhdx并不会自动缩水需要手动执行压缩。整理一下步骤# 第一步在WSL里彻底关机 wsl --shutdown # 第二步以管理员身份打开PowerShell找到ext4.vhdx的位置 # 一般在 %LOCALAPPDATA%\Packages\...\LocalState\ext4.vhdx # 第三步用diskpart压缩虚拟磁盘 diskpart # 在diskpart里执行 select vdisk fileC:\Users\你的用户名\AppData\Local\Packages\...\LocalState\ext4.vhdx attach vdisk readonly compact vdisk detach vdisk exit这个方法实测下来压缩效果明显但有两个注意点。一是wsl --shutdown会把所有正在运行的WSL实例都停掉数据记得先保存二是diskpart在压缩前必须把vdisk挂载为只读否则会报错。另外如果你平时大量用WSL做编译、下载文件频繁增删虚拟磁盘确实会越来越“胖”养成定期压缩的习惯就好。3.3 透明加密在内核态是怎么工作的“Linux透明加密”出现在热词里我猜不少人是因为公司数据安全管控才接触到这个概念。所谓透明加密简单说就是应用程序读写文件时自动完成加解密对用户来说无感知打开文件看到的是解密后的明文保存时自动加密回落到磁盘。它的应用场景很明确源代码防泄露、敏感文档管控、数据库文件保护防止有人把磁盘拆走或者把文件拷贝出去之后直接读到明文。Linux上实现透明加密有不同的层次。如果按文件系统级来看最简单粗暴的方式是磁盘级加密比如LUKS/dm-crypt整个分区加密开机输入密码解锁优点是性能好、和文件系统解耦缺点是解锁后所有用户都能按权限访问细粒度控制不足。如果要做到目录级别的加密ext4从内核4.1开始支持fscrypt可以针对某个目录开启加密不同的用户使用不同的密钥这个方案很适合Andorid和嵌入式设备。还有一种方式是走内核态的VFS层拦截也就是后文会提到的file_operations方向。在VFS层拦截read/write操作业务文件在写入磁盘前先加密读取时先解密再返回给用户态。这种方案的优点是灵活、可以做到文件级精细化控制配合策略服务器能做到不同的用户、不同的进程看到不同的结果。但它的门槛也高因为要侵入内核数据路径稳定性、兼容性、性能都是大考验不是随便写个驱动就能上生产环境的。如果你只是想在个人电脑上给某个目录加个密直接用ecryptfs-utils或者fscrypt就足够了没必要折腾内核开发。4. 系统服务与部署实操4.1 DNS配置踩坑记录这次复习到Linux的DNS配置又翻车了把踩坑过程完整记下来。现代Linux系统普遍使用systemd-resolved来管理DNS解析它最大的特点是会接管/etc/resolv.conf这个文件通常是指向/run/systemd/resolve/stub-resolv.conf的软链接。很多老教程教你直接修改/etc/resolv.conf结果一重启机子或者重启systemd-resolved改动就被覆盖了这就是最常见的坑。正确的做法是修改systemd-resolved的配置文件sudo vim /etc/systemd/resolved.conf需要修改的核心参数是DNS和FallbackDNS[Resolve] DNS114.114.114.114 223.5.5.5 FallbackDNS8.8.8.8改完后重启systemd-resolved服务sudo systemctl restart systemd-resolved然后用resolvectl status查看当前生效的DNS配置。如果发现某个网卡还在用自己的DNS设置那可能是NetworkManager或者netplan在下发配置。在Ubuntu上如果你用netplan配置网络DNS应该在/etc/netplan/*.yaml里写比如network: version: 2 ethernets: eth0: dhcp4: true nameservers: addresses: [114.114.114.114, 223.5.5.5]这里想强调的是排查DNS问题一定不要只看/etc/resolv.conf要顺着“谁在管理这块”这条线走。先弄清楚系统用的是systemd-resolved、NetworkManager还是纯dhclient再用对应的工具去改。否则就会出现“明明改了但就是不生效”的怪现象。4.2 Linux安装JDK的规范化流程Java开发在Linux上跑是家常便饭但安装JDK的方式五花八门。有人用apt安装有人下载压缩包解压有人用Docker各有各的场景。我比较推荐手动解压的方式因为可控性强、版本切换方便而且对离线环境友好。以JDK 17为例完整流程如下。先下载tar.gz包然后解压到统一目录sudo mkdir -p /opt/java sudo tar -zxvf jdk-17_linux-x64_bin.tar.gz -C /opt/java/为了以后切换版本方便我习惯把版本目录做一个软链接比如ln -s /opt/java/jdk-17.0.10 /opt/java/current。接着用update-alternatives把java注册到系统命令体系里sudo update-alternatives --install /usr/bin/java java /opt/java/current/bin/java 200 sudo update-alternatives --install /usr/bin/javac javac /opt/java/current/bin/javac 200设置环境变量把JAVA_HOME写进/etc/profile.d/java.shexport JAVA_HOME/opt/java/current export PATH$JAVA_HOME/bin:$PATH最后刷新并验证source /etc/profile java -version这里有个很容易踩的坑如果你用apt安装过OpenJDK再手动装新版本时直接java -version可能还是旧版本。原因就是update-alternatives的优先级没有覆盖掉apt包的设置这时候可以用sudo update-alternatives --config java手动选择优先级。还有一个细节JAVA_HOME不要直接指向具体的jdk-17.0.10目录而是指向软链接current这样下一次升级JDK只需要把新版本目录替换掉current的指向所有依赖JAVA_HOME的配置都不用动。4.3 在Linux上部署RocketMQ 5.5.0RocketMQ是阿里开源的消息队列中间件2026年初已经发布到了5.5.x系列。RocketMQ是Java写的所以先保证JDK 8环境就绪。我个人测试JDK 8和JDK 17都能跑通5.5版本但如果你用的是商业版或者对GC参数有特殊要求建议先看一下官方文档的版本矩阵。部署过程分为三步。第一步下载二进制发行包并解压wget https://dist.apache.org/repos/dist/release/rocketmq/5.5.0/rocketmq-all-5.5.0-bin-release.zip unzip rocketmq-all-5.5.0-bin-release.zip -d /opt/ cd /opt/rocketmq-all-5.5.0-bin-release第二步调整运行参数。默认的runserver.sh和runbroker.sh里配置的内存数值比较大比如runserver.sh默认给NameServer分配4g内存runbroker.sh默认给Broker分配8g内存如果开发机没有这么大内存启动必挂。需要修改这两个脚本里的JAVA_OPT把-Xms和-Xmx调小vim bin/runserver.sh # 找到 JAVA_OPT${JAVA_OPT} -server -Xms4g -Xmx4g ...改为 -Xms512m -Xmx512m vim bin/runbroker.sh # 同样把 -Xms8g -Xmx8g 改为 -Xms1g -Xmx1g第三步启动服务。先启动NameServer再启动Broker注意Broker后面要用-n参数指定NameServer地址nohup sh bin/mqnamesrv logs/namesrv.log 21 nohup sh bin/mqbroker -n 127.0.0.1:9876 logs/broker.log 21 验证是否启动成功sh bin/mqadmin clusterList -n 127.0.0.1:9876实际部署的时候还要注意三点。一是防火墙如果跨机器访问9876、10909、10911、10912这几个端口要放行二是磁盘Broker的commitlog目录默认在当前目录的store下如果系统盘比较小建议在conf/broker.conf里把storePathRootDir指向大分区三是最新版本5.x支持controller模式实现高可用架构升级时不用再依赖老旧的Master-Slave模式。4.4 常见的Linux桌面客户端安装现在Linux桌面生态越来越好了很多常用软件的官方客户端都开始提供Linux版本比如企业微信、豆包、希沃白板这些。安装方式大致分两种如果发行版是Ubuntu/Debian系下载.deb包后直接用dpkg安装sudo dpkg -i weixin_xxx.deb sudo apt-get install -f # 如果有依赖缺失用这个命令自动修复如果是Fedora/openSUSE这类RPM系用rpm -ivh安装。安装完如果快捷方式没出现在应用菜单需要注销重新登录或者手动执行gtk-update-icon-cache更新图标缓存。另外现在的浏览器生态也完全可以覆盖日常办公需求我的习惯是优先用Electron版或原生Linux版客户端实在没有的软件用浏览器访问Web版这样既能保证Linux环境的一致性又能最大程度减少对Windows虚拟机的依赖。5. 内核模块学习动态加载与file_operations5.1 为什么学习笔记绕不开file_operations如果说Linux用户态编程是“调用系统API”那内核态编程的核心就是“实现系统API背后的机制”。而file_operations这个结构体就是Linux VFS虚拟文件系统面向驱动层定义的一组函数指针。简单说当你在用户态调用open、read、write、ioctl这些系统调用时内核最终会走到对应驱动设备的file_operations里的回调函数。理解file_operations的价值有两个层面。第一个层面写驱动时必须实现它。字符设备、块设备、网络设备只要你想让某个设备在/dev下能以文件的方式访问就必须注册一套file_operations。第二个层面它是内核里做数据面拦截的基础。不管是文件系统过滤、透明加密还是访问审计很多方案的本质都是在file_operations或者相近的VFS路径里插入自己的逻辑。这也就是热词里“内核动态加载file_operations拦截read/write”这个方向的底层逻辑。学习这个不是为了搞什么旁门左道而是为了做安全的访问监控、透明的文件处理或者干脆就是研究内核是如何工作的。5.2 一个最小字符设备驱动的完整骨架直接上代码我写了一个最简单的字符设备驱动目的是展示file_operations结构体的完整生命周期。麻雀虽小五脏俱全它包含模块入口、设备注册、read/write回调、设备注销这几块。#include linux/module.h #include linux/init.h #include linux/fs.h #include linux/cdev.h #include linux/uaccess.h #define MY_MAJOR 238 #define MY_MAX_MINORS 1 static struct cdev my_cdev; static dev_t my_dev_id; static char kernel_buf[128] {0}; static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { size_t len strlen(kernel_buf); if (*pos len) return 0; if (count len - *pos) count len - *pos; if (copy_to_user(buf, kernel_buf *pos, count)) return -EFAULT; *pos count; return count; } static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos) { if (count sizeof(kernel_buf)) count sizeof(kernel_buf) - 1; memset(kernel_buf, 0, sizeof(kernel_buf)); if (copy_from_user(kernel_buf, buf, count)) return -EFAULT; return count; } static int my_open(struct inode *inode, struct file *filp) { return 0; } static int my_release(struct inode *inode, struct file *filp) { return 0; } static struct file_operations my_fops { .owner THIS_MODULE, .open my_open, .release my_release, .read my_read, .write my_write, }; static int __init my_init(void) { if (register_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS, mychardev) 0) { return -ENODEV; } cdev_init(my_cdev, my_fops); my_cdev.owner THIS_MODULE; if (cdev_add(my_cdev, MKDEV(MY_MAJOR, 0), MY_MAX_MINORS) 0) { unregister_chrdev_region(MKDEV(MY_MAJOR, 0), MY_MAX_MINORS); return -ENODEV; } my_dev_id MKDEV(MY_MAJOR, 0); printk(KERN_INFO mychardev: initialized\n); return 0; } static void __exit my_exit(void) { cdev_del(my_cdev); unregister_chrdev_region(my_dev_id, MY_MAX_MINORS); printk(KERN_INFO mychardev: exit\n); } module_init(my_init); module_exit(my_exit); MODULE_LICENSE(GPL);这段代码里有几个关键点。第一read/write回调里的copy_to_user和copy_from_user函数是核心媒介因为内核空间不能直接访问用户空间的指针必须通过这两个函数安全地拷贝数据。原因是用户态进程可能传一个无效或者越界的指针直接解引用会导致内核崩溃。第二read回调里维护了文件偏移*pos这样才能让用户态像读普通文件一样连续读取数据。第三register_chrdev_region指定的主设备号是静态指定的正式写驱动时更推荐用alloc_chrdev_region动态分配避免冲突。5.3 加载、卸载与调试insmod到dmesg写好驱动源码之后需要一个Makefile来编译。最简单的Makefile内容是obj-m mychardev.o all: make -C /lib/modules/$(shell uname -r)/build M$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M$(PWD) clean然后在源码目录执行make会生成mychardev.ko文件。加载和测试步骤如下# 加载模块 sudo insmod mychardev.ko # 查看内核日志确认初始化信息 dmesg | tail -10 # 创建设备节点 sudo mknod /dev/mychardev c 238 0 # 测试写入 echo hello kernel | sudo tee /dev/mychardev # 测试读取 sudo cat /dev/mychardev # 卸载模块 sudo rmmod mychardev如果一切正常cat /dev/mychardev时会输出hello kernel。这里有几个很常见的坑。第一insmod加载时可能会报“Operation not permitted”或者“Invalid module format”前者一般是因为当前内核的模块签名验证开启后者是因为编译模块时用的内核头文件版本和当前运行内核版本不一致。第二mknod时主设备号238必须和代码里一致次设备号填0否则打开设备节点会找不到设备。第三每次修改代码重新编译之前记得先rmmod卸载旧模块否则旧模块仍在内存中新模块无法注册同一个设备号。5.4 拦截read/write的正确学习姿势回到热词里的“拦截read/write”我建议把它当成一个内核编程练习课题来理解。最常见的合法做法是替换某个目标设备的file_operations结构体。比如有一个字符设备在系统中已经注册好了你想在它每次read的时候做一次计数或打印日志可以先保存原始的fops然后把自己的函数指针填进去static struct file_operations *target_fops; static struct file_operations orig_fops; static ssize_t hooked_read(struct file *filp, char __user *buf, size_t count, loff_t *pos) { printk(KERN_INFO hooked_read: pid%d count%zu\n, current-pid, count); return orig_fops.read(filp, buf, count, pos); } static int __init hook_init(void) { // 这里假设target_fops已经通过某个方式拿到 orig_fops *target_fops; target_fops-read hooked_read; return 0; }这种做法的风险在于如果目标设备的fops被其他模块同时修改或者驱动卸载时没有恢复原状很容易引发双重释放或者空指针。所以单纯做学习验证还好真要上生产必须配合完善的锁机制和引用计数管理。更推荐的正规路线是使用内核提供的fprobe/kprobe机制通过动态插桩的方式观测read/write调用而不直接修改目标结构体。这年头内核因为加固等原因直接修改fops的限制越来越多kprobe在调试和观测场景下是更安全、更优雅的选择。6. 今天遇到问题的排查速查表学习笔记的意义不在于记住所有细节而在于下次遇到问题时能快速度过卡壳阶段。把我这次实践过程中碰到的典型问题和解决方案整理成一张速查表方便日后翻找。现象或报错原因处理方案修改/etc/resolv.conf后DNS还是不对systemd-resolved或NetworkManager接管了配置改用systemctl edit systemd-resolved或在netplan里配置DNSunzip解压后文件名乱码文件名GBK编码系统认为是UTF-8用unzip -O GBK或7z解压已乱码则用convmv转码磁盘df显示满但在目录里du找不到大文件文件被进程删除但仍被占用lsof L1或lsof | grep deleted找到进程后重启或killWSL删除文件后C盘空间不释放ext4.vhdx虚拟磁盘不会自动收缩wsl --shutdown后用diskpart compact vdiskjava命令还是旧版本update-alternatives优先级覆盖了新JDKupdate-alternatives --config java手动选择启动RocketMQ报内存不足runserver.sh/runbroker.sh默认内存配置过高调小-Xms/-Xmx开发环境建议512m/1ginsmod报Invalid module format内核头文件版本与当前内核不一致确认uname -r和/lib/modules/$(uname -r)一致mknod后打开设备失败主次设备号与驱动中注册的不一致cat /proc/devices查看已注册主设备号find /usr -perm -4000发现可疑suid程序存在提权风险文件确认来源如异常直接删除并排查系统日志中文内容在终端显示为方块缺少中文字体或locale未配置安装fonts-noto-cjk设置LANGzh_CN.UTF-8排查问题最重要的一条经验永远先看日志。用户态的问题看journalctl -xe和应用程序自己的日志内核态的问题看dmesg网络问题看ss -lntp和ping对端一点一点缩小范围。不要瞎猜Linux的生态强大就强大在一切都有迹可查。最后再分享一个我自己的学习心得学Linux最高效的方式不是抱着书啃而是给自己布置一个具体的小项目去实现比如“搭一套带用户认证的Samba文件服务”“写一个统计磁盘占用的小脚本”“编译一个外部内核模块并接入一个自己的字符设备”。这个过程中遇到的所有报错和排查才是真正能沉淀成经验的东西。学完一个就记一篇笔记笔记里既要有命令抄录更要有自己的思考过程。等到几个月后再回看你会发现自己对Linux的理解已经完全不同了。
返回列表