linux 驱动 学习步骤,​Linux驱动开发学习的一些必要步骤

Linux驱动开发学习的一些必要步骤

1. 学会写简单的makefile

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

3. 学会写驱动的makefile

4. 写一简单char驱动,makefile编译通过,可以insmod,

lsmod,

rmmod. 在驱动的init函数里打印hello

world,

insmod后应该能够通过dmesg看到输出。

5. 写一完整驱动, 加上read,

write,

ioctl,

polling等各种函数的驱动实现。

在ioctl里完成从用户空间向内核空间传递结构体的实现。

6. 写一block驱动,

加上read,write,ioctl,poll等各种函数实现。

7. 简单学习下内存管理, 这个是最难的,明白各种memory

alloc的函数实现细节。这是Linux开发的基本功。

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

9. 看内核中实际应用的驱动代码。 你会发现最基本的你已经知道了, 大的框架都是一样的, 无非是read,

write,

ioctl等函数的实现,

但里面包含了很多很多细小的实现细节是之前不知道的。 这时候就要考虑到很多别的问题而不仅仅是基本功能的实现。 推荐您看2.6.20中integrated的一个驱动

kvm,

记得是在driver/lguest下,很好玩的,

就是Linux下的虚拟机驱动,

代码不长,但功能强大。有能力的可以自己写一操作系统按照要求做成磁盘镜像加载到虚拟机中, 然后客户机可以有自己的4G虚拟地址空间。

10. 看完驱动欢迎您进入Linux

kernel学习中来。

最简单的方法,跟着ldd(Linux

devive driver)做一遍。

1、Makefile

是如何编写

eg:

# 这是上面那个程序的Makefile

文件 1

main:main.omytool1.o

mytool2.o

2

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

main.o:main.cmytool1.h

mytool2.h

4

gcc-c main.c

5

mytool1.o:mytool1.cmytool1.h

6

gcc-c mytool1.c

7

mytool2.o:mytool2.cmytool2.h

8

gcc-c

mytool2.c 9

分析:

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

明.一般的格式是:Linux

操作系统C

语言编程入门

target:components

//表示的是依赖关系

TABrule //规则

main:main.omytool1.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有三个非常有用的变量.分别是$@,$^,$

目标文件;$^--所有的依赖文件;$

2、字符设备驱动

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

2.1、驱动结构

1)

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

定义:

1 structcdev{

3 struct kobject

kobj;

4 struct  module

*owner;

5 struct file_operations

*ops;

6 struct list_head

list;

7 dev_t dev; 定义了设备号

8 unsignedint

count;

9 };

dev_t成员定义了设备号,为

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

MAJOR(dev_tdev)

//主设备号

MINOR(dev_tdev) //次设备号

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

MKDEV(intmajor, int minor)

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

Linux 2.6内核提供了一组函数用于操作

cdev结构体

Voidcdev_init(structcdev

*, 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 voidcdev_init(structcdev

*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;

8 }

cdev_alloc()函数用于动态申请一个cdev内存

1 structcdev *cdev_alloc(void)

2 {

3 struct cdev *p=kmalloc(sizeof(struct cdev),GFP_KERNEL);

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 returnp;

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 structfile_operations

2 {

3 structmodule *owner; // 拥有该结构的模块的指针,一般为THIS_MODULES

5 loff_t(*llseek)(structfile *, loff_t, int); // 用来修改文件当前的读写位置

7

ssize_t(*read)(structfile *, 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)(structfile *, void *, filldir_t); //

仅用于读取目录,对于设备文件,该字段为NULL

17 unsignedint(*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)(structfile *, unsigned int, unsigned long);

// 在64位系统上,32位的ioctl调用将使用此函数指针代替

25 int(*mmap)(structfile *, struct vm_area_struct*); //

用于请求将设备内存映射到进程地址空间

27 int(*open)(structinode *, struct file*); // 打开

29 int(*flush)(structfile*);

30 int(*release)(structinode *, struct file*); / 关闭

32 int(*synch)(structfile *, struct dentry *, int datasync); //

刷新待处理的数据

34 int(*aio_fsync)(structkiocb *, int datasync); //

异步fsync

36 int(*fasync)(int,struct file *, int); // 通知设备FASYNC标志发生变化

38 int(*lock)(structfile *, 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)(structfile *, struct page *, int, size_t, loff_t

*, int); // 通常为NULL

46 unsignedlong(*get_unmapped_area)(struct file *,unsigned long,

unsigned long, unsignedlong, unsigned long); // 在进程地址空间找到一个将底层设备中的内存段映射的位置

49 int(*check_flags)(int);// 允许模块检查传递给fcntl(F_SETEL...)调用的标志

51 int(*dir_notify)(structfile *filp, unsigned long arg); //

仅对文件系统有效,驱动程序不必实现

53 int(*flock)(structfile *, 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 structxxx_dev_t

3 {

4 structcdev cdev;

5 ...

6 }xxx_dev;

7 //设备驱动模块加载函数

8 staticint _ _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

18 int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd,

unsignedlong arg)

20 {

21 ...

22 switch(cmd)

23 {

24 caseXXX_CMD1:

25 ...

26 break;

27 caseXXX_CMD2:

28 ...

29 break;

30 default:

32 return - ENOTTY;

33 }

34 return0;

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

};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值