详解sys_read和sys_write

本文解析了Linux内核中的read和write系统调用实现原理,重点介绍了文件指针管理和文件读写流程,并分析了关键函数如fget_light、vfs_read、vfs_write的工作机制。

    内核源码:linux-2.6.38.8.tar.bz2

    目标平台:ARM体系结构

 

    在Linux内核中,系统调用read和write的定义如下所示:

/* arch/arm/include/asm/posix_types.h */
#ifdef __GNUC__
typedef long long		__kernel_loff_t;
#endif

/* include/linux/types.h */
#if defined(__GNUC__)
typedef __kernel_loff_t		loff_t;
#endif

/* fs/read_write.c */
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
	struct file *file;
	ssize_t ret = -EBADF;
	int fput_needed;

	file = fget_light(fd, &fput_needed);
	if (file) {
		loff_t pos = file_pos_read(file); //返回文件偏移量file->f_pos
		ret = vfs_read(file, buf, count, &pos);
		file_pos_write(file, pos); //重置文件偏移量file->f_pos = pos
		fput_light(file, fput_needed);
	}

	return ret;
}

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
		size_t, count)
{
	struct file *file;
	ssize_t ret = -EBADF;
	int fput_needed;

	file = fget_light(fd, &fput_needed);
	if (file) {
		loff_t pos = file_pos_read(file); //在ARM平台上loff_t的长度都为64位
		ret = vfs_write(file, buf, count, &pos);
		file_pos_write(file, pos);
		fput_light(file, fput_needed); //这里主要是配合fget_light函数递减文件指针的引用计数
	}

	return ret;
}
    fget_light函数用于根据文件描述符fd获得相应的文件指针,即structfile结构体实例。而fput_light函数在这里的主要作用是递减文件指针的引用计数。源代码如下所示:
/* fs/file_table.c */
struct file *fget_light(unsigned int fd, int *fput_needed)
{
	struct file *file;
	struct files_struct *files = current->files; //从当前进程获取struct files_struct结构体实例

	*fput_needed = 0;
	if (atomic_read(&files->count) == 1) { //struct files_struct结构体实例的引用计数,大于1表示多个进程共享该实例
		file = fcheck_files(files, fd); //即files->fdt->->fd[fd]
	} else {
		rcu_read_lock();
		file = fcheck_files(files, fd);
		if (file) {
			if (atomic_long_inc_not_zero(&file->f_count)) //文件指针引用计数如果不为零则递增且返回真值,为零则直接返回假且不递增
				*fput_needed = 1;
			else
				/* Didn't get the reference, someone's freed */
				file = NULL;
		}
		rcu_read_unlock();
	}

	return file;
}
/* include/linux/file.h */
static inline void fput_light(struct file *file, int fput_needed)
{
	if (fput_needed)
		fput(file);
}

/* fs/file_table.c */
void fput(struct file *file)
{
	if (atomic_long_dec_and_test(&file->f_count)) //文件指针引用计数递减之后等于零则为真
		__fput(file);
}
    紧接着,各自调用vfs_read或vfs_write函数。源代码如下所示:
/* fs/read_write.c */
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
	ssize_t ret;

	if (!(file->f_mode & FMODE_READ)) //确认读取标志
		return -EBADF;
	if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read)) //read和aio_read函数指针必须有一个为真
		return -EINVAL;
	if (unlikely(!access_ok(VERIFY_WRITE, buf, count))) //实际未使用参数VERIFY_WRITE(这里是将要将数据写入用户空间内存)
		return -EFAULT;

	ret = rw_verify_area(READ, file, pos, count);
	if (ret >= 0) {
		count = ret;
		if (file->f_op->read) //read函数指针为真
			ret = file->f_op->read(file, buf, count, pos); //调用文件系统的读函数
		else
			ret = do_sync_read(file, buf, count, pos); //使用异步I/O实现同步读操作
		if (ret > 0) {
			fsnotify_access(file); //实现文件系统事件监控的IN_ACCESS事件
			add_rchar(current, ret); //累加当前进程已读数据的字节数
		}
		inc_syscr(current); //递增当前进程read系统调用的计数
	}

	return ret;
}

ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
	ssize_t ret;

	if (!(file->f_mode & FMODE_WRITE)) //确认写入标志
		return -EBADF;
	if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write)) //write和aio_write函数指针必须有一个为真
		return -EINVAL;
	if (unlikely(!access_ok(VERIFY_READ, buf, count))) //实际未使用参数VERIFY_READ(这里是将要从用户空间内存读取数据)
		return -EFAULT;

	ret = rw_verify_area(WRITE, file, pos, count);
	if (ret >= 0) {
		count = ret;
		if (file->f_op->write) //write函数指针为真
			ret = file->f_op->write(file, buf, count, pos); //调用文件系统的写函数
		else
			ret = do_sync_write(file, buf, count, pos); //使用异步I/O实现同步写操作
		if (ret > 0) {
			fsnotify_modify(file); //实现文件系统事件监控的IN_MODIFY事件
			add_wchar(current, ret); //累加当前进程已写数据的字节数
		}
		inc_syscw(current); //递增当前进程write系统调用的计数
	}

	return ret;
}
    1、access_ok函数用于确认所传入的用户空间内存[buf,buf+count]是否有效。源代码如下所示:
