您的位置:首页 > 其它

字符设备驱动程序编写基础

2012-05-14 09:15 288 查看
《字符设备驱动程序编写基础》

本文档的Copyleft归rosetta所有,使用GPL发布,可以自由拷贝、转载,转载时请保持文档的完整性。

参考资料:《Linux设备驱动程序 第三版》,scull源码,Linux内核源码

来源:http://blog.csdn.net/rosetta/article/details/7563606

本文写了字符设备驱动编写的基础知识,以自己学习过程来记录,写的内容比较具体,也可以说是《Linux设备驱动程序》第三章字符设备驱动程序的读书笔记。

主要写怎么生成字符设备文件?字符设备文件怎么和内核模块挂钩?怎么编写一个简单的字符设备模块插入内核?怎么访问字符设备模块注册的函数?

适合入门级新手。

使用代码示例源于scull源码。

1、基本概念

*对字符设备的访问是通过文件系统的设备名称进行的,文件系统内的设备名称可通过ls /dev -l命令查看,第一列用"c"标识的为字符设备文件,

即访问字符设备是通过访问字符设备驱动的设备文件达到的。

*主设备号和次设备号

ls /dev -al

crw-r----- 1 root kmem 1, 1 04-29 11:57 mem

crw-rw---- 1 root audio 14, 2 04-29 11:58 midi

crw-rw---- 1 root audio 14, 0 04-29 11:58 mixer

crw-r--r-- 1 root root 252, 0 04-29 12:19 my_module

crw-r--r-- 1 root root 260, 1 04-29 12:20 my_module1

其中的1,14,252,260为主设备号,1, 2, 0为次设备号。

主设备号用来标识设备对应的驱动程序。

而这些设备文件可由自行创建,以上的my_module使用252号驱动程序及my_module1使用260号驱动都是由我自己添加的(当然现我在这个驱动是假使的,是不存在的)

添加字符设备文件命令:mknod /dev/my_module1 c 260 1(添加后没有任何作用,需要注册后才和驱动挂钩起作用,一会会有实例)

当前系统有哪些驱动程序可通过

[root@xxx scull]# cat /proc/devices

Character devices:

1 mem

4 /dev/vc/0

4 tty

4 ttyS

5 /dev/tty

5 /dev/console

5 /dev/ptmx

6 lp

7 vcs

10 misc

13 input

14 sound

21 sg

29 fb

116 alsa

128 ptm

136 pts

162 raw

180 usb

189 usb_device

253 scull //register_chrdev_region注册的。

253 scullp

253 sculla

次设备号由内核使用,用于正确确定设备文件所指的设备,因为多个设备文件可使用同一驱动,但需要通过次设备号来区别,这样通过次设备号可以获得一个指向内核设备的指针,

也可以理解为数组的索引,指针的偏移量。(次设备号具体有什么作用,没接触过这方面的代码,所以没什么体会)

2,在建立字符设备前,驱动程序需要首先获得设备号,可以指定一个未使用的设备号,也可以动态分配。

当驱动程序获得了一个设备号后,怎么和设备文件联系起来呢?比如当前驱动程序获得的设备号为252

那么可以使用类似mknod /dev/my_module c 252 0 来建立一个设备文件,驱动程序把此主设备号的设备文件向内核注册后即可使用。



下面看实例:

首先创建设备文件:

mknod /dev/scull0 c 253 0 (scull源码目录下的scull/scull_load可自动生成)

分配主从设备号

int result, i;

dev_t dev = 0;

if (scull_major) {//主动指定主设备号

dev = MKDEV(scull_major, scull_minor);//把主从设备号转换成dev_t类型

result = register_chrdev_region(dev, scull_nr_devs, "scull");//最后一个参数“scull"就是cat /proc/devices显示的,如上面显示。

} else {//如果不指定,则由内核自动分配主从设备号

result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,

"scull");

scull_major = MAJOR(dev);

}

if (result < 0) {

printk(KERN_WARNING "scull: can't get major %d\n", scull_major);

return result;

}

分配了主从设备号后,怎么和刚才建立的设备文件/dev/scull0关联起来呢?那就需要向内核注册了

注册的时候需要一个关键的结构体struct cdev,最关键的一个成员ops,cdev结构可以封闭在自已定义

的结构体中,也可以直接就申请一个此结构体。看scull源码:

struct scull_dev *scull_devices //他的这个scull_dev就是自己定义的,里面包含有cdev结构体

scull_setup_cdev(&scull_devices[i], i)

static void scull_setup_cdev(struct scull_dev *dev, int index)

{

int err, devno = MKDEV(scull_major, scull_minor + index);



cdev_init(&dev->cdev, &scull_fops);

dev->cdev.owner = THIS_MODULE;//默认

dev->cdev.ops = &scull_fops;//cdev结构体的ops在此初始化。

err = cdev_add (&dev->cdev, devno, 1);//真正告诉内核我初始化化完成,即向内核注册。devno

//对就的函数cdev_del从系统中移除一个设备。

/* Fail gracefully if need be */

if (err)

printk(KERN_NOTICE "Error %d adding scull%d", err, index);

}



