我本仁慈,奈何苍天不许
分类: LINUX
2013-12-26 16:46:43
字符设备驱动
{//?什么是字符设备
从设备数据传递的特点看, 如果是按字节流顺序传递过来的,就像生产流水线,如串口,键盘,鼠标等。
我们把它们抽象为字符设备。
}
{//?为何要提出字符设备概念
//?多种多样的硬件设备需要控制,我们如何去管理控制它们
统一形式 --- 抽象为文件
分类 --- 不同类的设备对应不同的框架
从数据通信的角度:
|--- 字符设备 字节流顺序传输 (类似流水线生产,按先后顺序进行传递)
数据顺序 ---|
|--- 块设备 按块传输,块内部可随机访问(类似卡车送货,一车一车的送,车内的货可任意拿)
|---- UART
协议 ---|---- I2C
|---- SPI
|---- USB
|---- 网络设备
从其它角度:
平台设备(Platform) 显示设备 视频设备等
}
{//?重点在那
掌握如何去创建和使用一个简单的字符设备
}
{//?如何实现和使用一个字符设备
{1. 注册设备号
{//---hello_char/step1/hello.c
#include
#include
#include
#include
MODULE_LICENSE ("GPL");
#define DEVICE_MAJOR 250 //主设备号 用于区分不同种类的设备
//某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。 这里我们常使用250
#define DEVICE_MINOR 0 //次设备号 用于区分同一类型的多个设备
#define DEVICE_NUM 1 //有多少个设备
#define DEVICE_NAME "hello" //设备名称
static int __init hello_init (void)
{
int ret;
dev_t dev_id = 0; //设备号(由主次设备号合并而成)
if(DEVICE_MAJOR){ //当主设备号为零时,用动态分配设备号
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR); //MKDEV 用于合并主次设备号生成设备号 见 include/linux/kdev_t.h
ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME); /*注册字符设备号(静态分配)
为一个字符驱动获取一个或多个设备编号
dev_id: 分配的起始设备编号(常常是0)
DEVICE_NUM: 请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
DEVICE_NAME: 是应当连接到这个编号范围的设备的名字
*/
if (ret<0) {
printk (" can't get major number %d\n", DEVICE_MAJOR);
return ret;
}
}else{
ret = alloc_chrdev_region(&dev_id,0, DEVICE_NUM, DEVICE_NAME); //注册字符设备(动态分配),当不清楚主设备号时,常用该方式,让系统为它自动分配
//因设备号是随机的,采用从cat /proc/devices中获取设备号后,再mknod 创建设备文件
if(ret<0)
{
printk("alloc_chrdev fail\n");
return ret;
}
}
printk("char device register ok\n");
return 0;
}
static void __exit hello_exit (void)
{
dev_t dev_id = 0;
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);
unregister_chrdev_region (dev_id, DEVICE_NUM);
printk("char unregister ok\n");
}
module_init (hello_init);
module_exit (hello_exit);
}
//---Makefile 和内核模块Makefile一样
//---使用
insmod hello.ko
cat /proc/devices //有hello 表示该注册成功
}
{2. 初始化字符设备
//---hello_char/step2/hello.c
#include
#include
#include
#include
#include
MODULE_LICENSE ("GPL");
#define DEVICE_MAJOR 250
#define DEVICE_MINOR 0
#define DEVICE_NUM 1
#define DEVICE_NAME "hello"
struct cdev *cdev; //定义字符设备指针
struct file_operations hello_fops = { //文件操作
.owner = THIS_MODULE
};
static int __init hello_init (void)
{
int ret;
dev_t dev_id = 0;
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);
ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME);
if (ret<0) {
printk (" can't get major number %d\n", DEVICE_MAJOR);
goto err_reg_cdev;
}
cdev = cdev_alloc(); //分配空间给字符设备
if(NULL == cdev)
{
printk (" cdev_alloc fail\n");
goto err_cdev_alloc;
}
cdev_init(cdev,&hello_fops); //字符设备初始化
cdev->owner = THIS_MODULE; //THIS_MODULE 相当于c++中this ,表示该字符设备为本模块所有
cdev->ops = &hello_fops; //字符设备相关的文件操作
ret = cdev_add(cdev,dev_id,DEVICE_NUM); //添加字符设备到系统中
if(ret)
{
printk("cdev_add fail %d\n",ret);
goto err_cdev_add;
}
printk("char device register ok\n");
return 0;
//---goto 出错处理, 顺序申请,逆序释放,避免资源回收不完全(如内存泄露)
err_cdev_add:
cdev_del(cdev);
err_cdev_alloc:
unregister_chrdev_region(dev_id,DEVICE_NUM);
err_reg_cdev:
return ret;
}
static void __exit hello_exit (void)
{
dev_t dev_id = 0;
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);
if (cdev)
cdev_del (cdev);
unregister_chrdev_region (dev_id, DEVICE_NUM);
printk("char unregister ok\n");
}
module_init (hello_init);
module_exit (hello_exit);
}
{3. 最简方式操作设备
{//---hello_char/hello.c
#include
#include
#include
#include
#include
#include
MODULE_LICENSE ("GPL");
#define DEVICE_MAJOR 250
#define DEVICE_MINOR 0
#define DEVICE_NUM 1
#define DEVICE_NAME "hello"
struct cdev *cdev;
static int hello_open (struct inode *inode, struct file *file) //打开文件
{
printk ("device opened\n");
return 0;
}
static int hello_release (struct inode *inode, struct file *file) //关闭文件
{
printk ("device closed\n");
return 0;
}
// file_operations 中 定义了针对文件的一系列操作方法,实现你需要的
struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
};
static int __init hello_init (void)
{
int ret;
dev_t dev_id = 0;
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);
ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME);
if (ret<0) {
printk (" can't get major number %d\n", DEVICE_MAJOR);
goto err_reg_cdev;
}
cdev = cdev_alloc();
if(NULL == cdev)
{
printk (" cdev_alloc fail\n");
goto err_cdev_alloc;
}
cdev_init(cdev,&hello_fops);
cdev->owner = THIS_MODULE;
cdev->ops = &hello_fops;
ret = cdev_add(cdev,dev_id,DEVICE_NUM);
if(ret)
{
printk("cdev_add fail %d\n",ret);
goto err_cdev_add;
}
printk("char device register ok\n");
return 0;
err_cdev_add:
cdev_del(cdev);
err_cdev_alloc:
unregister_chrdev_region(dev_id,DEVICE_NUM);
err_reg_cdev:
return ret;
}
static void __exit hello_exit (void)
{
dev_t dev_id = 0;
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);
if (cdev)
cdev_del (cdev);
unregister_chrdev_region (dev_id, DEVICE_NUM);
printk("char unregister ok\n");
}
module_init (hello_init);
module_exit (hello_exit);
}
{//---使用 注意:使用root 用户登录,否则可能出现open /dev/hello fail 的错误
#mknod /dev/hello c 250 0 //创建设备文件,应用才能访问它. ( ls -l /dev 可以看到很多其它设备文件)
// crw------- 1 root root 250, 0 2013-01-15 16:00 farsight_dev
// c表示是字符设备 rw表示可读可写 250是主设备号 0是次设备号 farsight_dev是设备名
//---test.c
#include
#include
#include
#include
#include
#include
int main (void)
{
int fd;
fd = open ("/dev/hello",O_RDWR); /* 应用open是如何调用到驱动的open的
open -> sys_open -> inode -> driver .open
应用 | 系统调用 | 文件系统 | 驱动
*/
if (fd < 0) {
printf("open /dev/hello fail\n");
return -1;
}
close (fd);
printf("app end\n");
return 0;
}
}
}
--------------------------------------
{4. 实现需要的文件操作 //重点 难点
{//---hello_char/hello.c
#include
#include
#include
#include
#include
#include
#include "hello.h"
MODULE_LICENSE ("GPL");
#define DEVICE_MAJOR 250
#define DEVICE_MINOR 0
#define DEVICE_NUM 1
#define DEVICE_NAME "hello"
struct cdev *cdev;
#define C_BUF_LEN 64
static char c_buf[C_BUF_LEN]; //用于存储读写数据
//打开文件
static int hello_open (struct inode *inode, struct file *file)
{
printk ("device opened\n");
return 0;
}
//关闭文件
static int hello_release (struct inode *inode, struct file *file)
{
printk ("device closed\n");
return 0;
}
//读文件
static ssize_t hello_read (struct file *filp, char *buff, size_t count, loff_t *offp)
{
ssize_t result = 0;
if(count > C_BUF_LEN -1 ) count = C_BUF_LEN -1;
if(count < 0) return -EINVAL;
if (copy_to_user(buff,c_buf, count))
result = -EFAULT;
else
printk ("read %d bytes\n", count);
result = count;
return result;
}
//写文件
static ssize_t hello_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
ssize_t ret = 0;
printk ("Writing %d bytes\n", count);
if (count > C_BUF_LEN -1) return -ENOMEM;
if (count<0) return -EINVAL;
if (copy_from_user (c_buf, buf, count)) {
ret = -EFAULT;
}
else {
c_buf[63]='\0';
printk ("Received: %s\n", c_buf);
ret = count;
}
return ret;
}
//ioctl 用于完成特殊操作
static int hello_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret=0;
switch (cmd) {
case HELLO_ONE:
printk ("HELLO_ONE cmd called\n");
break;
case HELLO_TWO:
printk ("HELLO_TWO cmd called\n");
break;
default:
printk ("cmd other called\n");
break;
}
return ret;
}
/* file_operations 中 定义了针对文件的一系列操作方法
不是每个都需实现
*/
struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
.ioctl = hello_ioctl
};
static int __init hello_init (void)
{
int ret;
dev_t dev_id = 0;
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);
ret = register_chrdev_region (dev_id, DEVICE_NUM, DEVICE_NAME);
if (ret<0) {
printk (" can't get major number %d\n", DEVICE_MAJOR);
goto err_reg_cdev;
}
cdev = cdev_alloc();
if(NULL == cdev)
{
printk (" cdev_alloc fail\n");
goto err_cdev_alloc;
}
cdev_init(cdev,&hello_fops);
cdev->owner = THIS_MODULE;
cdev->ops = &hello_fops;
ret = cdev_add(cdev,dev_id,DEVICE_NUM);
if(ret)
{
printk("cdev_add fail %d\n",ret);
goto err_cdev_add;
}
printk("char device register ok\n");
return 0;
err_cdev_add:
cdev_del(cdev);
err_cdev_alloc:
unregister_chrdev_region(dev_id,DEVICE_NUM);
err_reg_cdev:
return ret;
}
static void __exit hello_exit (void)
{
dev_t dev_id = 0;
dev_id = MKDEV (DEVICE_MAJOR , DEVICE_MINOR);
if (cdev)
cdev_del (cdev);
unregister_chrdev_region (dev_id, DEVICE_NUM);
printk("char unregister ok\n");
}
module_init (hello_init);
module_exit (hello_exit);
}
{//---hello_char/hello.h
#define HELLO_MAGIC 'x' //幻数:0~0xff的数。用于区分不同的驱动, 见Documentation/ioctl/ioctl-number.txt
#define HELLO_ONE _IO (HELLO_MAGIC, 1) //加幻数方式来定义命令,防止不同驱动间命令错乱
#define HELLO_TWO _IO (HELLO_MAGIC, 2)
}
{//---使用 注意:使用root 用户登录,否则可能出现open /dev/hello fail 的错误
#mknod /dev/hello c 250 0 //创建设备文件,应用才能访问它. ( ls -l /dev 可以看到很多其它设备文件)
// crw------- 1 root root 250, 0 2013-01-15 16:00 farsight_dev
// c表示是字符设备 rw表示可读可写 250是主设备号 0是次设备号 farsight_dev是设备名
//---test.c
#include
#include
#include
#include
#include
#include
#include
#include "hello.h"
int main (void)
{
int fd;
char buff[]=" let's go ";
fd = open ("/dev/hello",O_RDWR); /* 应用open是如何调用到驱动的open的
open -> sys_open -> inode -> driver .open
应用 | 系统调用 | 文件系统 | 驱动
*/
if (fd < 0) {
printf("open /dev/hello fail\n");
return -1;
}
write (fd, buff, sizeof(buff));
memset(buf,'\0',sizeof(buf));
read (fd, buff, sizeof(buff) - 1);
printf("read buf is %s\n",buf);
ioctl (fd, HELLO_ONE);
close (fd);
printf("app end\n");
return 0;
}
}
}
}
{//方法
{//?linux中有许多函数的功能,参数不知道怎么办
1. 跳转到函数定义的地方,有些函数内核中有功能和参数的说明。
有的仅根据函数或参数的命名就能猜出来。
2. 内核中有许多使用它的例子,根据这些例子进行推测
}
}