(二)字符设备驱动-驱动原理,创建设备节点,字符设备驱动相关函数


驱动提供的是操作硬件的方法,但是不提供操作硬件的逻辑(应用层)。
/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的返回值)

返回值:无

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值