Linux-0.12内核打开文件过程--sys_open源码分析

本文深入解析了Linux系统中进程打开文件的过程,详细介绍了sys_open函数的实现原理,并通过具体代码展示了如何构造进程与文件之间的连接关系。此外,还对关键函数如open_namei及其依赖的辅助函数进行了分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上图展示了进程打开文件使用的内核数据结构,所以要打开文件,就要构造上图中的关系。

int sys_open(const char *filename,int flag,int mode)
{
	struct m_inode *inode;
	struct file *f;
	int i,fd;
	
	mode&=0777&~current->umask;
	//在filp数组中寻找“空闲位置”
	for(fd=0;fd<NR_OPEN;fd++)
		if(!current->filp[fd])
			break;
	if(fd>=NR_OPEN)
		return -EINVAL;
	current->close_on_exec&=~(1<<fd);
	//在file_table中寻找“空闲位置”
	f=0+file_table;
	for(i=0;i<NR_FILE,i++,f++)
		if(!f->f_count)
			break;
	if(i>=NR_FILE)
		return -EINVAL;
	
	//此时我们让进程对应文件句柄fd的文件结构指针指向搜索到的文件结构,并令文件引用计数加1,然后调用open_namei()函数执行打开操作,如果返回值小于0,则说明出错,于是释放刚刚申请的文件结构,返回出错码i;若文件打开成功,则inode是打开文件的i节点指针。
	(current->filp[fd]=f)->count++;
	if((i=open_namei(filename,flag,mode,&inode))<0)
	{
		current->filp[fd]=NULL;
		f->f_count=0;
		return i;
	}
	....
	f->f_mode=inode->i_mode;
	f->f_flags=flag;
	f->f_inode=inode;
	f->f_pos=0;
	return fd;
}

注解:f->f_inode=inode,这步骤完成之后,本文上来的那个图的关系就全连起来了。
sys_open核心就在于open_namei()函数,这个函数会牵扯到整个文件系统,理解这个函数就对0.12内核的文件系统有一个整体的认识,下面来重点分析open_namei()函数,这会涉及到dir_namei()、get_dir()、find_entry()、new_inode()、add_entry()、follow_link()、bmap()等函数,我们需要首先说明这些函数的功能。

1.static struct m_inode* get_dir(const char *pathname,struct m_inode *inode)

功能:根据函数给定的路径名进行搜索,直到达到最顶端目录(/etc/bin/vi--bin/是最顶端目录喔!)

参数:

pathname--路径名

inode--指定其实目录的i节点

返回值:目录或者文件的i节点指针

2.static struct m_inode* dir_namei(const char *pathname,int *namelen,const char **name,struct m_inode *base)

功能:返回指定目录名的i节点指针,以及在最顶层目录的名称

参数:

pathname--目录路径名(in)

namelen--路径名长度(out)

name--返回的最顶层目录名(out)

base--搜索其实目录的i节点(in)

返回:指定目录名最顶层目录的i节点指针和最顶层目录名和长度,出错返回NULL

static struct m_inode* dir_namei(const char *pathname,int *namelen,const char **name,struct m_inode *base)
{
	char c;
	const char *basename;
	struct m_inode *dir;
	