/* arch/arm/include/asm/uaccess.h */
#define access_ok(type,addr,size)	(__range_ok(addr,size) == 0)

/* We use 33-bit arithmetic here... */
#define __range_ok(addr,size) ({ \
	unsigned long flag, roksum; \
	__chk_user_ptr(addr);	\ //只有函数声明,用于Sparse工具
	//roksum = addr + size;
	//如果上一步操作没有发生上溢出(意味着addr加size的算术结果小于等于0xffffffff)则roksum = roksum - flag,否则空操作;
	//如果上一步操作有发生下溢出(意味着addr加size小于flag)则flag = 0,否则空操作。
	//简单地讲,如果addr + size < current_thread_info()->addr_limit则flag = 0,否则flag保持原有的初始值current_thread_info()->addr_limit不变。
	__asm__("adds %1, %2, %3; sbcccs %1, %1, %0; movcc %0, #0" \								
		: "=&r" (flag), "=&r" (roksum) \
		: "r" (addr), "Ir" (size), "0" (current_thread_info()->addr_limit) \ //“0”表示用括号里变量的值初始化第一个操作数,也就是flag
		: "cc"); \
	flag; }) //flag的值就是整个表达式的值
	
#define USER_DS		TASK_SIZE

/* arch/arm/include/asm/memory.h */
#define TASK_SIZE		(UL(CONFIG_PAGE_OFFSET) - UL(0x01000000))
    其中当前进程addr_limit的值在程序执行时通常在start_thread函数中通过调用set_fs函数将其初始化为USER_DS值。如果CONFIG_PAGE_OFFSET的值配置为0xc0000000,则USER_DS的值为0xbf000000,也就是所传入的用户空间地址buf+count必须小于该值。

    2、rw_verify_area函数主要用于确认文件可读写的区域。源代码如下所示:

/* arch/arm/include/asm/page.h */
#define PAGE_SHIFT		12
#define PAGE_SIZE		(_AC(1,UL) << PAGE_SHIFT)
#define PAGE_MASK		(~(PAGE_SIZE-1))

/* include/linux/pagemap.h */
#define PAGE_CACHE_MASK		PAGE_MASK

/* include/linux/kernel.h */
#define INT_MAX		((int)(~0U>>1))

/* include/linux/fs.h */
#define MAX_RW_COUNT (INT_MAX & PAGE_CACHE_MASK)

