read和write原型
read和write方法完成的任务是相似的,亦即,拷贝数据到应用程序空间,或者反过来从应用程序空间拷贝数据;因此,它们的原型很相似,如下:
1 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 2 3 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
其中参数filp是文件指针,参数count是请求传输的数据长度,参数buff指向用户空间的缓冲区,这个缓冲区或者保存将要写入的数据,或者是一个存放新读入数据的空缓冲区;offp指明了用户在文件中进行存放操作的位置;
一般而言,调用read和write都应该更新*offp所指向的文件位置,以便反应新系统调用成功完成之后当前文件的位置;
返回值:
read-
n==count,说明所请求的字节数成功完成;
n>0 && n<count,说明只有部分数据传输成功,大部分情况下,程序会重新读取数据;如果用fread函数读取数据,则库函数会不断的调用系统调用,直至所有的数据传输完成;
n==0,到达了文件尾;
n<0,意味着发生了错误,该值指明了发生了什么错误;
write-
n==count,说明所请求数目的字节数成功完成;
n>0 && n<count,说明只有部分数据传输成功,程序很可能再次试图写入余下的数据;
n==0,意味着什么也没写入,这个结果不是错误,而且也没理由返回一个错误码,标准库会重复调用write;
n<0,意味着发生了错误,该值指明了发生了什么错误;
内核不能直接引用用户空间指针
因为read和write方法的buff参数是用户空间指针,所以,内核代码不能直接引用其中的内容,这种限制的原因如下:
1. 随着驱动程序所运行的架构的不同或者内核配置的不同个,在内核模式中运行时,用户空间的指针可能是无效的,该地址可能根本无法映射到内核空间,或者可能指向某些随机数据;
2. 即使该指针在内核空间代表相同的东西,但是用户空间的内存是分页的,而在系统调被调用时,涉及到的内存可能根本就不在RAM中,对用户空间内存的直接应用将导致页错误,而这对内核嗲吗来说是不允许发生的事情,其结果可能是oops,导致调用该系统调用的进程死亡;
3. 我们讨论的指针可能由用户程序提供,而改程序可能存在缺陷或者是个恶意程序,如果我们的驱动程序盲目的引用用户提供的指针,则将导致系统出现打开的后门,从而允许用户空间程序随意的访问或者覆盖系统中的内存;为了驱动程序的安全性,永远会不要直接引用用户空间指针;
大多数read和write代码要做的工作就是将用户地址空间和内核地址空间进行整段的数据拷贝,这种能力是由下面的内核函数提供的,它们用于拷贝任意一段字节序列;
1 static __always_inline unsigned long __must_check 2 copy_from_user(void *to, const void __user *from, unsigned long n) 3 4 static __always_inline unsigned long __must_check 5 copy_to_user(void __user *to, const void *from, unsigned long n)
虽然这些函数的行为很想通常的memcpy,但是当内核空间运行的代码访问用户空间时要多加小心,被寻址的用户空间页面可能当前并且不再内存中,于是虚拟内存子系统将该进程转入睡眠状态,知道该页面呗传送至期望的位置;例如,当页面必须从交换空间取回时,这样的情况就会发生,对于驱动程序编写人员来说,者带来的结果是访问用户空间的任何函数都必须是可重入的,并且必须能和其他驱动程序函数并发执行,更特别的是,必须处于能够合法休眠的状态;
这两个函数的作用并不限于在内核空间和用户空间之间拷贝数据,它们还检查用户空间的指针是佛有效,如果无效,则不会进行拷贝;另一方面,如果在拷贝过程中遇到无效地址,则仅仅会赋值部分数据;在这两种情况下,返回值是还需要拷贝的内存数量值;
如果并不需要检查用户空间指针,则建议调用__copy_to_user和__copy_from_user;在预先知道参数已经检查过时,这两个函数很有用,但是,如果并没有真正的检查传递给这些函数的用户空间指针,则可能导致内核崩溃或者建立安全漏洞;