当前位置:首页 > 嵌入式培训 > 嵌入式学习 > 讲师博文 > Linux底层驱动开发需要学习哪些内容

Linux底层驱动开发需要学习哪些内容 时间:2018-01-09      来源:未知

Linux底层驱动开发需要学习哪些内容想必这是很多学习Linux的朋友十分头疼的问题,今天就让我来告诉大家我们到底该学习哪些内容呢?

1. 要会一些硬件知识,比如Arm接口编程

2. 学会写简单的makefile

3. 编一应用程序,可以用makefile跑起来

4. 学会写驱动的makefile

5. 写一简单char驱动,makefile编译通过,可以insmod, lsmod, rmmod. 在驱动的init函数里打印hello world, insmod后应该能够通过dmesg看到输出。

6. 写一完整驱动, 加上read, write, ioctl, polling等各种函数的驱动实现。 在ioctl里完成从用户空间向内核空间传递结构体的实现。

7. 写一block驱动, 加上read,write,ioctl,poll等各种函数实现。

8. 简单学习下内存管理, 这个是难的,明白各种memory alloc的函数实现细节。这是Linux开发的基本功。

9. 学习锁机制的应用,这个不是难的但是容易犯错的,涉及到很多同步和并发的问题。

10. 看内核中实际应用的驱动代码。 你会发现基本的你已经知道了, 大的框架都是一样的, 无非是read, write, ioctl等函数的实现, 但里面包含了很多很多细小的实现细节是之前不知道的。 这时候就要考虑到很多别的问题而不仅仅是基本功能的实现。 推荐您看2.6.20中integrated的一个驱动 kvm, 记得是在driver/lguest下,很好玩的, 就是Linux下的虚拟机驱动, 代码不长,但功能强大。有能力的可以自己写一操作系统按照要求做成磁盘镜像加载到虚拟机中, 然后客户机可以有自己的4G虚拟地址空间。

11. 看完驱动欢迎您进入Linux kernel学习中来。 简单的方法,跟着ldd(Linux devive driver)做一遍。

1、 Makefile 是如何编写

eg:

# 这是上面那个程序的 Makefile 文件

main:main.o mytool1.o mytool2.o

gcc -o main main.o mytool1.o mytool2.o

main.o:main.c mytool1.h mytool2.h

gcc -c main.c

mytool1.o:mytool1.c mytool1.h

gcc -c mytool1.c

mytool2.o:mytool2.c mytool2.h

gcc -c mytool2.c

分析:

在 Makefile 中也#开始的行都是注释行.Makefile 中重要的是描述文件的依赖关系的说

明.一般的格式是: Linux 操作系统 C 语言编程入门

target: components //表示的是依赖关系

TAB rule //规则

main:main.o mytool1.o mytool2.o 表示我们的目标(target)main 的依赖对象(components)是 main.o mytool1.o mytool2.o 当倚赖的对象在目标修改后修改的话,就要去执行规则一行所指定的命令.就象我们的上

面那个 Makefile 第3行所说的一样要执行 gcc -o main main.o mytool1.o mytool2.o

(注意规则一行中的 TAB表示那里是一个 TAB 键)

Makefile 有三个非常有用的变量.分别是$@,$^,$<代表的意义分别是:

$@--目标文件; $^--所有的依赖文件; $<--第一个依赖文件。

1、 字符设备驱动

Linux字符设备驱动的关键数据结构cdev及file_operations结构体的操作方法,并分析了Linux字符设备的整体结构,给出了简单的设计模板.

2.1、驱动结构

1) cdev结构体(cdev结构体描述字符设备)

定义:

1 struct cdev {

3 struct kobject kobj; /* 内嵌的kobject对象 */

4 struct module *owner; /*所属模块*/

5 struct file_operations *ops; /*文件操作结构体*/

6 struct list_head list;

7 dev_t dev; /*设备号*/ 定义了设备号

8 unsigned int count;

9 };

dev_t 成员定义了设备号,为 32 位,其中高 12 位为主设备号,低20位为次设备号。使用下列宏可以从dev_t获得主设备号和次设备号:

MAJOR(dev_t dev) //主设备号

MINOR(dev_t dev) //次设备号

而使用下列宏则可以通过主设备号和设备号生成 dev_t:

MKDEV(int major, int minor)

file_operations 定义了字符设备驱动提供给虚拟文件系统的接口函数

Linux 2.6内核提供了一组函数用于操作 cdev结构体

Void cdev_init(struct cdev *, struct file_operations *);

struct cdev *cdev_alloc(void);

void cdev_put(struct cdev *p);

int cdev_add(struct cdev *, dev_t, unsigned);

void cdev_del(struct cdev *);

