当前位置:   article > 正文

Linux驱动入门[一]_linux驱动分类

linux驱动分类

linux驱动主要作用就是初始化硬件设备,并给硬件接口提供上层应用程序调用。

1. 驱动的分类

linux系统将驱动分为三类:字符设备驱动、块设备驱动、网络设备驱动

  1. 字符设备:是指只能一个字节一个字节进行读写的设备,读取数据需要按照前后顺序读取,不能随机读取内存中的某一数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台等。

  1. 块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备主要由硬盘、磁盘、U盘等。

  1. 网络设备:网络设备是指可以硬件设备,如网卡;也可以是软件设备,如回环接口(lo),通过软件实现虚拟网络接口。

2. 驱动的认知

linux系统结构如图所示:

用户空间

指用户在进行程序编写和运行的层面,用户在进行开发时需要C语言和C库来进行。C库其实是就是C libary,它提供程序调用内核的接口,如open、read、write、fork等命令是在C库中进行封装实现。

内核空间

用户程序在使用某个硬件设备时,需要调用内核空间的设备驱动程序,从而驱动硬件设备。由于linux中一切皆文件,各类设备都是文件,因此可以通过文件操作函数来操作这些设备。所以的linux设备都是存放在/dev目录下,为了管理这些设备,系统为设备进行编号,每个设备分为主设备和次设备。主设备是用来区分设备的类型,次设备号是用来区分同一个类型的多种设备。并且每一种硬件设备都对应着不同的驱动。对于一些常用的设备,linux会约定俗成编号,如硬盘设备的编号为3。

在文件权限前面的字母表示着不同含义:

(1)(‘-’,regular file)表示普通文件,分为二进制文件和文本文件。

(2) ('d',directory file)表示文件夹文件,一般需要库函数将其打开。

(3) ('l',link file)表示链接文件。

(4) ('p',piple file)管道文件,用于进程通信

(5) ('c',character file)字符设备文件,为虚拟文件,本身不存在与硬盘中,由fs创建,不能直接读写,需要使用API调用

(6) ('b',block file)块设备文件,也是虚拟文件,是要API调用。

(7) ('s',socket file)套接字文件,用于网络中。

驱动链表:用于管理所有设备的驱动,添加或查找。添加驱动程序是在写完驱动程序后,加载到内核。驱动插入到链表的顺序是由设备号检索,通过设备号将驱动程序加载到链表的某个位置。

3. 字符设备驱动原理

  1. 在linux文件系统中,每个文件都有一个struct inode结构体来描述,这个结构体存放着这个文件的所有信息,如文件类型、访问权限等。当使用open函数打开设备文件时,可根据设备文件对应的struct inode结构体里描述的信息,可知道操作的设备类型(字符设备还是块设备),并且会分配一个struct file结构体。

  1. 根据struct inode结构体中记录的设备号,可以找到对应的驱动程序。每个字符设备都有一个struct cdev结构体。该结构体中记录了字符设备的操作函数接口等关键信息。

  1. 在找到struct cdev结构体后,linux内核会将struct cdev结构体所在的空间首地址记录在struct inode结构体i_cdev成员中,将struct cdev结构体中记录的函数操作接口地址记录在struct file结构体的f_opts成员中。

  1. 任务完成后,VFS层会给应用返回一个文件描述符(fd)。fd与struct file结构体相对应,上层应用程序可以通过fd找到struct file,然后在struct file找到操作字符设备的函数接口file_operation。

4.字符设备驱动结构

字符设备驱动结构如图:

在linux内核中,使用cdev结构体来描述字符设备,使用dev_t定义设备号(分主、次设备号)来确定字符设备的唯一性。通过成员file_operation定义字符设备驱动提供给VFS的接口函数,例如open()、read()、write()等。

在linux字符设备驱动中,模块加载函数通过register_chrdev_region()或alloc_chrdev_region来静态或动态获取设备号,使用cdev_init()建cdev与file_operations之间的连接,通过cdev_add()向系统添加一个cdev完成注册。模块卸载函数通过cdev_del()来注销cdev,通过unregister_chrdev_region()来释放设备号。

5.字符驱动程序开发流程

5.1 字符设备的注册和注销

对于字符设备驱动,需要在驱动模块加载成功后注册字符设备,同样在卸载驱动模块时注销字符设备。字符设备的注册和注销函数如下:

  1. static int __init hello_driver_init(void)
  2. {
  3. int ret;
  4. printk("hello_driver_init\r\n");
  5. ret = register_chrdev(CHRDEVBASE_MAJOR,"hello_driver",&hello_world_fops);
  6. return 0;
  7. }
  8. static void __exit hello_driver_cleanup(void)
  9. {
  10. unregister_chrdev(CHRDEVBASE_MAJOR,"hello_driver");
  11. printk("hello_driver_cleanup\r\n");
  12. }

register_chrdev函数用于注册字符设备,其中CHRDEVBASE_MAJOR表示主设备号;"hello_driver"表示设备名称;hello_world_fops指向结构体file_operations类型指针。

unregister_chrdev函数用于注销字符设备,函数只有有两个参数。

5.2 字符设备方法构成

  1. static int hello_world_open(struct inode * inode, struct file * file)
  2. {
  3. printk("hello_world_open\r\n");
  4. return 0;
  5. }
  6. static int hello_world_release (struct inode * inode, struct file * file)
  7. {
  8. printk("hello_world_release\r\n");
  9. return 0;
  10. }
  11. static ssize_t hello_world_read (struct file * file, char __user * buffer, size_t size, loff_t * ppos)
  12. {
  13. printk("hello_world_read size:%ld\r\n",size);
  14. copy_to_user(buffer,kernel_buffer,size);
  15. return size;
  16. }
  17. static ssize_t hello_world_write(struct file * file, const char __user * buffer, size_t size, loff_t *ppos)
  18. {
  19. printk("hello_world_write size:%ld\r\n",size);
  20. copy_from_user(kernel_buffer,buffer,size);
  21. return size;
  22. }
  23. static const struct file_operations hello_world_fops = {
  24. .owner = THIS_MODULE,
  25. .open = hello_world_open,
  26. .release = hello_world_release,
  27. .read = hello_world_read,
  28. .write = hello_world_write,
  29. };

用户空间的函数需要在file_operations结构体中对应,才能使用户空间实现对内核的操作。其中两个函数是用于内核与用户空间交换数据的:

copy_to_user(buffer,kernel_buffer,size);将内核空间的数据到用户空间复制。buffer是目标地址,kernel_buffer表示源地址,size表示复制的数据长度。

copy_from_user(kernel_buffer,buffer,size);将用户空间的数据复制到内核空间。

5.3 驱动的加载和卸载

linux驱动有两种运行方式,一种是将驱动编译到linux内核中,当linux内核启动的时候会自动运行驱动程序。第二种是将驱动编译成模块(扩展名为.ko),在linux内核中使用“insmod”命令加载模块。

模块有加载和卸载两种操作,在编写驱动时需要注册这两种操作函数,模块的加载和卸载注册函数为:

  1. module_init(hello_driver_init);
  2. module_exit(hello_driver_cleanup);

5.4 编写测试程序

测试程序如下:

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <sys/types.h>
  4. #include <sys/stat.h>
  5. #include <fcntl.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. uint8_t buffer[512] = {0};
  9. int main(int argc, char *argv[])
  10. {
  11. int fd;
  12. int ret;
  13. fd = open(argv[1], O_RDWR);
  14. if(!strcmp("read",argv[2]))
  15. {
  16. printf("read data from kernel\r\n");
  17. ret = read(fd,buffer,sizeof(buffer));
  18. printf("ret len:%d data:%s\r\n",ret,buffer);
  19. }
  20. if(!strcmp("write",argv[2]))
  21. {
  22. printf("write data to kernel %s len:%d\r\n",argv[3],strlen(argv[3]));
  23. ret = write(fd,argv[3],strlen(argv[3]));
  24. printf("ret len:%d\r\n",ret);
  25. }
  26. close(fd);
  27. }

5.5 Makefile文件

  1. kERNELDIR := /lib/modules/$(shell uname -r)/build
  2. PWD := $(shell pwd)
  3. obj-m := hello_drive.o
  4. all:
  5. make -C /lib/modules/$$(uname -r)/build/ M=$(PWD) modules
  6. clean:
  7. make -C /lib/modules/$$(uname -r)/build/ M=$(PWD) clean

6 代码编译

6.1 make编译

makefile文件编写好后直接使用make命令进行编译

6.2 insmod加载模块

make编译完成后发现文件中存在.ko文件,使用“insmod”命令记载模块,并使用“lsmod”查看

6.3 程序验证

使用cat /proc/devices可以查看设备号

使用mknod创建节点

编译测试文件gcc -o test_app test_app.c

应用程序读取数据:sudo .test/test_app /dev/chardev read ,并使用dmesg查看

应用程序写数据:sudo .test/test_app /dev/chardev write 1234567890,并使用dmesg查看

6.4 卸载模块

使用sudo rmmod hello_drive.ko,lsmod查看模型信息,模块已删除。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/盐析白兔/article/detail/384734?site
推荐阅读
相关标签
  

闽ICP备14008679号