字符设备驱动
驱动提供的是操作硬件的方法,但是不提供操作硬件的逻辑(应用层)。
/dev:存放设备驱动的文件
应用层实现逻辑操作,只需操作对应的 /dev 驱动文件进行系统调用open、read、write、close。
通过系统调用进入内核层,内核驱动中有相对应的函数。
驱动的原理
以led驱动为例:
user:
控制灯的亮灭逻辑
open("/dev/led",) read write close
流程:通过led驱动文件能找到与之相对应的唯一的设备号,再通过设备号就能找到唯一的设备驱动;
再通过唯一的设备驱动就能找到唯一的驱动操作函数指针。
通过函数指针就能找到和应用层相对应的内核中每个函数的功能。
-----------------------系统调用-------
kernel: 每一个驱动都有自己唯一的设备号
设备号(32位):高12位(主设备号) + 低20位(次设备号)
2^12 = 4096 2^20 = 1M
主设备号:识别是哪一类的设备
次设备号:代表是同一类设备的第几个设备
设备号:LED字符设备驱动设备号
应用层调用时,对应结构体内的函数指针:
fops{
int (*open)();
int (*read)();
int (*write)();
int (*close)();
}
led_open:灯的初始化
led_read:获取灯的状态
led_write:控制灯的亮灭
led_close:释放灯的操作
uart字符设备驱动(设备号)
lcd字符设备驱动(设备号)
...
------------------------------------------------
hardware: LED
原理:
- open打开/dev/led文件时,open在应用层通过inode号(索引节点)来唯一识别、找到/dev/led文件。( inode号是文件系统唯一识别文件的索引号)
- /dev/led文件又可以通过设备号在内核中唯一识别、找到驱动。
- 再通过fops指针找到结构体对应的函数。
字符设备驱动-函数
(一)注册字符设备驱动:register_chrdev
static inline int register_chrdev(unsigned int major, const char *name, \
const struct file_operations *fops);
inline:内联;函数的代码被放入符号表中,在编译时直接进行函数替换(像宏一样展开),没有了调用时在跳转到函数定义的地方的开销,效率也很高。
1)功能:注册字符设备驱动
2)参数:
-
major :主设备号
major > 0:手动指定设备号(有可能重复)
major = 0:系统会自动分配主设备号 -
name :设备驱动的名字
可通过命令:cat /proc/devices,查看系统的设备使用情况。
linux@ubuntu:~/kernel/kernel-3.4.39$ cat /proc/devices
Character devices:
主设备号 设备名字
1 mem
4 /dev/vc/0
4 tty
4 ttyS
5 /dev/tty
5 /dev/console
5 /dev/ptmx
5 ttyprintk
6 lp
7 vcs
10 misc
13 input
- fops:操作方法结构体
fops指向的函数不会主动执行,只有当应用层的应用程序调用时,对应的fops指向的函数才会执行。
struct file_operations
{
int (*open) (struct inode *, struct file *);//open
ssize_t (*read) (
struct file * file,
char __user ubuf*, //用户的缓冲区ubuf大小
size_t size, //用户想读的字节大小 size要大于内核的缓冲区:ubuf > kbuf, size = sizeof(kbuf)
loff_t *offs);//偏移量
ssize_t (*write) (struct file *,
const char __user *,
size_t, //用户想写的字节大小
loff_t *);
int (*release) (struct inode * inode, struct file *);//close
};
3)返回值:
- major > 0 :成功返回0;失败返回错误码
- major = 0 :成功返回主设备号major;失败返回错误码
4)内核中的错误码:内核返回的错误码给应用程序,return 返回错误码要加 - 负号。
vi -t EIO:
4 #define EPERM 1 /* Operation not permitted */
5 #define ENOENT 2 /* No such file or directory */
6 #define ESRCH 3 /* No such process */
7 #define EINTR 4 /* Interrupted system call */
8 #define EIO 5 /* I/O error */
9 #define ENXIO 6 /* No such device or address */
10 #define E2BIG 7 /* Argument list too long */
11 #define ENOEXEC 8 /* Exec format error */
12 #define EBADF 9 /* Bad file number */
13 #define ECHILD 10 /* No child processes */
14 #define EAGAIN 11 /* Try again */
15 #define ENOMEM 12 /* Out of memory */
16 #define EACCES 13 /* Permission denied */
17 #define EFAULT 14 /* Bad address */
...
(二)注销字符设备驱动:unregister_chrdev
static inline void unregister_chrdev(unsigned int major, const char *name)
{
__unregister_chrdev(major, 0, 256, name);
}
1)功能:注销字符设备驱动
2)参数:
@major:主设备号
@name :驱动设备名字
3)返回值:无
字符设备驱动-程序
一、驱动程序
-
:set tags=/home/linux/kernel/kernel-3.4.39/tags
ctrl + ] 追踪 ,ctrl + t返回 -
添加打印调试信息:
printk("%s:%s:%d\n", FILE, func, LINE);
二、应用程序
fd = open("/dev/led", O_RDWR);
read、write
close
(一)创建设备节点
- cat /proc/devices:查看系统中驱动的主设备号,获得major。
- 再创建设备节点:sudo mknod /dev/led c 250 0
mknod:创建设备节点的命令
/dev/led:创建设备节点所在的路径,创建设备的节点可以在任意路径下,一般放在/dev/下。
c :字符设备驱动,b:块设备驱动。mknod无法创建网卡设备节点。
250:主设备号major
0:次设备号,取值范围(0-255)
(二)用户和内核之间的数据传递
两个函数都是在驱动程序中写的,但是站在用户的角度读、写。
头文件:#include <asm/uaccess.h>
(1)copy_to_user(user_read)
static inline unsigned long __must_check copy_to_user(void __user *to,
const void *from, unsigned long n) ;
inline:内联
__must_check:编译器对内存拷贝的位置进行检查
__user:告诉编译器这个地址是用户空间的地址
unsigned long copy_to_user(void __user * to, const void * from, unsigned long n);
1)功能:从内核向用户空间拷贝数据,用于驱动的用户读数据
- copy_to_user:在memcpy()函数基础上进行封装成一个具有地址标记的函数;
目的是告诉编译器不要对地址进行检查,如果要检查,则地址必须是用户空间的地址。
2)参数
- to :用户空间的首地址
- from:内核空间的首地址
- n :大小(字节)
3)返回值
成功返回0,
失败返回:拷贝的字节的个数
(2)copy_from_user(user_write)
static inline unsigned long __must_check copy_from_user(void *to, \
const void __user *from, unsigned long n)
(三)物理地址和虚拟地址间的转化
物理地址通过MMU映射虚拟地址
(一)ioremap
void * ioremap(phys_addr_t offset, unsigned long size);
1)功能:将物理地址映射成虚拟地址
2)参数:
- offset :物理地址的首地址
- size :想要映射的虚拟内存大小(字节)
3)返回值
成功:虚拟地址
失败:NULL
(二)iounmap
void iounmap(void *addr)
功能:取消映射
参数:
- addr :虚拟地址(ioremap的返回值)
返回值:无