Linux字符驱动设备开发入门之框架搭建

声明

本博客所记录的关于正点原子i.MX6ULL开发板的学习笔记,(内容参照正点原子I.MX6U嵌入式linux驱动开发指南,可在正点原子官方获取正点原子Linux开发板 — 正点原子资料下载中心 1.0.0 文档,旨在如实记录我在学校学习该开发板过程中所遭遇的各类问题以及详细的解决办法。其初衷纯粹是为了个人知识梳理、学习总结以及日后回顾查阅方便,同时也期望能为同样在学习这款开发板的同学或爱好者提供一些解决问题的思路和参考。我尽力保证内容的准确性和可靠性,但由于个人知识水平和实践经验有限,若存在错误或不严谨之处,恳请各位读者批评指正。

责任声明:虽然我力求提供有效的问题解决办法,但由于开发板使用环境、硬件差异、软件版本等多种因素的影响,我的笔记内容不一定适用于所有情况。对于因参考本笔记而导致的任何直接或间接损失,我不承担任何法律责任。使用本笔记内容的读者应自行承担相关风险,并在必要时寻求专业技术支持。

1. 字符设备驱动简介

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、 IIC、 SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

Linux 应用程序对驱动程序的调用

在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。

应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作。

每一个系统调用,在驱动中都有与之对应的一个驱动函数,在 Linux 内核文件 include/linux/fs.h 中有个叫做 file_operations 的结构体,此结构体就是 Linux 内核驱动操作函数集合

file_operation 结构体中比较重要的、常用的函数:

第 1589 行, owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。第 1590 行, llseek 函数用于修改文件当前的读写位置。

第 1591 行, read 函数用于读取设备文件。

第 1592 行, write 函数用于向设备文件写入(发送)数据。

第 1596 行, poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写。

第 1597 行, unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。

第 1598 行, compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上, 32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。

第 1599 行, mmap 函数用于将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。

第 1601 行, open 函数用于打开设备文件。

第 1603 行, release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。第 1604 行, fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。

第 1605 行, aio_fsync 函数与 fasync 函数的功能类似,只是 aio_fsync 是异步刷新待处理的数据。

在字符设备驱动开发中最主要的工作就是实现上面这些函数,不一定全部都要实现,但是像 open、 release、 write、 read 等都是需要实现的,具体需要实现哪些函数还是要看具体的驱动要求。

2. 字符设备驱动开发步骤

2.1 驱动模块的加载和卸载

Linux 驱动有两种运行方式,第一种就是将驱动编译进 Linux 内核中,这样当 Linux 内核启动的时候就会自动运行驱动程序。第二种就是将驱动编译成模块(Linux 下模块扩展名为.ko),在Linux 内核启动以后使“insmod”/modprob”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。而且在调试的时候只需要加载或者卸载驱动模块即可,不需要重启整个系统。

模块有加载和卸载两种操作,我们在编写驱动的时候需要注册这两种操作函数,模块的加载和卸载注册函数如下:

module_init(xxx_init); //注册模块加载函数

module_exit(xxx_exit); //注册模块卸载函数

module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用“insmod”命令加载驱动的时候, xxx_init 这个函数就会被调用。 module_exit()函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用。字符设备驱动模块加载和卸载模板如下所示:

驱动编译完成以后扩展名为.ko,有两种命令可以加载驱动模块: insmod和modprobe, insmod是最简单的模块加载命令,此命令用于加载指定的.ko 模块,比如加载 drv.ko 这个驱动模块,命令如下:

insmod drv.ko

insmod 命令不能解决模块的依赖关系,比如 drv.ko 依赖 first.ko 这个模块,就必须先使用insmod 命令加载 first.ko 这个模块,然后再加载 drv.ko 这个模块。但是 modprobe 就不会存在这个问题, modprobe 会分析模块的依赖关系,然后会将所有的依赖模块都加载到内核中,因此modprobe 命令相比 insmod 要智能一些。 modprobe 命令主要智能在提供了模块的依赖性分析、错误检查、错误报告等功能,推荐使用 modprobe 命令来加载驱动。 modprobe 命令默认会去/lib/modules/<kernel-version>目录中查找模块,比如本书使用的 Linux kernel 的版本号为 4.1.15,因此 modprobe 命令默认会到/lib/modules/4.1.15 这个目录中查找相应的驱动模块,一般自己制作的根文件系统中是不会有这个目录的,所以需要自己手动创建

驱动模块的卸载使用命令“rmmod”即可,比如要卸载 drv.ko,使用如下命令即可: rmmod drv.ko

也可以使用“modprobe -r”命令卸载驱动,比如要卸载 drv.ko,命令如下: modprobe -r drv.ko

使用 modprobe 命令可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则就不能使用 modprobe 来卸载驱动模块。所以对于模块的卸载,还是推荐使用 rmmod 命令

2.2 字符设备注册与注销

当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。字符设备的注册和注销函数原型如下所示:

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)static inline void unregister_chrdev(unsigned int major, const char *name)

register_chrdev 函数用于注册字符设备,此函数一共有三个参数,这三个参数的含义如下: major: 主设备号, Linux 下每个设备都有一个设备号,设备号分为主设备号和次设备号两部分

name:设备名字,指向一串字符串。

fops: 结构体 file_operations 类型指针,指向设备的操作函数集合变量。unregister_chrdev 函数用户注销字符设备,此函数有两个参数,这两个参数含义如下: major: 要注销的设备对应的主设备号。

name: 要注销的设备对应的设备名。

一般字符设备的注册在驱动模块的入口函数 xxx_init 中进行,字符设备的注销在驱动模块的出口函数 xxx_exit 中进行。

加入字符设备注册和注销后的代码:

CHRDEVBASE_MAJOR是宏定义,使用cat /proc/devices可以查看当前设备的设备号,自己取一个没被占用的,我选的是66, CHRDEVBASE_NAME就是名字,也是宏定义。

这里使用了 printk 来输出信息,而不是 printf!因为在 Linux 内核中没有 printf 这个函数。 printk 相当于 printf 的孪生兄妹, printf运行在用户态, printk 运行在内核态。在内核中想要向控制台输出或显示一些内容,必须使用printk 这个函数。不同之处在于, printk 可以根据日志级别对消息进行分类,一共有 8 个消息级别,这 8 个消息级别定义在文件 include/linux/kern_levels.h 里面,定义如下:

一共定义了 8 个级别,其中 0 的优先级最高, 7 的优先级最低。如果要设置消息级别,参考如下示例:

printk(KERN_EMERG "gsmi: Log Shutdown Reason\n");

上述代码就是设置“gsmi: Log Shutdown Reason\n”这行消息的级别为 KERN_EMERG。在具体的消息前面加上 KERN_EMERG 就可以将这条消息的级别设置为 KERN_EMERG。如果使用 printk 的 时 候 不 显 式 的 设 置 消 息 级 别 , 那 么 printk 将 会 采 用 默 认 级 别MESSAGE_LOGLEVEL_DEFAULT, MESSAGE_LOGLEVEL_DEFAULT 默认为 4。在 include/linux/printk.h 中有个宏 CONSOLE_LOGLEVEL_DEFAULT,定义如下: #define CONSOLE_LOGLEVEL_DEFAULT 7

CONSOLE_LOGLEVEL_DEFAULT 控制着哪些级别的消息可以显示在控制台上,此宏默认为7,意味着只有优先级高于 7 的消息才能显示在控制台上。

这个就是 printk 和 printf 的最大区别,可以通过消息级别来决定哪些消息可以显示在控制台上。默认消息级别为 4, 4 的级别比 7 高,所示直接使用 printk 输出的信息是可以显示在控制台上的。

2.3 设备号

Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。 Linux 提供了一个名为 dev_t 的数据类型表示设备号, dev_t 定义在文件 include/linux/types.h 里面,就是 unsigned int 类型,是一个 32 位的数据类型。 32 位的数据构成了主设备号和次设备号两部分,其中高 12 位为主设备号, 20 位为次设备号。系统中主设备号范围为 0~4095,所以在选择主设备号的时候一定不要超过这个范围

2.4 实现设备的具体操作函数

&test_fops是Linux 内核驱动操作函数

每个对应的实现:

这四个函数就是 chrtest 设备的 open、 read、 write 和 release 操作函数

2.5 添加LICENSE 和作者信息

最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。 LICENSE 和作者信息的添加使用如下两个函数:

MODULE_LICENSE() //添加模块 LICENSE 信息

