字符设备驱动程序编写基础
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操作实现等,内存共享、异步等原理的学习理解等都需要进一步
去研究学习。
本文档的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操作实现等,内存共享、异步等原理的学习理解等都需要进一步
去研究学习。
相关文章推荐
- Linux字符设备驱动程序编写基本流程
- Linux 字符设备驱动开发基础(二)—— 编写简单 PWM 设备驱动
- Linux字符设备驱动程序编写基本流程
- Linux字符设备驱动程序的编写框架
- 第12课第2.1节 字符设备驱动程序之LED驱动程序_编写编译
- Linux字符设备驱动程序的编写框架
- [转]ubuntu 编写字符设备驱动程序
- Linux字符设备驱动程序的编写框架
- Linux 字符设备驱动开发基础(一)—— 编写简单 LED 设备驱动
- Linux设备驱动开发基础---字符设备驱动程序开发之mini2440_LED驱动
- Linux字符设备驱动程序编写基本流程
- 字符设备驱动程序的编写_点亮LED灯
- Linux字符设备驱动程序的编写框架
- Linux字符设备驱动程序的编写框架
- Linux字符设备驱动程序的编写框架
- Linux设备驱动开发基础---字符设备驱动程序开发之基于中断的按键驱动
- Linux字符设备驱动程序的编写框架
- 字符设备驱动程序的编写
- Linux字符设备驱动程序的编写框架
- Linux字符设备驱动程序编写基本流程