struct file_operations scull_fops = {//这个结构体中的东西才是核心啊!

.owner = THIS_MODULE,

.llseek = scull_llseek,

.read = scull_read,

.write = scull_write,

.ioctl = scull_ioctl,

.open = scull_open,

.release = scull_release,

};

因为单位历史因素,从一开始的2.4内核搞到现的2.6内核,所以还在延续使用老一设备接口,他们是:

register_chrdev()

和unregister_chrdev()

3,到目前为止一个简单的设备驱动模块就已经完成(完整源码请参考scull)。那么我们怎么对其操作和使用呢?

可以直接到scull/scull/ make 再执行./scull_load,进行简单的测试



读,通过cat /dev/scull进行

写,可以通过ls -al > /dev/scull

下面再写一个测试程序,演示用户层和内核层的读、写、ioctl操作。

//其中的test.h是由scull.h改过来的。

#include <stdio.h>

#include <string.h>

#include <stdlib.h>



#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <linux/sysctl.h>



#define TESTFILE "/dev/scull"

#include "test.h"



int main(int argc, char *argv[])

{

int fd = -1;

fd = open(TESTFILE, O_RDWR);

if(fd < 0)

{

printf("open error.\n");

return -1;

}



char buf[30];

if(strncmp(argv[1], "write", 5) == 0)//读和写都是比较简单的,也是容易理解。ioctl费了点神。

{

printf("write.\n");

write(fd, "hello scull.\n", 20);

}else if(strncmp(argv[1], "read", 4) == 0)

{

printf("read.\n");

read(fd, buf, 20);

printf("read buf:%s\n", buf);

}else if(strncmp(argv[1], "ioctl", 4) == 0)

{

printf("ioctl.\n");

int tmp = 111;//题外话,现在喜欢用这种风格,如果把局部变量都一起定义在开头固然整洁,但不方便看代码,还是什么时候用什么定义好。

int ret = -1;

if(ret = ioctl(fd, SCULL_IOCQQUANTUM, &tmp) < 0)//执行这个就是掉了内核里的scull_ioctl(),SCULL_IOCQQUANTUM

{

printf("ioctl error\n");

return -1;

}

printf("ret:%d, tmp:x0%0x, %d\n", ret, &tmp, tmp);//可以在ko模块中把对应的值打出来对比

}



close(fd);



return 0;

}

[root@xxx scull]# gcc test.c -Wall -g -o app

//内核层ioctl,用户层的ioctl传进来时,cmd = SCULL_IOCQQUANTUM, arg = tmp(地址相同);

int scull_ioctl(struct inode *inode, struct file *filp,

unsigned int cmd, unsigned long arg)



//截取部分内核层ioctl

switch(cmd) {

case SCULL_IOCRESET:

scull_quantum = SCULL_QUANTUM;

scull_qset = SCULL_QSET;

break;

case SCULL_IOCSQUANTUM: /* Set: arg points to the value */

if (! capable (CAP_SYS_ADMIN))

return -EPERM;

retval = __get_user(scull_quantum, (int __user *)arg);

break;

case SCULL_IOCTQUANTUM: /* Tell: arg is the value */

if (! capable (CAP_SYS_ADMIN))

return -EPERM;

scull_quantum = arg;

break;

case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */

retval = __put_user(scull_quantum, (int __user *)arg);

break;

case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */

{//自己改了下这个case分析。

printk("SCULL_IOCQQUANTUM\n");//打印了此字符串。

retval = __put_user(200, (int __user *)arg);//再把arg设为200.

//return scull_quantum;

return retval;

}

下面再帖一段调试结果:

[root@xxx scull]# ./app write

write.

[root@xxx scull]# ./app read

read.

read buf:hello scull.



[root@xxx scull]# ./app ioctl

ioctl.

ret:0, tmp:x0bfd01af0, 200

[root@xxx scull]# dmesg

in func:scull_open

buf:hello scull.



in func:scull_release

in func:scull_open

kernel read buf:hello scull.



in func:scull_release

in func:scull_open

in func:scull_ioctl

cmd :27399

arg:0xbfd01af0

_IOC_TYPE(cmd):107

SCULL_IOC_MAGIC:107

SCULL_IOCQQUANTUM

in func:scull_release

[root@panlimin scull]#



本人暂时也只能理解到这么多,至于ioctl能干什么?可以从内核获得一些数据这是必然的,

看书上说还可以对其进行一系列的控制,这当然肯定可以,这么多case分支,用户层选择适合自己

的cmd就可以完成对应的操作。除了这些,它还能干吗?或者说ioctl的用法? 除了read、write、

ioctl外,其它的比如mmap、poll操作实现等,内存共享、异步等原理的学习理解等都需要进一步

去研究学习。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: