一、复习
1.1、字符设备驱动编写
- alloc_chrdev_region/register_chrdev_region
- cdev_alloc
- cdev_init
- cdev_add
- class_create
- device_create
注意:错误处理,goto语句
卸载的时候:释放申请的资源,并删除注册的结构体
1.2、设备文件创建
- mknod
- 通过udev/mdev创建设备文件(根据uevent文件中的设备文件信息创建)
- 一个驱动对应驱动多个同类型设备:cdev_demo0、cdev_demo1、cdev_demo2,而应用层打开设备文件,在驱动中,怎么区别应用层打开的是哪一个?
- 通过次设备号进行区分—>>>怎么去读出次设备号?
- 应用层open(设备文件名)—系统调用—>>> fops -> open(struct inode *,struct file *)
- inode结构体中的设备号成员变量区分
二、open的系统调用过程
2.1、应用层
设备文件是创建的:根据设备文件名,设备号,系统在创建文件的时候会创建一个结构体,描述被创建的文件的所有信息:inode 结构体
在一个进程中调用open(),通过系统调用接口,进入内核层。task_struct
结构体,描述的是进程所有的信息。
同理,打开文件的时候,系统会创建一个file
结构体描述被打开的文件的所有信息,这个结构体指针被存在当前进程维护的一个fd_array[NR_OPEN_DEFAULT]数组中,存的位置就是打开文件的fd值。系统为当前进程task默认打开的标准输入、标准输出和标准错误输出文件占用了数组的前3项0、1、2:
2.2、open系统调用
- 打开/fs/open.c文件:
-
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, umode_t, mode)
-
do_sys_open(AT_FDCWD, filename, flags, mode);
-
struct file *f = do_filp_open(dfd, tmp, &op, lookup);
-
filp = path_openat(dfd, pathname, &nd, op, flags | LOOKUP_RCU);
-
do_last(nd, &path, op, pathname);
-
filp = nameidata_to_filp(nd);
-
filp = __dentry_open(nd->path.dentry, nd->path.mnt, filp, NULL, cred);
-
f->f_op = fops_get(inode->i_fop);
三、文件接口——操作方法集
3.1、read/write
- **读/写:**对用户层来讲,用户读(从内核空间拷贝数据到用户空间),用户写(从用户空间拷贝数据到内核层)。
- 使用的内核空间和用户空间数据拷贝函数:
3.2、读写例程
使用外部编译的方法进行编译,生成内核模块:make (KDIR路径:根据自己的路径修改)
加载执行(根据编译的架构,放在对应的平台上执行)
自己写应用层代码:调用read/wirte函数
3.2、ioctl接口
long (*unlocked_ioctl) (struct file *, unsigned cmd, unsigned args);
3.2.1、内核提供的封装命令宏函数
3.2.2、例程
头文件:
驱动层源文件:
应用层源文件:
编译内核模块,和应用层C文件,加载内核模块到操作系统中,执行应用层程序,使用dmesg打印内核信息,进行对比
四、访问硬件
操作系统运行起来后,访问的是虚拟地址,我们在数据手册中读到的是设备的物理地址,所以我们需要通过物理地址,获取到对应的虚拟地址。