当用户进程open设备文件时, 内核会根据打开的设备文件的设备号找到对应的cdev对象,
检查cdev.ops->open, 如果不为空,则调用驱动里的open函数,如为空,内核直接返回fd.
//注意用户进程打开设备文件得到文件描述符不是由设备驱动里指定的,设备驱动里的open函数仅仅是告诉内核是否已正常打开.
用户进程:
app: open ---> kernel ---> cdev.ops->open(..)
read ---> kernel ---> cdev.ops->read(..)
ioctl ---> kernel ---> cdev.ops->unlocked_ioctl(..)
设备驱动实现好功能后,基本上由用户进程通过系统调用后进来调用的.
如需要循环操作硬件,则应是在用户进程里循环。
//
用户进程操作设备驱动,网络通信等与普通的文本文件操作的编程接口基本一样(open, read, write, ioctl…).
这套接口就是所谓的VFS(虚拟文件系统)。
//
//在linux内核,用一个inode节点对象描述一个要操作的文件/设备文件, 包括权限,设备号等信息. 就是描述一个要操作的文件的属性. 一个文件可以打开很多次, 但都是共用一个inode对象来描述属性的. 文件描述符属于一个进程的资源,不同进程里有可能相同的文件描述符.
struct inode {
...
dev_t i_rdev; //设备文件对应的设备号, 驱动里即可通过区分次设备号来区别不同的具体硬件
struct cdev *i_cdev; //指向对应的字符设备驱动cdev对象的地址.
...
};
//在用户进程里用一个int类型来表示文件描述符.但文件描述符里有还存有对文件位置的偏移,打开标志等信息, 用一个int数无法记录下来的,所在每个文件描述符的信息都是由内核里用file对象描述文件描述符, 在文件打开时创建, 关闭时销毁
struct file {
...
struct path f_path;
const struct file_operations *f_op; //对应的文件操作对象的地址
unsigned int f_flags; //文件打开的标志
fmode_t f_mode; //权限
loff_t f_pos; //文件描述符的偏移
struct fown_struct f_owner; //属于哪个进程
unsigned int f_uid, f_gid;
void *private_data; //给驱动程序员使用
...
};
如果打开设备文件,那么得到的file对象:
file对象里的成员f_path.dentry->d_inode->i_rdev可以获取到设备文件的设备号
file对象里的成员f_path.dentry->d_inode可以获取到设备文件的inode对象的地址
注意: 一个文件只有一个inode节点对象, 但是可以打开多次,得到不同的文件描述符对象(也就是多个struct file对象)
///
/struct file_operations 里函数参数
//inode表示应用程序打开的文件的节点对象, file表示打开文件获取到的文件描述符
int (*open) (struct inode *, struct file *);
//返回值0表示成功打开,负数表示打开失败。内核根据open函数的返回值来确定是否给调用的用户进程分配文件描述符。
//在驱动可以不实现此函数, 如不实现。则表示每次打开都是成功的.
//buf指向用户进程里的缓冲区, len表示buf的大小(由用户调用read时传进来的)
//off表示fl文件描述符的操作偏移, 返回值为实际给用户的数据字节数.
ssize_t (*read) (struct file *fl, char __user *buf, size_t len, loff_t *off);
//注意,必须通过off指针来改变文件描述符的偏移(*off += 操作字节数). 不可以直接通过"fl->f_pos"来设置
//用户进程把数据给驱动, 也就是让驱动存放用户进程传进来的数据.
// 参考read函数
ssize_t (*write) (struct file *, const char __user *buf, size_t len, loff_t *off);
// cmd表示用户进程调用ioctl时的第二个参数, arg表示第三个参数(可选)
long (*unlocked_ioctl) (struct file *fl, unsigned int cmd, unsigned long arg);
// 返回值为0表示ioctl成功, 返回负数表示失败.
// app: lseek(fd, 54, SEEK_SET)
loff_t (*llseek) (struct file *fl, loff_t offset, int whence);
//在驱动里操作用户数据缓冲区的函数:
#include <asm/uaccess.h>
//to指用户进程的缓冲区, from指驱动里装数据的缓冲区, n要复制多少字节。
extern inline long copy_to_user(void __user *to, const void *from, long n)
//返回值为还有多少字节没有复制成功. 正常情况下返回0.
//to指驱动的... from用户... n多少字节, ....
extern inline long copy_from_user(void *to, const void __user *from, long n)
//返回值为还有多少字节没有复制成功. 正常情况下返回0.
//如果与用户进程交互的数据是1,2,4,8字节的话, 可用
put_user(x,p) //x为值, p为地址
//如果从用户进程获取1,2,4字节的话, 可用
get_user(x,p)
/驱动里动态申请缓冲区的函数:
#include <linux/slab.h>
///动态申请内存, 并清零. size为申请多大(不要超过128K),
//flags为标志(常为GFP_KERNEL). 成功返回地址, 失败返回NULL
// GFP_ATOMIC, 使用系统的内存紧急池
void *kmalloc(size_t size, gfp_t flags);//申请后要内存要清零
void *kzalloc(size_t size, gfp_t flags); //申请出来的内存已清零
void kfree(const void *objp); //回收kmalloc/kzalloc的内存
void *vmalloc(unsigned long size); //申请大内存空间
void vfree(const void *addr); //回收vmalloc的内存
// kmalloc申请出来的内存是物理地址连续的, vmalloc不一定是连续的