MODULE_AUTHOR() //添加模块作者信息

3. chrdevbase 字符设备驱动开发实验

3.1、创建 VSCode 工程

在 Linux_Drivers 目录下新建一个名为 1_chrdevbase 的子目录来存放本实验所有文件

在 1_chrdevbase 目录中新建 VSCode 工程

3.2、添加头文件路径

打开 VSCode,按下“Crtl+Shift+P”打开 VSCode 的控制台,然后输入“C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件,第 5 行的 includePath 表示头文件路径,需要将 Linux 源码里面的头文件路径添加进来,也就是我们前面移植的 Linux 源码中的头文件路径。添加头文件路径以后的 c_cpp_properties.json的文件内容如下所示:

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/mayachao/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include","/home/mayachao/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include","/home/mayachao/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated/"],"defines": [],"compilerPath": "/usr/bin/clang","cStandard": "c11","cppStandard": "c++17","intelliSenseMode": "clang-x64"}],"version": 4}



4.3、编写实验程序

在 Ubuntu 中输入“man 2 open”即可查看 open 函数的详细内容,其他以此类推。

chrdevbaseAPP.c如下所示:

4.4、编译驱动程序

编译驱动程序,也就是 chrdevbase.c 这个文件,我们需要将其编译为.ko 模块,创建Makefile 文件,然后在其中输入如下内容:

第 1 行, KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,大家根据自己的实际情况填写即可。

Makefile 编写好以后输入“make”命令编译驱动模块,编译过程如图

编译成功以后就会生成一个叫做 chrdevbaes.ko 的文件,此文件就是 chrdevbase 设备的驱动模块。至此, chrdevbase 设备的驱动就编译成功。

测试 APP 比较简单,只有一个文件,因此就不需要编写 Makefile 了,直接输入命令编译。因为测试 APP 是要在 ARM 开发板上运行的,所以需要使用 arm-linux-gnueabihf-gcc 来编译,输入如下命令:

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp

编译完成以后会生成一个叫做 chrdevbaseApp 的可执行程序,输入如下命令查看chrdevbaseAPP 这个程序的文件信息:

file chrdevbaseApp

chrdevbaseAPP 这个可执行文件是 32 位 LSB 格式, ARM 版本的,因此 chrdevbaseAPP 只能在 ARM 芯片下运行。

4.5 运行测试

1、加载驱动模块

驱动模块 chrdevbase.ko 和测试软件 chrdevbaseAPP 都已经准备好了,接下来就是运行测试。为了方便测试, Linux 系统选择通过 TFTP 从网络启动,并且使用 NFS 挂载网络根文件系统,确保 uboot 中 bootcmd 环境变量的值为:

tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000

bootargs 环境变量的值为:

console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:/home/mayachao/linux/nfs/ rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off

设置好以后启动 Linux 系统,检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话自行创建。 注意,“/lib/modules/4.1.15”这个目录用来存放驱动模块,使用modprobe 命令加载驱动模块的时候,驱动模块要存放在此目录下。“/lib/modules”是通用的,不管你用的什么板子、什么内核,这部分是一样的。不一样的是后面的“4.1.15”,这里要根据你所使用的 Linux 内核版本来设置。

将 chrdevbase.ko 和 chrdevbaseAPP 复制到 rootfs/lib/modules/4.1.15 目录中,命令如下:

sudo cp chrdevbase.ko chrdevbaseApp /home/mayachao/linux/nfs/rootfs/lib/modules/4.1.15/ -f

输入“depmod”命令以后会自动生成 modules.alias、modules.symbols 和 modules.dep 这三个文件,如图

有些根文件系统可能没有 depmod 这个命令,如果没有这个命令就只能重新配置busybox,使能此命令,然后重新编译 busybox。

输入如下命令加载 chrdevbase.ko 驱动文件:

insmod chrdevbase.ko

modprobe chrdevbase.ko

输入“lsmod”命令即可查看当前系统中存在的模块

输入如下命令查看当前系统中有没有 chrdevbase 这个设备:

cat /proc/devices

2、创建设备节点文件

输入如下命令创建/dev/chrdevbase 这个设备节点文件:

mknod /dev/chrdevbase c 66 0

“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“ 66”是设备的主设备号,“ 0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看