cdev_init()函数用于初始化 cdev 的成员,并建立 cdev 和 file_operations 之间的连接。
1            void cdev_init(struct cdev *cdev, struct file_operations *fops)
2            {
3                   memset(cdev, 0, sizeof *cdev);
4                   INIT_LIST_HEAD(&cdev->list);
5                   cdev->kobj.ktype = &ktype_cdev_default;
6                   kobject_init(&cdev->kobj);
7                   cdev->ops = fops;                     /*将传入的文件操作结构体指针赋值给cdev的ops*/
8            }
       cdev_alloc()函数用于动态申请一个cdev内存
1            struct cdev *cdev_alloc(void)
2            {
3                   struct  cdev  *p=kmalloc(sizeof(struct  cdev),GFP_KERNEL);  /*分配cdev的内存*/
4                   if (p) {
5                         memset(p, 0, sizeof(struct cdev));
6                         p->kobj.ktype = &ktype_cdev_dynamic;
7                         INIT_LIST_HEAD(&p->list);
8                         kobject_init(&p->kobj);
9                   }
10         return p;
11          }

cdev_add()函数和 cdev_del()函数分别向系统添加和删除一个cdev,完成字符设备的注册和注销。对 cdev_add()的调用通常发生在字符设备驱动模块加载函数中,而对cdev_del()函数的调用则通常发生在字符设备驱动模块卸载函数中。

2) 分配和释放设备号

在 调用 cdev_add() 函 数 向系统注册 字符 设备 之前 , 应首先调用register_chrdev_region()或 alloc_chrdev_region()函数向系统申请设备号。register_chrdev_region() 函 数 用 于 已 知 起 始 设 备的 设备 号 的 情 况; 而alloc_chrdev_region()用于设备号未知,向系统动态申请未被占用的设备号的情况,相反地 ,在 调用 cdev_del() 函 数 从系 统 注销 字符设备 之 后,unregister_chrdev_region()应该被调用以释放原先申请的设备号。

3)file_operations结构体
1            struct file_operations
2            {
3                   struct module *owner; // 拥有该结构的模块的指针,一般为THIS_MODULES
5                  loff_t(*llseek)(struct file *, loff_t, int); // 用来修改文件当前的读写位置 
7                  ssize_t(*read)(struct file *, char _  _user *, size_t, loff_t*); // 从设备中同步读取数据
9                  ssize_t(*aio_read)(struct  kiocb  *,  char  _  _user  *,  size_t,  loff_t); // 初始化一个异步的读取操作
11                ssize_t(*write)(struct  file  *,  const  char  _  _user  *,  size_t, loff_t*); // 向设备发送数据
13                 ssize_t(*aio_write)(struct kiocb *, const char _  _user *, size_t, loff_t); // 初始化一个异步的写入操作
15                int(*readdir)(struct file *, void *, filldir_t); // 仅用于读取目录,对于设备文件,该字段为 NULL
17                 unsigned int(*poll)(struct file *, struct poll_table_struct*);  // 轮询函数,判断目前是否可以进行非阻塞的读取或写入
19                 int(*ioctl)(struct  inode  *, struct  file *, unsigned  int, unsigned long); // 执行设备I/O控制命令
21                 long(*unlocked_ioctl)(struct  file  *,  unsigned  int,  unsigned  long); // 不使用BLK文件系统,将使用此种函数指针代替ioctl
23                 long(*compat_ioctl)(struct file *, unsigned int, unsigned long); // 在64位系统上,32位的ioctl调用将使用此函数指针代替
25                 int(*mmap)(struct file *, struct vm_area_struct*); // 用于请求将设备内存映射到进程地址空间
27                 int(*open)(struct inode *, struct file*); // 打开
29                 int(*flush)(struct file*);
30                 int(*release)(struct inode *, struct file*); / 关闭
32                 int(*synch)(struct file *, struct dentry *, int datasync); // 刷新待处理的数据
34                 int(*aio_fsync)(struct kiocb *, int datasync); // 异步fsync
36                 int(*fasync)(int, struct file *, int); // 通知设备FASYNC标志发生变化
38                 int(*lock)(struct file *, int, struct file_lock*);
39                 ssize_t(*readv)(struct  file  *,  const  struct  iovec  *,  unsigned  long, loff_t*);
40                 ssize_t(*writev)(struct  file  *,  const  struct  iovec  *,  unsigned  long, loff_t*);  // readv和writev:分散/聚集型的读写操作
42                 ssize_t(*sendfile)(struct  file  *,  loff_t  *,  size_t,  read_actor_t, void*); // 通常为NULL
44                 ssize_t(*sendpage)(struct file *, struct page *, int, size_t, loff_t *, int); // 通常为NULL
46                 unsigned long(*get_unmapped_area)(struct file *,unsigned long, unsigned long, unsigned long, unsigned long); // 在进程地址空间找到一个将底层设备中的内存段映射的位置
49                 int(*check_flags)(int); // 允许模块检查传递给fcntl(F_SETEL...)调用的标志
51                 int(*dir_notify)(struct file *filp, unsigned long arg); // 仅对文件系统有效,驱动程序不必实现
53                 int(*flock)(struct file *, int, struct file_lock*); 
54          };

