Python read/write/seek 幕后操作
Python read/write/seek operations under the hood
在 Linux 系统上创建字符设备时,我使用 Python 及其基本文件操作与它交互。
在经历了几次崩溃后,我开始打印调试消息并注意到一个奇怪的行为:似乎 Python "optimizes" 文件操作在一种奇怪的意义上。
让我们看一个例子;这是交互的基本代码和输出:
内核模块
// Several includes and kernel module initialization
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, long long *offset){
printk(KERN_INFO "[DEBUGGER] - dev_read with len: %d, offset: 0x%llx.\n", len, offset[0]);
return len;
}
static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, long long *offset){
printk(KERN_INFO "[DEBUGGER] - dev_write with len: %d, offset: 0x%llx.\n", len, offset[0]);
return len;
}
static long long dev_llseek(struct file *filep, long long offset, int orig){
printk(KERN_INFO "[DEBUGGER] - dev_llseek with offset: 0x%llx, orig: %d\n", offset, orig);
return offset;
}
static int dev_release(struct inode *inodep, struct file *filep){
return 0; // Success
}
static int dev_open(struct inode *inodep, struct file *filep){
return 0; // Success
}
static struct file_operations fops =
{
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
.llseek = dev_llseek,
};
int init_module(void){
// Code to create character device
return 0;
}
void cleanup_module(void){
// Code to delete character device
}
Python
with open("/dev/chardevice", "r+b") as character:
character.seek(1)
character.read(4)
character.seek(0x7f123456)
character.read(20)
character.write("\xff" * 4)
输出
# seek(1)
[DEBUGGER] - dev_llseek with offset: 0x0, orig: 0
[DEBUGGER] - dev_read with len: 1, offset: 0x0.
[DEBUGGER] - dev_llseek with offset: 0x1, orig: 0
# read(4)
[DEBUGGER] - dev_read with len: 4, offset: 0x0.
# seek(0x7f123456)
[DEBUGGER] - dev_llseek with offset: 0x7f123000, orig: 0
[DEBUGGER] - dev_read with len: 1110, offset: 0x0.
# read(20)
[DEBUGGER] - dev_read with len: 4096, offset: 0x0.
# write("\xff" * 4)
[DEBUGGER] - dev_write with len: 4, offset: 0x0.
很明显,基本的文件操作不会直接转化为对文件的相同操作,最明显的例子是寻找 0x7f123000 而不是 0x7f123456 并且读取 4096 字节,而只读取 20 字节请求。
这引发了以下问题:
- 为什么这是一个功能?
- 它实现了什么优化,因为它的大部分看起来都不像一个好的 "next-operation" 预测?
- 它是否在任何地方都有记录,以了解预先编写 read/write 功能时会发生什么?
- 除了对这个领域的纯粹兴趣之外,我仍然希望使用 Python 以便于访问 - 那么有没有什么方法可以禁用此优化,并强制 Python 像执行 C 代码一样运行这些操作?
谢谢!
我不确定这里的情况,但我认为这与读取一个字节与读取整个扇区相同的时间损失有关,所以为什么不总是从磁盘读取整个扇区(或者你可能甚至不能要求读取少于底层系统扇区大小的字节)
Python的文件对象实际上是FILE*
对象的包装器(在C语言中),所以它们是缓冲的流。由于缓冲,Python 对文件的操作不会将它们转换为具有相同参数的系统调用,而是尝试优化请求时间(针对当前和未来的操作)。
方法open()
accepts缓冲参数作为3d参数。传递 0 应该禁用缓冲,因此 python 会将所有文件的请求 直接 转换为底层系统:
open("/dev/chardevice", "r+b", 0)
在 Linux 系统上创建字符设备时,我使用 Python 及其基本文件操作与它交互。
在经历了几次崩溃后,我开始打印调试消息并注意到一个奇怪的行为:似乎 Python "optimizes" 文件操作在一种奇怪的意义上。
让我们看一个例子;这是交互的基本代码和输出:
内核模块
// Several includes and kernel module initialization
static ssize_t dev_read(struct file *filep, char *buffer, size_t len, long long *offset){
printk(KERN_INFO "[DEBUGGER] - dev_read with len: %d, offset: 0x%llx.\n", len, offset[0]);
return len;
}
static ssize_t dev_write(struct file *filep, const char *buffer, size_t len, long long *offset){
printk(KERN_INFO "[DEBUGGER] - dev_write with len: %d, offset: 0x%llx.\n", len, offset[0]);
return len;
}
static long long dev_llseek(struct file *filep, long long offset, int orig){
printk(KERN_INFO "[DEBUGGER] - dev_llseek with offset: 0x%llx, orig: %d\n", offset, orig);
return offset;
}
static int dev_release(struct inode *inodep, struct file *filep){
return 0; // Success
}
static int dev_open(struct inode *inodep, struct file *filep){
return 0; // Success
}
static struct file_operations fops =
{
.open = dev_open,
.read = dev_read,
.write = dev_write,
.release = dev_release,
.llseek = dev_llseek,
};
int init_module(void){
// Code to create character device
return 0;
}
void cleanup_module(void){
// Code to delete character device
}
Python
with open("/dev/chardevice", "r+b") as character:
character.seek(1)
character.read(4)
character.seek(0x7f123456)
character.read(20)
character.write("\xff" * 4)
输出
# seek(1)
[DEBUGGER] - dev_llseek with offset: 0x0, orig: 0
[DEBUGGER] - dev_read with len: 1, offset: 0x0.
[DEBUGGER] - dev_llseek with offset: 0x1, orig: 0
# read(4)
[DEBUGGER] - dev_read with len: 4, offset: 0x0.
# seek(0x7f123456)
[DEBUGGER] - dev_llseek with offset: 0x7f123000, orig: 0
[DEBUGGER] - dev_read with len: 1110, offset: 0x0.
# read(20)
[DEBUGGER] - dev_read with len: 4096, offset: 0x0.
# write("\xff" * 4)
[DEBUGGER] - dev_write with len: 4, offset: 0x0.
很明显,基本的文件操作不会直接转化为对文件的相同操作,最明显的例子是寻找 0x7f123000 而不是 0x7f123456 并且读取 4096 字节,而只读取 20 字节请求。
这引发了以下问题:
- 为什么这是一个功能?
- 它实现了什么优化,因为它的大部分看起来都不像一个好的 "next-operation" 预测?
- 它是否在任何地方都有记录,以了解预先编写 read/write 功能时会发生什么?
- 除了对这个领域的纯粹兴趣之外,我仍然希望使用 Python 以便于访问 - 那么有没有什么方法可以禁用此优化,并强制 Python 像执行 C 代码一样运行这些操作?
谢谢!
我不确定这里的情况,但我认为这与读取一个字节与读取整个扇区相同的时间损失有关,所以为什么不总是从磁盘读取整个扇区(或者你可能甚至不能要求读取少于底层系统扇区大小的字节)
Python的文件对象实际上是FILE*
对象的包装器(在C语言中),所以它们是缓冲的流。由于缓冲,Python 对文件的操作不会将它们转换为具有相同参数的系统调用,而是尝试优化请求时间(针对当前和未来的操作)。
方法open()
accepts缓冲参数作为3d参数。传递 0 应该禁用缓冲,因此 python 会将所有文件的请求 直接 转换为底层系统:
open("/dev/chardevice", "r+b", 0)