如果 chrdevbaseAPP 想要读写 chrdevbase 设备,直接对/dev/chrdevbase 进行读写操作即可。

3 chrdevbase 设备操作测试

输入如下命令:

./chrdevbaseApp /dev/chrdevbase

4、卸载驱动模块

如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉 chrdevbase 这个设备:

rmmod chrdevbase.ko

卸载以后使用 lsmod 命令查看 chrdevbase 这个模块还存不存在,结果如图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/74677.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小刚说C语言刷题——第15讲 多分支结构

1.多分支结构 所谓多分支结构是指在选择的时候有多种选择。根据条件满足哪个分支&#xff0c;就走对应分支的语句。 2.语法格式 if(条件1) 语句1; else if(条件2) 语句2; else if(条件3) 语句3; ....... else 语句n; 3.示例代码 从键盘输入三条边的长度&#xff0c;…

Apache httpclient okhttp(1)

学习链接 Apache httpclient & okhttp&#xff08;1&#xff09; Apache httpclient & okhttp&#xff08;2&#xff09; httpcomponents-client github apache httpclient文档 apache httpclient文档详细使用 log4j日志官方文档 【Java基础】- HttpURLConnection…

洛谷题单3-P1420 最长连号-python-流程图重构

题目描述 输入长度为 n n n 的一个正整数序列&#xff0c;要求输出序列中最长连号的长度。 连号指在序列中&#xff0c;从小到大的连续自然数。 输入格式 第一行&#xff0c;一个整数 n n n。 第二行&#xff0c; n n n 个整数 a i a_i ai​&#xff0c;之间用空格隔开…

使用binance-connector库获取Binance全市场的币种价格,然后选择一个币种进行下单

一个完整的示例,展示如何使用 api 获取Binance全市场的币种价格,然后选择一个最便宜的币种进行下单操作 代码经过修改,亲测可用,目前只可用于现货,合约的待开发 获取市场价格:使用client.ticker_price()获取所有交易对的当前价格 账户检查:获取账户余额,确保有足够的资…

算法设计学习10

实验目的及要求&#xff1a; 本查找实验旨在使学生深入了解不同查找算法的原理、性能特征和适用场景&#xff0c;培养其在实际问题中选择和应用查找算法的能力。通过实验&#xff0c;学生将具体实现多种查找算法&#xff0c;并通过性能测试验证其在不同数据集上的表现&#xff…

5天速成ai agent智能体camel-ai之第1天:camel-ai安装和智能体交流消息讲解(附源码,零基础可学习运行)

嗨&#xff0c;朋友们&#xff01;&#x1f44b; 是不是感觉AI浪潮铺天盖地&#xff0c;身边的人都在谈论AI Agent、大模型&#xff0c;而你看着那些密密麻麻的代码&#xff0c;感觉像在读天书&#xff1f;&#x1f92f; 别焦虑&#xff01;你不是一个人。很多人都想抓住AI的风…

MySQL介绍及使用

1. 安装、启动、配置 MySQL 1. 安装 MySQL 更新软件包索引 sudo apt update 安装 MySQL 服务器 sudo apt install mysql-server 安装过程中可能会提示你设置 root 用户密码。如果没有提示&#xff0c;可以跳过&#xff0c;后续可以手动设置。 2. 配置 MySQL 运行安全脚本…

九、重学C++—类和函数

上一章节&#xff1a; 八、重学C—动态多态&#xff08;运行期&#xff09;-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/147004745?spm1001.2014.3001.5502 本章节代码&#xff1a; cpp/cppClassAndFunc.cpp CuiQingCheng/cppstudy - 码云 - 开源中国…

lua和C的交互

1.C调用lua例子 #include <iostream> #include <lua.hpp>int main() {//用于创建一个新的lua虚拟机lua_State* L luaL_newstate();luaL_openlibs(L);//打开标准库/*if (luaL_dofile(L, "test.lua") ! LUA_OK) {std::cerr << "Lua error: &…

java高并发------守护线程Daemon Thread

文章目录 1.概念2.生命周期与行为2. 应用场景3. 示例代码4. 注意事项 1.概念 Daemon &#xff1a; 滴门 在Java中&#xff0c;线程分为两类&#xff1a;用户线程(User Thread)和守护线程(Daemon Thread)。 守护线程是后台线程&#xff0c;主要服务于用户线程&#xff0c;当所…

Docker存储策略深度解析:临时文件 vs 持久化存储选型指南

Docker存储策略深度解析&#xff1a;临时文件 vs 持久化存储选型指南 一、存储类型全景对比二、临时存储适用场景与风险2.1 最佳使用案例2.2 风险警示 三、持久化存储技术选型3.1 Volume核心优势Volume管理命令&#xff1a; 3.2 Bind Mount适用边界挂载模式对比&#xff1a; 四…

【Linux网络#18】:深入理解select多路转接:传统I/O复用的基石

&#x1f4c3;个人主页&#xff1a;island1314 &#x1f525;个人专栏&#xff1a;Linux—登神长阶 目录 一、前言&#xff1a;&#x1f525; I/O 多路转接 为什么需要I/O多路转接&#xff1f; 二、I/O 多路转接之 select 1. 初识 select2. select 函数原型2.1 关于 fd_set 结…

高级:微服务架构面试题全攻略

一、引言 在现代软件开发中&#xff0c;微服务架构被广泛应用于构建复杂、可扩展的应用程序。面试官通过相关问题&#xff0c;考察候选人对微服务架构的理解、拆分原则的掌握、服务治理的能力以及API网关的运用等。本文将深入剖析微服务架构相关的面试题&#xff0c;结合实际开…

使用MQTTX软件连接阿里云

使用MQTTX软件连接阿里云 MQTTX软件阿里云配置MQTTX软件设置 MQTTX软件 阿里云配置 ESP8266连接阿里云这篇文章里有详细的创建过程&#xff0c;这里就不再重复了&#xff0c;需要的可以点击了解一下。 MQTTX软件设置 打开软件之后&#xff0c;首先点击添加进行创建。 在阿…

【HFP】蓝牙Hands-Free Profile(HFP)核心技术解析

蓝牙 Hands-Free Profile&#xff08;HFP&#xff09;作为车载通信和蓝牙耳机的核心协议&#xff0c;定义了设备间语音交互的标准化流程&#xff0c;并持续推动着无线语音交互体验的革新。自2002年首次纳入蓝牙核心规范以来&#xff0c;HFP历经多次版本迭代&#xff08;最新为v…

轻量化大模型微调工具XTuner指令微调实战(下篇)

接着上篇文章《轻量化大模型微调工具XTuner指令微调实战&#xff08;上篇&#xff09;》来接着写教程。 一、模型转换 模型训练后会自动保存成 PTH 模型&#xff08;例如 iter_500.pth&#xff09;&#xff0c;我们需要利用 xtuner convert pth_to_hf 将其转换为 HuggingFace…

pyTorch框架使用CNN进行手写数字识别

目录 1.导包 2.torchvision数据处理的方法 3.下载加载手写数字的训练数据集 4.下载加载手写数字的测试数据集 5. 将训练数据与测试数据 转换成dataloader 6.转成迭代器取数据 7.创建模型 8. 把model拷到GPU上面去 9. 定义损失函数 10. 定义优化器 11. 定义训练…

强化学习课程:stanford_cs234 学习笔记(3)introduction to RL

文章目录 前言7 markov 实践7.1 markov 过程再叙7.2 markov 奖励过程 MRP&#xff08;markov reward process&#xff09;7.3 markov 价值函数与贝尔曼方程7.4 markov 决策过程MDP&#xff08;markov decision process&#xff09;的 状态价值函数7.4.1 状态价值函数7.4.2 状态…

操作系统 4.5-文件使用磁盘的实现

通过文件进行磁盘操作入口 // 在fs/read_write.c中 int sys_write(int fd, const char* buf, int count) {struct file *file current->filp[fd];struct m_inode *inode file->inode;if (S_ISREG(inode->i_mode))return file_write(inode, file, buf, count); } 进程…

libreoffice-help-common` 的版本(`24.8.5`)与官方源要求的版本(`24.2.7`)不一致

出现此错误的原因主要是软件包依赖冲突&#xff0c;具体分析如下&#xff1a; ### 主要原因 1. **软件源版本不匹配&#xff08;国内和官方服务器版本有差距&#xff09; 系统中可能启用了第三方软件源&#xff08;如 PPA 或 backports 源&#xff09;&#xff0c;导致 lib…