	if(!dir=get_dir(pathname,base))
		return NULL;
	basename=pathname;
	while(c=get_fs_byte(pathname++)
		if(c=='/')
			basename=pathname;
	*namelen=pathname-basename-1;
	*name=basename;
	return dir;
}


比如给定目录/abc/def/gh

get_dir返回def目录对应的内存i节点

dir_namei,不仅完成get_dir功能,而且返回gh和长度2

3.static struct buffer_head* find_entry(struct m_inode **dir,const char *name,int namelen,struct dir_entry **res_dir)
功能:查找指定目录和文件名的目录项
参数:*dir--指定目录i节点指针;name--文件名;namelen--文件名长度
返回:成功则返回高速缓冲区指针,并在res_dir处返回的目录项结构指针。失败则返回空指针NULL。

4.open_namei函数

int open_namei(const char *pathname,int flag,int mode,struct m_inode **res_inode)
{
	const char *basename;
	int inr,dev,namelen;
	struct m_inode *dir,*inode;
	struct buffer_head *bh;
	struct dir_entry *de;
	
	...
	if(!(dir=dir_namei(pathname,&namelen,&basename,NULL)))
		return -ENOENT
	if(!namelen)
	{
		if(!(flag & (O_ACCMODE|O_CREATE|O_TRUNC)))
		{
			*res_inode=dir;
			return 0;
		}
		iput(dir);
		return -EISDIR;
	}
	bh=find_entry(&dir,basename,namelen,&de);
	if(!bh)
	{
	//没有对应的目录项,很有可能是创建文件...
	}
	//如果bh!=NULL
	inr=de->inode;
	dev=dir->i_dev;
	brelse(bh);
	if(flag & O_EXCL)
	{
		iput(dir);
		return -EEXIST;
	}
	//读取目录项的i节点内容
	if(!(inode=follow_link(dir,iget(dev,inr))))
		return -EACCES;
	...
	inode->i_atime=CURRENT_TIME;
	if(flag & o_TRUNC)
		truncate(inode);
	*res_inode=inode;
	return;
}

<think>好的,我现在需要帮助用户解决Linux服务器CPU负载过高的问题。用户想要具体的排查命令和思路,所以我得整理一个系统化的流程,确保他们能一步步定位问题。 首先,我应该从整体负载情况开始。用户可能已经注意到服务器变慢,但需要确认CPU负载是否真的过高。使用uptime或top命令查看平均负载,这三个数值分别代表1分钟、5分钟、15分钟的平均负载。如果这些值超过CPU核心数,说明存在高负载问题。比如,双核CPU的话,如果负载超过2,可能就有问题。 接下来需要确定是用户进程还是系统进程导致的高负载。top命令是一个很好的起点,因为它能实时显示各个进程的CPU占用情况。在top界面中,按Shift+P可以按CPU使用率排序,找出占用高的进程。这时候,用户可能不知道哪些进程是正常的,所以需要提醒他们注意异常的进程名或高占用的未知进程。 如果发现某个进程占用过高,可能需要进一步分析。这时候可以用pidstat命令,指定进程ID和采样间隔,比如pidstat -p <PID> 1 5,这样可以持续观察该进程的CPU使用情况。此外,strace命令可以跟踪进程的系统调用,帮助确定是否有频繁的系统调用或阻塞问题。不过要注意,strace可能会影响性能,生产环境需谨慎使用。 除了用户空间的进程,还要考虑内核态的CPU使用情况。top命令中,如果sy(系统CPU时间)过高,说明内核态任务繁重。这时候需要使用perf工具来分析,比如perf top查看热点函数,或者记录性能数据后离线分析。另外,mpstat -P ALL 1可以查看每个CPU核心的使用情况,帮助判断是否有单个核心被占满的情况。 中断和上下文切换也是可能的因素。使用vmstat 1查看上下文切换(cs列)和中断(in列),如果数值异常高,可能需要进一步分析。比如,用pidstat -w查看进程的上下文切换情况,或者检查硬件中断是否过多,使用cat /proc/interrupts查看中断分布。 如果怀疑是I/O等待导致CPU负载高,top中的wa(等待I/O时间)值高的话,需要检查磁盘I/O。iostat -x 1可以查看设备的I/O使用率和等待时间。同时,iotop可以找出I/O高的进程。如果磁盘I/O确实很高,可能需要优化查询或增加缓存。 对于多线程应用,如果存在线程竞争或死锁,可能导致CPU空转。这时候可以用pstree -p <PID>查看线程情况,或者用top -H -p <PID>查看线程级别的CPU使用。结合jstack(对于Java应用)可以分析线程状态,但要注意用户的应用类型。 另外,短期故障可以使用dmesg | grep -i kill检查是否有OOM Killer终止进程,这可能导致资源争用。系统日志/var/log/messages也可能有相关错误信息。 最后,如果用户没有安装高级工具,可以用ps aux --sort=-%cpu | head快速找出占用高的进程,或者检查/proc/stat和/proc/<PID>/stat中的CPU时间信息。 总结下来,步骤应该是:确认负载情况→定位高CPU进程→分析进程行为→检查内核和中断→I/O分析→线程分析→日志检查。需要提醒用户在生产环境中谨慎使用某些诊断工具,避免影响服务,同时考虑历史数据对比,比如sar工具可以提供历史性能数据。如果用户是新手,可能需要更详细的命令解释和注意事项,比如在试运行环境测试,避免直接在生产环境执行可能影响服务的命令。</think>以下是一个系统化的CPU负载问题排查指南,包含诊断思路、常用命令及解决方案: --- ### **一、快速诊断流程图** ```mermaid graph TD A[发现CPU负载高] --> B{查看整体负载} B --> |1.uptime检查| C[确认负载阈值] B --> |2.top/htop| D[定位高CPU进程] D --> E{分析进程行为} E --> |用户态高| F[应用代码/配置优化] E --> |内核态高| G[系统调用/驱动分析] D --> H{检查关联指标} H --> I[上下文切换/中断] H --> J[I/O等待] H --> K[锁竞争/死锁] ``` --- ### **二、具体排查步骤** #### **1. 确认CPU负载情况** ```bash # 查看平均负载(1/5/15分钟) $ uptime 12:30:45 up 15 days, 2:30, 2 users, load average: 3.25, 2.80, 2.40 # 显示CPU核心数 $ grep 'model name' /proc/cpuinfo | wc -l 4 ``` **判断标准**:若1分钟负载 > CPU核心数*0.7 需关注 --- #### **2. 定位高CPU进程** ```bash # 实时进程监控(按P排序CPU) $ top -c PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 1234 mysql 20 0 28.765g 4.823g 12384 S 198.3 15.2 100:30.12 /usr/sbin/mysqld ``` **关键列解析**: - `%CPU`:进程CPU占用率 - `S`:进程状态(R=运行中, S=睡眠) - `TIME+`:累计CPU时间 --- #### **3. 深入分析进程行为** ```bash # 查看进程详细状态 $ pidstat -p 1234 1 5 # 每秒采样,共5次 # 跟踪系统调用 $ strace -p 1234 -c # 统计系统调用(生产环境慎用) $ perf top -p 1234 # 分析函数级CPU消耗 ``` --- #### **4. 检查内核态CPU使用** ```bash # 查看CPU时间分布 $ mpstat -P ALL 1 CPU %usr %nice %sys %iowait %irq %soft %steal %guest %gnice %idle 0 12.34 0.00 3.45 0.12 0.01 0.23 0.00 0.00 0.00 83.85 # 查看软中断分布 $ cat /proc/softirqs ``` **关键指标**: - `%sys`高:内核空间消耗大(常见于频繁系统调用) - `%soft`高:软中断处理耗时(网络包处理等) --- #### **5. 分析上下文切换** ```bash # 查看全局上下文切换 $ vmstat 1 procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- r b swpd free buff cache si so bi bo in cs us sy id wa st 3 0 0 1680044 292136 4582308 0 0 12 24 234 4567 20 15 65 0 0 # 查看进程级上下文切换 $ pidstat -w -p 1234 1 ``` **判断标准**: - `cs`(context switch) > 100,000/秒需关注 - 自愿切换多:I/O等待 - 非自愿切换多:CPU竞争 --- #### **6. 检查I/O等待** ```bash # 查看I/O压力 $ iostat -x 1 Device r/s w/s rkB/s wkB/s rrqm/s wrqm/s %rrqm %wrqm r_await w_await aqu-sz rareq-sz wareq-sz svctm %util vda 0.12 2.34 4.56 234.56 0.00 0.12 0.00 4.76 0.23 1.23 0.01 38.15 100.23 0.12 0.34 # 查看进程I/O $ iotop -oP ``` --- ### **三、常见问题场景及解决** #### **场景1:用户态CPU高** - **特征**:`%usr`高,某个进程持续占用CPU - **解决方案**: 1. 分析进程堆栈:`gdb -p <PID>` → `thread apply all bt` 2. Java应用:`jstack <PID> > stack.log` 分析线程状态 3. Python应用:`py-spy top -p <PID>` #### **场景2:内核态CPU高** - **特征**:`%sys`高,常见于频繁系统调用 - **解决方案**: 1. 使用`perf record -g -p <PID>`记录性能数据 2. 检查系统调用频率:`strace -c -p <PID>` 3. 优化文件操作(减少`open/close`调用) #### **场景3:大量软中断** - **特征**:`%soft`高,常见于网络密集型应用 - **解决方案**: 1. 检查网卡多队列:`ethtool -l eth0` 2. 启用RSS/RPS:`/sys/class/net/eth0/queues/` 3. 调整`net.core.netdev_budget`参数 --- ### **四、高级诊断工具** | 工具 | 用途 | 示例命令 | |---------------|-------------------------------|------------------------------| | `perf` | 函数级性能分析 | `perf record -g -p <PID>` | | `bpftrace` | 动态内核追踪 | `bpftrace -e 'tracepoint:syscalls:sys_enter_* { @[probe] = count(); }'` | | `sar` | 历史性能数据分析 | `sar -u -f /var/log/sa/sa15` | | `numastat` | NUMA内存分析 | `numastat -p <PID>` | --- ### **五、预防建议** 1. **监控告警**:部署Prometheus+Node Exporter监控`node_load1`指标 2. **压测预案**:使用`stress-ng`模拟高负载场景进行测试 3. **内核参数调优**: ```bash # 减少进程切换开销 echo 'kernel.sched_migration_cost_ns=5000000' >> /etc/sysctl.conf # 调整进程调度策略 echo 'kernel.sched_child_runs_first=0' >> /etc/sysctl.conf ``` 通过以上步骤,90%以上的CPU负载问题可在15分钟内定位到根本原因。建议生产环境操作前先在测试环境验证诊断方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值