/* fs/read_write.c */
int rw_verify_area(int read_write, struct file *file, loff_t *ppos, size_t count)
{
	struct inode *inode;
	loff_t pos; //loff_t被定义为long long
	int retval = -EINVAL;

	inode = file->f_path.dentry->d_inode;
	if (unlikely((ssize_t) count < 0)) //读写字节数不能小于零
		return retval;
	pos = *ppos;
	if (unlikely(pos < 0)) {
		if (!unsigned_offsets(file)) //未配置FMODE_UNSIGNED_OFFSET标志,即不能将pos当作无符号整型
			return retval;
		if (count >= -pos) //pos加count的算术结果超过了unsigned long long能容纳的最大值(这里将pos当作无符号整型)
			return -EOVERFLOW;
	} else if (unlikely((loff_t) (pos + count) < 0)) { //pos加count的和超过了loff_t所能表达的最大正整数
		if (!unsigned_offsets(file))
			return retval;
	}

	if (unlikely(inode->i_flock && mandatory_lock(inode))) { //文件系统支持强制锁并且文件的S_ISGID置位但S_IXGRP必须未置位
		retval = locks_mandatory_area( //只被rw_verify_area和locks_verify_truncate等两个函数所调用,用于检查锁冲突
			read_write == READ ? FLOCK_VERIFY_READ : FLOCK_VERIFY_WRITE,
			inode, file, pos, count);
		if (retval < 0)
			return retval;
	}
	retval = security_file_permission(file,
				read_write == READ ? MAY_READ : MAY_WRITE); //安全模块检查
	if (retval)
		return retval;
	return count > MAX_RW_COUNT ? MAX_RW_COUNT : count; //这里的MAX_RW_COUNT等于0x7ffff000
}
    3、do_sync_read和do_sync_write等两个函数是使用相应的异步I/O来实现同步读写。源代码如下:
/* fs/read_write.c */
ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)
{
	struct iovec iov = { .iov_base = buf, .iov_len = len }; //保存将要存储数据的内存空间
	struct kiocb kiocb;
	ssize_t ret;
	
	//初始化kiocb
	init_sync_kiocb(&kiocb, filp);
	kiocb.ki_pos = *ppos;
	kiocb.ki_left = len;
	kiocb.ki_nbytes = len;

	for (;;) {
		ret = filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);
		if (ret != -EIOCBRETRY) //等于EIOCBRETRY时将触发下一次读取尝试
			break;
		wait_on_retry_sync_kiocb(&kiocb);
	}

	if (-EIOCBQUEUED == ret) //将获得完成事件
		ret = wait_on_sync_kiocb(&kiocb);
	*ppos = kiocb.ki_pos;
	return ret;
}

ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
	struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len }; //保存将要写入的数据
	struct kiocb kiocb;
	ssize_t ret;
	
	//初始化kiocb
	init_sync_kiocb(&kiocb, filp);
	kiocb.ki_pos = *ppos;
	kiocb.ki_left = len;
	kiocb.ki_nbytes = len;

	for (;;) {
		ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos);
		if (ret != -EIOCBRETRY) //等于EIOCBRETRY时将触发下一次写入尝试
			break;
		wait_on_retry_sync_kiocb(&kiocb);
	}

	if (-EIOCBQUEUED == ret)
		ret = wait_on_sync_kiocb(&kiocb);
	*ppos = kiocb.ki_pos;
	return ret;
}

/* fs/read_write.c */
static void wait_on_retry_sync_kiocb(struct kiocb *iocb)
{
	set_current_state(TASK_UNINTERRUPTIBLE); //设置进程为不可被中断的睡眠状态
	if (!kiocbIsKicked(iocb))
		schedule(); //让出CPU时间
	else
		kiocbClearKicked(iocb);
	__set_current_state(TASK_RUNNING); //设置进程为可运行状态
}

/* fs/aio.c */
ssize_t wait_on_sync_kiocb(struct kiocb *iocb)
{
	while (iocb->ki_users) {
		set_current_state(TASK_UNINTERRUPTIBLE);
		if (!iocb->ki_users)
			break;
		io_schedule();
	}
	__set_current_state(TASK_RUNNING);
	return iocb->ki_user_data; //返回已成功读写的字节数
}
    有些文件系统在定义普通文件的操作函数struct file_operations时,将成员read指向do_sync_read函数,而成员aio_read指向通用函数generic_file_aio_read;成员write指向do_sync_write函数,而成员aio_write指向通用函数generic_file_aio_write。

    综合上文的分析可知,文件读写的大部分操作实际上是在文件系统层来完成的,VFS层所做的操作非常之少。