llseek()函数用来修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值

read()函数用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。

write()函数向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。

readdir()函数仅用于目录,设备节点不需要实现它。

ioctl()提供设备相关控制命令的实现 (既不是读操作也不是写操作) , 当调用成功时,返回给调用程序一个非负值。内核本身识别部分控制命令,而不必调用设备驱动中的

ioctl()。如果设备不提供ioctl()函数,对于内核不能识别的命令,用户进行ioctl()系统调用时将获得-EINVAL返回值。

mmap()函数将设备内存映射到进程内存中,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV返回值。 这个函数对于帧缓冲等设备特别有意义。

3)字符设备驱动的组成

A、字符设备驱动模块加载与卸载函数

字符设备驱动模块加载函数中应该实现设备号的申请和cdev的注册, 而在卸载函数中应实现设备号的释放和 cdev的注销常见的设备结构体、模块加载和卸载函数形式如代码清单:
1     //设备结构体
2            struct xxx_dev_t
3            {
4                  struct cdev cdev;
5                  ...
6            } xxx_dev;
7     //设备驱动模块加载函数
8            static int _  _init xxx_init(void)
9            {
10                 ...
11                 cdev_init(&xxx_dev.cdev, &xxx_fops); //初始化cdev
12                 xxx_dev.cdev.owner = THIS_MODULE; //获取字符设备号
14                 if (xxx_major)
15                 {
16                       register_chrdev_region(xxx_dev_no, 1, DEV_NAME);
17                 }
18                 else
19                 {
20                       alloc_chrdev_region(&xxx_dev_no, 0, 1, DEV_NAME);
21                 }
22   
23                 ret = cdev_add(&xxx_dev.cdev, xxx_dev_no, 1); //注册设备
24                 ...
25          }
26 //设备驱动模块卸载函数
27          static void _  _exit xxx_exit(void)
28          {
29                 unregister_chrdev_region(xxx_dev_no, 1); //释放占用的设备号
30                 cdev_del(&xxx_dev.cdev); //注销设备
31                 ...
32          }

B、字符设备驱动的file_operations 结构体中成员函数

file_operations 结构体中成员函数是字符设备驱动与内核的接口,是用户空间对Linux进行系统调用终的落实者。 大多数字符设备驱动会实现read()、 write()和 ioctl()函数,常见的字符设备驱动的这3个函数的形式如代码清单
1   /* 读设备*/
2            ssize_t  xxx_read(struct  file  *filp,  char  _  _user  *buf,  size_t  count,  loff_t*f_pos)
4            {
5                  ...
6                  copy_to_user(buf, ..., ...);
7                  ...
8            } 

设备驱动的读函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不能直接读写,count是要读的字节数,f_pos是读的位置相对于文件开头的偏移。
9   /* 写设备*/
10         ssize_t  xxx_write(struct  file  *filp,  const  char  _  _user  *buf,  size_t  count, loff_t *f_pos)
12         {
13                 ...
14                 copy_from_user(..., buf, ...);
15                 ...
16         } 

设备驱动的写函数中,filp是文件结构体指针,buf是用户空间内存的地址,该地址在内核空间不能直接读写,count是要写的字节数,f_pos是写的位置相对于文件开头的偏移
17  /* ioctl函数 */
18         int  xxx_ioctl(struct  inode  *inode,  struct  file  *filp,  unsigned  int cmd,
unsigned long arg)
20         {
21                 ...
22                 switch (cmd)
23                 {
24                       case XXX_CMD1:
25               ...
26               break;
27                       case XXX_CMD2:
28               ...
29               break;
30                       default: /* 不能支持的命令 */
32               return  - ENOTTY;
33                 }
34                 return 0;
35         }

I/O 控制函数的cmd参数为事先定义的I/O 控制命令, 而 arg为对应于该命令的参数。例如对于串行设备,如果SET_BAUDRATE 是一个设置波特率的命令,那后面的arg就应该是波特率值。

读和写函数中的_ _user 是一个宏,表明其后的指针指向用户空间

在字符设备驱动中,需要定义一个 file_operations 的实例,并将具体设备驱动的函数赋值给file_operations的成员,如代码清单:
1            struct file_operations xxx_fops =
2            {
3                   .owner = THIS_MODULE,
4                   .read = xxx_read,
5                   .write = xxx_write,
6                   .ioctl = xxx_ioctl,
7                   ...
8            };

上一篇:stm32的PWM实现过程

下一篇:GDB调试入门(一)

热点文章推荐
华清学员就业榜单
高薪学员经验分享
热点新闻推荐
前台专线:010-82525158 企业培训洽谈专线:010-82525379 院校合作洽谈专线:010-82525379 Copyright © 2004-2022 北京华清远见科技集团有限公司 版权所有 ,京ICP备16055225号-5京公海网安备11010802025203号

回到顶部