Linux Kernel Driver 之 阻塞,内存访问以及所谓上下文的补充说明

本文深入解析Linux内核中阻塞与非阻塞的实现机制,包括等待队列的应用及O_NONBLOCK标志的处理。同时,详细阐述32位Linux系统下内存访问原理,涉及用户空间与内核空间的虚拟内存映射,以及内核内存分配的各种方法。
部署运行你感兴趣的模型镜像

Linux 内核的阻塞

阻塞亦即等待,有两种,一种忙等待,一种休眠等待。

应用层阻塞,多数是指休眠等待。

阻塞的实现,一般都是基于等待队列机制。

非阻塞:事件如果得不到满足,不会休眠等待,而是立即返回到用户空间。

Linux 系统默认的实现是阻塞方式,但是,linux系统也要求,驱动编程必须支持非阻塞方式的实现。

那么,阻塞,我们可以利用等待队列完成,那么非阻塞的方式呢?

非阻塞的实现

应用层,用户空间需要显式操作。可以在open时给定 O_NONBLOCK,或者,调用fcntl 函数设置。

驱动的行为:

如果指定了阻塞,那么事件不满足,进行休眠

如果指定了非阻塞,那么时间不满足,不休眠,立刻返回用户空间

那么应用层这样的设置,是怎么下来的呢?

每当用户层open一个设备时,内核会创建一个struct file对象,来描述这个文件被打开以后的状态属性,如果指定了 O_NONBLOCK,那么这个字段会被保存到该对象的 f_flags 域中。底层驱动操作时,用户自实现的比如 write read这样的callback中该struct file 对象的地址会被传下来,驱动层便可以通过该参数,去访问 f_flags 域,获取到该 O_NONBLOCK 标识。

if ((file->f_flags & O_NONBLOCK) != 0x00)
{
    // 存在 O_NONBLOCK 标识
    // 非阻塞方式
    if (condition)
    {
        // 满足条件,进行处理
    }
    else
    {
        // 不满足条件,直接返回
        return -EAGAIN;
    }
}
else
{
    // 阻塞方式
    // 采用等待队列实现
}

Linux 内核的内存访问(32bit机器为例)

首先,需要明确一些Linux 系统的一些实现规则

1. 不管是用户空间还是内核空间,程序访问的地址都是虚拟地址

2. CPU最终访问的地址必然是物理地址,虚拟地址转换成物理地址是通过MMU硬件逻辑单元

3. 有些CPU没有MMU,也能运行衍生版的linux(uclinux)

4. 4G虚拟内存,用户空间每个进程独占前3G虚拟内存(0x0~0xBFFFFFFF)

5. 4G虚拟内存,内核空间占后1G虚拟内存(0xC0000000~0xFFFFFFFF)

6. 用户空间只能最大访问3G的物理内存

7. 内核空间能够访问所有的物理内存

8. 一个物理地址可以有多个虚拟地址,一个虚拟地址不能对应多个物理地址

虚拟内存和物理内存的映射关系

虚拟内存分为:用户虚拟内存和内核虚拟内存

用户虚拟内存和物理内存的映射采用动态映射(用户需要访问某块物理内存,内核会动态创建用户虚拟内存和物理内存的映射关系(页表)),如果将来不再访问,  需要将这种映射关系解除,动态映射的缺点在于内存访问的效率不高!

内核虚拟内存和物理内存的映射采用静态映射(又称一一映射),在内核启动初始化的时候就已经完成这种一一映射关系,将来内核只需直接访问即可,无需再次建立映射关系,加快内存的访问效率。

内核虚拟内存如果真的与物理内存严格的一一对应,内核访问内存的效率得到提高,但是内核只能访问1G的物理内存,无法访问多余的物理内存,内核如何访问其余的物理内存或者任何一个设备的物理地址呢?

内核将1G的内核虚拟内存进行分区划分,既满足内存访问的效率,又能满足内核能够访问所有的物理内存。

具体划分地址由低到高如下(对于X86架构):

直接内存映射区

内核初始化时,将1G内核虚拟内存的前896M跟物理内存的前896M进行一一映射,这块虚拟内存的访问效率高。这块内核虚拟内存区域又称低端内存(lowmem)

动态内存映射区

如果要访问其余的物理内存或者任何一个物理地址,可以将物理内存和物理地址跟动态内存映射区的内核虚拟内存或者地址进行映射(建立页表),如果不在使用,记得要解除地址映射;缺点是内存的访问效率不高,但是能够保证访问所有的物理内存和地址,默认大小为120M。

永久内存映射区(kmap) /  固定内存映射区(kmap_atomic)

永久就是固定,固定就是永久,如果对某块物理内存或者某个物理地址要频繁的访问,可以将此地址跟永久或者固定内存映射区的虚拟内存进行映射,不再使用时,可以不用解除地址映射,优点是即可访问其余的物理内存或者地址也加快内存的访问效率。