#include <reg51.h> #include <intrins.h> // 硬件接口定义 sbit LCD_RS = P2^0; sbit LCD_RW = P2^1; sbit LCD_EN = P2^2; sbit DS1820 = P3^7; sbit BUZZER = P1^7; sbit KEY_SET = P3^0; sbit KEY_UP = P3^1; sbit KEY_DN = P3^2; sbit KEY_OK = P3^3; // 全局变量 - 添加volatile确保中断安全 volatile unsigned int sys_tick = 0; // 系统毫秒计数器 unsigned char hour = 12, minute = 0, second = 0; unsigned char countdown_min = 0, countdown_sec = 0; unsigned char temp_integer = 25, temp_decimal = 0; bit alarm_enabled = 1; bit in_setting = 0; bit last_hour_alarm = 0; bit countdown_active = 0; unsigned char set_mode = 0; // 0:正常, 1:设置分钟, 2:设置倒计时, 3:设置小时 // 温度传感器状态机 enum TEMP_STATE { TEMP_IDLE, TEMP_START_CONV, TEMP_WAIT_CONV, TEMP_READ }; enum TEMP_STATE temp_state = TEMP_IDLE; unsigned int last_temp_time = 0; unsigned int conv_start_time = 0; // 蜂鸣器控制 bit beep_active = 0; unsigned int beep_end = 0; // LCD功能函数 (优化减少延时) void lcd_write_cmd(unsigned char cmd) { LCD_RS = 0; LCD_RW = 0; P0 = cmd; LCD_EN = 1; _nop_(); _nop_(); _nop_(); _nop_(); // 缩短延时 LCD_EN = 0; } void lcd_write_data(unsigned char dat) { LCD_RS = 1; LCD_RW = 0; P0 = dat; LCD_EN = 1; _nop_(); _nop_(); _nop_(); _nop_(); LCD_EN = 0; } void lcd_init() { lcd_write_cmd(0x38); // 8位数据线,2行显示 lcd_write_cmd(0x0C); // 显示开,光标关 lcd_write_cmd(0x01); // 清屏 lcd_write_cmd(0x06); // 写入后光标右移 } void lcd_set_cursor(unsigned char x, unsigned char y) { unsigned char addr = y ? 0xC0 + x : 0x80 + x; lcd_write_cmd(addr); } // 数字转字符串辅助函数 (优化内存使用) void byte_to_str(unsigned char num, char *str) { str[0] = num/10 + '0'; str[1] = num%10 + '0'; str[2] = '\0'; } // DS18B20温度传感器函数 (非阻塞实现) void ds18b20_delay_us(unsigned char us) { while (us--) _nop_(); } bit ds18b20_init() { bit presence; DS1820 = 0; ds18b20_delay_us(240); // 480us以上复位 DS1820 = 1; ds18b20_delay_us(30); // 等待15-60us presence = !DS1820; // 检测应答信号 ds18b20_delay_us(210); // 完成时序 return presence; } void ds18b20_write_bit(bit val) { DS1820 = 0; _nop_(); _nop_(); // 2us DS1820 = val; ds18b20_delay_us(60); // 保持60-120us DS1820 = 1; } unsigned char ds18b20_read_bit() { unsigned char val; DS1820 = 0; _nop_(); _nop_(); // 2us DS1820 = 1; _nop_(); _nop_(); // 等待14us val = DS1820; ds18b20_delay_us(45); // 完成60us周期 return val; } void ds18b20_write_byte(unsigned char dat) { unsigned char i; for(i=0; i<8; i++) { ds18b20_write_bit(dat & 0x01); dat >>= 1; } } unsigned char ds18b20_read_byte() { unsigned char i, dat = 0; for(i=0; i<8; i++) { if(ds18b20_read_bit()) dat |= 0x01 << i; } return dat; } // 温度读取状态机处理器 void temp_handler() { unsigned char LSB, MSB; int temp; switch(temp_state) { case TEMP_IDLE: if(sys_tick - last_temp_time >= 5000) { // 每5秒读取一次 if(ds18b20_init()) { temp_state = TEMP_START_CONV; } } break; case TEMP_START_CONV: ds18b20_write_byte(0xCC); // 跳过ROM ds18b20_write_byte(0x44); // 启动温度转换 conv_start_time = sys_tick; temp_state = TEMP_WAIT_CONV; break; case TEMP_WAIT_CONV: if(sys_tick - conv_start_time >= 750) { // 等待转换完成 if(ds18b20_init()) { temp_state = TEMP_READ; } } break; case TEMP_READ: ds18b20_write_byte(0xCC); // 跳过ROM ds18b20_write_byte(0xBE); // 读取暂存器 LSB = ds18b20_read_byte(); MSB = ds18b20_read_byte(); temp = (MSB << 8) | LSB; temp_integer = temp >> 4; temp_decimal = (temp & 0x0F) * 625 / 1000; // 精确到0.0625 temp_state = TEMP_IDLE; last_temp_time = sys_tick; break; } } // 蜂鸣器控制 (非阻塞) void start_beep(unsigned int duration) { beep_end = sys_tick + duration; beep_active = 1; BUZZER = 0; // 启动蜂鸣器 } void update_beep() { if(beep_active && sys_tick >= beep_end) { BUZZER = 1; // 关闭蜂鸣器 beep_active = 0; } } // 按键扫描 (添加长按支持) void scan_keys() { static unsigned char key_debounce = 0; static unsigned int key_press_time[4] = {0}; #define KEY_SET_IDX 0 #define KEY_UP_IDX 1 #define KEY_DN_IDX 2 #define KEY_OK_IDX 3 #define LONG_PRESS_MS 1000 // 检测按键按下 if(!KEY_SET || !KEY_UP || !KEY_DN || !KEY_OK) { if(key_debounce == 0) { key_debounce = 1; // 记录按键按下时间 if(!KEY_SET) key_press_time[KEY_SET_IDX] = sys_tick; if(!KEY_UP) key_press_time[KEY_UP_IDX] = sys_tick; if(!KEY_DN) key_press_time[KEY_DN_IDX] = sys_tick; if(!KEY_OK) key_press_time[KEY_OK_IDX] = sys_tick; } } else { if(key_debounce) { key_debounce = 0; // 处理短按 if(!KEY_SET) { set_mode++; if(set_mode > 3) set_mode = 0; in_setting = (set_mode != 0); } // 处理SET长按 if(sys_tick - key_press_time[KEY_SET_IDX] >= LONG_PRESS_MS) { set_mode = 0; in_setting = 0; } // 其他按键处理逻辑保持不变... } } } // 时间处理函数 (基于系统时钟) void process_time() { static unsigned int last_sec = 0; if(sys_tick - last_sec >= 1000) { last_sec = sys_tick; second++; // 原时间处理逻辑保持不变... } } // 显示刷新函数 (优化刷新频率) void refresh_display() { static unsigned int last_refresh = 0; if(sys_tick - last_refresh < 200) return; // 200ms刷新一次 last_refresh = sys_tick; char temp_str[3]; // 仅更新变化的部分 lcd_set_cursor(5, 0); byte_to_str(hour, temp_str); lcd_write_data(temp_str[0]); lcd_write_data(temp_str[1]); lcd_write_data(':'); byte_to_str(minute, temp_str); lcd_write_data(temp_str[0]); lcd_write_data(temp_str[1]); lcd_write_data(':'); byte_to_str(second, temp_str); lcd_write_data(temp_str[0]); lcd_write_data(temp_str[1]); // 第二行显示逻辑保持不变... } // 定时器0中断服务函数 (1ms中断) void timer0_isr() interrupt 1 { TH0 = 0xFC; // 1ms @11.0592MHz TL0 = 0x66; sys_tick++; // 更新系统时钟 } // 主函数 (优化任务调度) void main() { // 定时器初始化 TMOD = 0x01; // 定时器0工作方式1 TH0 = 0xFC; // 定时1ms TL0 = 0x66; ET0 = 1; // 允许定时器0中断 EA = 1; // 开启总中断 TR0 = 1; // 启动定时器 lcd_init(); BUZZER = 1; // 关闭蜂鸣器 // 显示初始信息 lcd_show_string(0, 0, "Smart Clock V2.0"); lcd_show_string(0, 1, "Optimized System"); delay_ms(1000); lcd_write_cmd(0x01); // 清屏 while(1) { // 任务调度 (每项任务按需执行) if(sys_tick % 10 == 0) scan_keys(); // 10ms扫描按键 if(sys_tick % 5 == 0) update_beep(); // 5ms更新蜂鸣器 process_time(); temp_handler(); refresh_display(); } }
最新发布
11-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tanglinux

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值