两者不同的是:前者用于进程上下文,后者用于中断上下文,大小:各占4M。

动态内存映射区+永久+固定 = 高端内存

Linux内核内存分配的方法

kmalloc/kfree

void *kmalloc(size_t size, gfp_t flags)

函数功能:

从直接内存映射区分配内存,申请的物理,虚拟都连续,内存的大小为最小为32字节,最大为4M

参数:

size,大小

flags,指定分配内存时的行为,一般指定为:

1. GFP_KERNEL:告诉内核,请努力帮我把这次内存分配搞定,如果内存不足,会导致休眠,不能用于中断上下文,成功概率比较大

2. GFP_ATOMIC:如果分配内存,内存不足,不会导致休眠而是立即返回,可以用于中断上下文,成功概率比较小

返回值:

返回分配的内核起始虚拟地址

void kfree(void *addr)

__get_free_pages/free_pages

1page=1页=4K

unsigned long __get_free_pages(gfp_t gfp_mask, unsigned int order)

从直接内存映射区分配;物理,虚拟都连续;最小为1页,最大为4M。

mask:指定分配内存时的行为,一般指定为:

GFP_KERNEL:告诉内核,请努力帮我把这次内存分配搞定,如果内存不足,会导致休眠,不能用于中断上下文,成功概率比较大

GFP_ATOMIC:如果分配内存,内存不足,不会导致休眠。立即返回,可以用于中断上下文,成功概率比较小

order: 0 分配 1页,1 分配 2页,2 分配 3页...

返回值:

返回分配的内核起始虚拟地址,注意数据类型的转换!

vmalloc/vfree

void *vmalloc(int size)

从动态内存映射区分配内存;虚拟上连续,物理上不一定连续;如果内存不足,会导致休眠,不能用于中断上下文;最大理论上默认120M。

返回值:返回起始的虚拟地址

void vfree(void *addr) //释放内存

全局数组,申请内存

比如: static char g_buf[5*1024*1024]; 

另:

1. 在内核的启动参数中指定vmalloc=250M,告诉内核将内核的动态内存映射区的大小由默认的120M扩展到250M。

setenv bootargs root=/dev/nfs nfsroot=... vmalloc=250M

saveenv

boot

2. 在内核的启动参数中指定mem=10M,告诉内核将物理内存的最后10M预留出来,预留给驱动单独使用,利用ioremap函数进行映射即可访问

setenv bootargs root=/dev/nfs nfsroot=... mem=10M

saveenv

boot

将来驱动利用ioremap函数将这10M的物理内存进行映射,即可访问

Linux 内核 ioremap

说明:

1. 不论是在用户空间还是在内核空间,一律不允许访问物理地址

2. 如果要访问外设的物理地址必须要进行地址映射,将物理地址要不映射到用户的虚拟地址上,要不映射到内核的虚拟地址上,一旦映射完毕,将来访问这个虚拟地址就是在访问对应的物理地址

如何将设备的物理地址和物理内存映射到内核空间的虚拟地址和内存上呢?

利用ioremap函数即可完成映射。

void *ioremap(unsigned long phys_addr, int size)

函数功能:

将物理内存或者地址映射到内核的虚拟内存或者地址上

将来访问这个内核虚拟内存或者地址就是在访问对应的物理内存和地址

说白了,就是把物理内存或者其他的一些硬件寄存器之类的映射到了内核的内存上。如果映射到用户空间的内存?后续mmap!

参数:

phys_addr,要映射的物理起始地址

size,物理内存的大小

"物理内存":不单单指内存条,外设对应的寄存器也通常称之为物理内存

返回值:

返回映射的对应内核起始的虚拟地址

解除地址映射:iounmap(映射的内核起始虚拟地址);

上下文

前面一直总是进程上下文,中断上下文。

中断上下文,典型的几个:

1. request_irq 注册的中断服务函数。

2. 基于软中断的 tasklet。

3. 软中断中注册的callback。

4. 信号处理函数(也是不允许休眠的)

以上的场景,其实都是通过特定的API,调用,挂载callback,至于callback的执行时机,我们是不能确定的,执行的上下文,都是“别人”那边场景。

所以,在内核编程中,注册 callback的时候,一定要小心,我们注册的函数,是否是中断上下文,这些其实都是需要一一甄别的,其实也不多,也就那几个...

为什么驱动中的函数,会有进程上下文的概念?

一般的驱动函数,比如前文中的 hello 的驱动,其中的函数,如果我们不是注册特定的callback,或者使用tasklet之类,一般都是运行在进程上下文中的,因为fops中的callback函数,如 open release,都是应用进程通过 open close 等等调用的,其实这就是所谓的进程上下文。

您可能感兴趣的与本文相关的镜像

Linly-Talker

Linly-Talker

AI应用

Linly-Talker是一款创新的数字人对话系统,它融合了最新的人工智能技术,包括大型语言模型(LLM)、自动语音识别(ASR)、文本到语音转换(TTS)和语音克隆技术

<think>我们注意到用户的问题集中在Linux系统中top命令显示%id(空闲CPU百分比)高达90%,但同时服务器没有明显进程占用CPU,而用户却观察到CPU使用率很高的情况。这看起来矛盾,因为%id高通常意味着CPU空闲,但用户又说CPU使用率高。这里需要澄清几个概念: 1. **top命令中的CPU状态分类**: - **us**:用户空间占用CPU百分比 - **sy**:内核空间占用CPU百分比 - **ni**:用户进程空间内改变过优先级的进程占用CPU百分比 - **id**:空闲CPU百分比 - **wa**:等待输入输出的CPU时间百分比 - **hi**:硬件中断占用百分比 - **si**:软件中断占用百分比 - **st**:虚拟机偷取时间(仅虚拟化环境) 通常,CPU使用率的计算是:$100\% - \%\text{id} = \%\text{used}$。所以,如果%id为90%,那么CPU使用率应该是10%(即空闲90%,使用10%)。但是用户提到“CPU使用率很高”,这可能是一种误解,或者用户观察到的现象与top显示不一致。 2. **问题分析**: - 可能性1:用户误解了指标。如果%id为90%,那么实际CPU使用率只有10%,这并不高。用户可能误以为其他指标(如wa、hi)高就是CPU使用率高,但实际上这些指标属于CPU使用的一部分,而%id高意味着总体上CPU是空闲的。 - 可能性2:用户观察到的“CPU使用率很高”可能是通过其他工具(如云监控平台)看到的,而该监控平台的采集方式可能和top不同。例如,云监控可能将I/O等待(wa)也视为使用率,而top中wa是单独列出的,且不计入%id(空闲)。 - 可能性3:虽然%id高,但用户观察到系统响应缓慢,因此认为CPU使用率高(但实际上可能是其他原因,如I/O阻塞内存不足等)。 3. **解决方案**: - **步骤1**:确认用户观察到的“CPU使用率很高”的来源。如果是top命令本身,那么%id=90%意味着使用率只有10%,并不高。如果是其他监控工具,则需要查看该工具的具体指标定义。 - **步骤2**:如果用户确实观察到系统响应慢而%id高,则需要排查其他可能导致系统缓慢的因素: - 检查I/O等待(%wa):如果%wa很高,说明CPU在等待磁盘I/O,此时虽然CPU空闲,但系统性能受磁盘限制。 - 检查内存使用:使用`free -h`查看内存和swap使用情况。如果swap被频繁使用(si/so数值高),则说明内存不足,系统在进行内存交换,导致性能下降。 - 检查中断和软中断:使用`mpstat -P ALL 1`查看每个CPU核心的详细状态,特别是%irq(硬件中断)和%soft(软中断)是否过高。网络或磁盘中断过多可能导致系统繁忙。 - 检查上下文切换:使用`vmstat 1`,查看cs(上下文切换)值是否过高。过多的上下文切换会导致CPU时间浪费在内核调度上。 4. **特殊情况**: - **隐藏进程**:虽然用户说没有进程占用CPU,但可能有内核线程或隐藏进程(如rootkit)在运行。使用`top -c`查看完整命令行,或者使用`ps -eLf`查看所有线程。也可以使用`atop`等更高级的工具。 - **内核问题**:某些内核bug可能导致CPU使用率统计异常。检查`dmesg`查看内核日志。 - **虚拟化环境**:在虚拟机中,%st(steal time)可能很高,这意味着物理CPU被其他虚拟机争用。如果%st高,则需要联系云服务提供商。 5. **总结**: 根据用户描述,top显示%id为90%,这意味着CPU使用率仅为10%,因此正常情况下不应认为CPU使用率高。需要进一步澄清用户所说的“CPU使用率很高”具体指什么指标。 由于用户的问题描述存在矛盾(%id高意味着CPU空闲,而用户说CPU使用率高),因此我们需要引导用户确认: - 请确认您是通过哪个工具观察到“CPU使用率很高”?如果是top,那么%id=90%意味着使用率只有10%,并不高。 - 如果系统响应慢,请关注其他指标(如wa, hi, si)以及内存和I/O情况。 如果用户坚持认为CPU使用率高(例如其他监控工具显示使用率90%),而top却显示%id=90%,则可能是监控工具的采集频率或计算方式不同(例如,采集瞬间的使用率峰值被捕捉,而top是平均值)。此时建议使用`mpstat -P ALL 1`连续观察,或者使用`pidstat 1`查看每个进程的CPU使用率。 **相关问题**: 1. Linux系统中top命令的%id指标和CPU使用率的关系是什么? 2. 如何正确理解top命令输出的各项CPU指标? 3. 当系统响应缓慢时,如何区分是CPU瓶颈还是I/O瓶颈? 4. 在虚拟化环境中,如何分析CPU性能问题? </think>### 🔍 Linux系统中top命令显示%id高却CPU使用率高的原因及解决方案 在Linux系统中,top命令显示**%id(空闲CPU百分比)高达90%**,但您观察到服务器CPU使用率很高,这种看似矛盾的现象通常由以下原因引起: --- ### ⚙️ 核心原因分析 1. **指标解读误区** - `%id` 表示**空闲CPU时间百分比**,计算公式为: $$ \small \text{\%id} = \left(1 - \frac{\text{总CPU使用时间}}{\text{总CPU时间}}\right) \times 100 $$ - **矛盾点**:当`%id=90%`时,理论上CPU使用率仅10%,与您观察到的"高CPU使用率"不符。可能原因是: - 监控工具统计口径不同(如容器环境) - 瞬时峰值未被top捕捉(默认3秒刷新) 2. **I/O等待瓶颈(%wa高)** ```bash top - 15:20:30 up 10 days, 1:23, 1 user, load average: 18.32, 17.64, 15.82 %Cpu(s): 5.3 us, 2.1 sy, 0.0 ni, 90.4 id, 2.2 wa, 0.0 hi, 0.0 si, 0.0 st ``` - **关键指标**:若`%wa`(I/O等待)异常高 → CPU因等待磁盘/网络而"假空闲" - **验证命令**: ```bash iostat -dx 2 5 # 观察%util列是否>90% dstat --disk-util # 实时磁盘利用率 ``` 3. **中断风暴(IRQ占用)** - 硬件故障或驱动异常导致大量中断请求 - **诊断步骤**: ```bash cat /proc/interrupts # 检查中断分布 watch -n1 "grep MHz /proc/cpuinfo" # CPU频率是否因降频而虚高 ``` 4. **虚拟化环境干扰(%st高)** - 在云服务器中,`%st`(steal time)表示被宿主机占用的CPU时间 - **检测**: ```bash vmstat 1 5 | awk '{print $16}' # 持续观察st列 ``` 若`%st >15%` → 联系云服务商调整资源配额[^3]。 --- ### ️ 系统化解决方案 #### ✅ 场景1:I/O瓶颈导致高负载 **特征**:`%wa`高 + `load average`远大于CPU核心数 **解决**: 1. 定位高I/O进程: ```bash iotop -oP # 显示实际I/O进程 pidstat -d 2 # 按进程统计I/O ``` 2. 优化措施: - 数据库:优化慢查询,增加缓存 - 文件系统:禁用`atime`挂载选项 ```bash mount -o remount,noatime / ``` - 更换SSD或升级RAID配置 #### ✅ 场景2:硬件中断异常 **特征**:`/proc/interrupts`中某项计数飙升 **解决**: 1. 更新驱动程序: ```bash lspci -v | grep -i ethernet # 查网卡型号 yum update ixgbe-driver # 示例:更新Intel网卡驱动 ``` 2. 调整中断均衡: ```bash apt install irqbalance # 安装中断优化工具 systemctl restart irqbalance ``` #### ✅ 场景3:隐藏内核活动 **特征**:`top`无进程但`sy%`(系统态)高 **诊断**: ```bash perf top -g # 追踪内核函数调用 dmesg -T | grep "error" # 检查内核错误日志 ``` **措施**: - 升级内核:`yum update kernel` - 禁用问题模块:`rmmod faulty_module` --- ### 📊 关键诊断命令速查 | 目的 | 命令 | 观察指标 | |---------------------|-------------------------------|--------------------------| | **CPU负载溯源** | `pidstat -u 1` | %usr/%system 列 | | **I/O瓶颈定位** | `ioprofiler -d /dev/sda` | await, svctm | | **中断分析** | `mpstat -P ALL 1` | %irq/%soft 列 | | **虚拟化干扰** | `virsh domstats <VM_ID>` | cpu.time/cpu.halt | > 💡 **操作建议**:生产环境优先使用`nohup [command] &`后台运行诊断工具,避免SSH断开导致中断[^1]。 --- ### ❓ 相关问题 1. 如何区分CPU高负载是由用户进程还是内核活动引起的? 2. Linux系统中`%wa`指标突然飙升应如何快速响应? 3. 在KVM虚拟化环境中如何准确测量虚拟机的真实CPU使用率? 4. `perf`工具在内核性能分析中有哪些高级用法? > 引用来源: > [^1]: Linux top命令的线程级监控原理与应用场景 > [^2]: 虚拟化环境中CPU资源隔离的技术实现 > [^3]: 云服务器资源争用的检测与优化实践
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值