您的位置:首页 > 其它

字符设备驱动程序的基本步骤

2013-08-29 00:17 393 查看
字符设备驱动程序的基本步骤

字符设备驱动程序的基本步骤

    一.设备号

    对字符设备的访问是通过文件系统内的设备名称来访问的,设备名称位于目录/dev下.为了便于系统管理,设置了和设备名称一一对应的设备号,它分为主设备号和次设备号.通常来说,主设备号标示了设备对应的驱动程序,次设备号则用来分辨拥有同一个主设备号的的各个不同设备.

    在内核中,设备号使用类型dev_t来保存,它包括了主设备号和次设备号.dev_t是一个32位的整数,其中的12位用来标示主设备号,其余的20位用来标示次设备号.我们可以使用两个宏来获得设备的主设备号及次设备号:

    MAJOR(dev_t dev_id);

    MINOR(dev_t dev_id);

    将主设备号和次设备号转换为dev_t类型,则可以使用下面的宏:

    MKDEV(int major, int minor);

    其中,major为主设备号,minor为次设备号.

    二.分配设备号

    在建立一个字符设备之前.首先要申请设备号,完成该功能的函数有两个,都包含在头文件中.下面分别来看这两个文件:

    1.int register_chrdev_region(dev_t first, unsigned int count, char *name);

    其中, first为要分配的设备编号范围的起始值,经常被置零.count则是所请求的连续设备编号的个数,这意味着只能申请连续的设备编号.

    2.int alloc_chrdev_region(dev_t *dev, unsigned firstminor, int count, char *name);

    其中dev用于保存申请成功后动态分配的第一个设备号, firstminor则是请求使用的第一个次设备号.其余与上个函数相同.

    三.定义并初始化file_operations结构体.

    file_operations结构体用于连接设备号和驱动程序的操作.在该结构体的内部包含了一组函数指针,这些函数用来实现系统调用.通常情况下,要注册如下的几个函数:

    1.struct module *owner:用来指向拥有该结构体的模块.

    2.ssize_t read(struct file *filp, char __user *buf, size_t count, loff_t *f_ops):用来从设备中读取数据.其中:

    filp为文件属性结构体指针.

    buf为用户态函数使用的字符内存缓冲.

    count为要读取的数据数.

    f_ops为文件指针的偏移量.

    2.ssize_t write(struct file *filp, const char __user *buf, size_t count, loff_t *f_ops):用来向设备输入数据.各函数的含义与上个函数相同.

    3.int open(struct inode *inode, struct file *):该函数用来打开一个设备文件.

    4.int release(struct inode *inode, struct file *):该函数用来关闭一个设备文件.

    该结构体的初始化形式如下例:

    struct file_operations scull_fops = {

        .owner = THIS_MODULE,

        .read = read,

        .write = write,

    .open = open,

    .release = release,

    }

    四.字符设备的注册.

    内核内部使用struct cdev结构来表示字符设备.在内核调用设备的操作之前,必须分配或注册一个或者多个该结构体.该结构体包含在头文件中.一般的步骤如下:

    首先定义该结构体:

    struct cdev my_cdev;

    然后即可以初始化该结构,使用如下的函数初始化:

    int cdev_init(struct cdev *dev, struct file_operations *fops).

    然后定义该结构体中的一个所有者字段:

    my_cdev.owner = THIS_MODULE;

    最后即可以向模块添加该结构体:

    int cdev_add(struct cdev *dev, dev_t dev_num, usigned int count).其中dev是cdev结构体,dev_num是该设备对应的第一个设备编号,
count则是与该设备关联的设备编号数量.

    五.移除字符设备

    vo
ab85
id cdev_del(struct cdev *dev);

    六.注销设备号.

    unregister_chrdev_region(dev_t first, unsigned int count);

    以上两个函数一般用于模块出口函数中.
 
linux 一个简单的字符设备驱动例子
先包含这些头文件

#include <linux/module.h>

#include <linux/types.h>

#include <linux/fs.h>

#include <linux/errno.h>

#include <linux/mm.h>

#include <linux/sched.h>

#include <linux/init.h>

#include <linux/cdev.h>

#include <asm/io.h>

#include <asm/system.h>

#include <asm/uaccess.h>
#define BUFFERSIZE 200

#define DEVICE_MAJOR 250 /*设置一个主设备号*/

static int device_major = DEVICE_MAJOR;

定义一个与字符设备对应的结构体

struct my_cdev

{

struct cdev cdev; /*cdev结构体,与字符设备对应*/
/*下面可以定义一些与字符设备相关的数据*/

usigned char mem[BUFFERSIZE];

};
struct my_cdev *my_cdevp; /*设备结构体指针*/
int my_cdev_open( struct inode *node, struct file *filp )

{

/*将设备结构体指针赋给文件私有数据指针*/

filp->private_data = my_cdevp /*这样可以通过文件私有数据指针得到设备结构体*/

return 0;

}
int my_cdev_release( struct inode *node, struct file *filp )

{

return 0;

}
static size_t my_cdev_read( struct file *filp, char __user *buf, size_t size, loff_t *ppos )

{

unsigned long p = *ppos; /*文件当前位置*/

unsigned int count = size; /*要读取的长度*/

int ret = 0;
struct my_cdev *dev = filp->private_data; /*通过文件私有数据指针得到设备结构体,和前面的open对应*/
if ( p >= BUFFERSIZE )

return count ? -ENXIO:0;

if ( count > BUFFERSIZE - p )

count = BUFFERSIZE - p;
/*内核空间->用户空间*/

if ( copy_to_user(buf, (void *)(dev->mem + p), count) )

{

ret = -EFAULT;

}

else

{

*ppos += count;

ret = count;

}

return ret;

}
static size_t my_cdev_write( struct file *filp, const char __user *buf, size_t size, loff_t *ppos )

{

unsigned long p = *ppos;

unsigned int count = size;

int ret = 0;
struce my_cdev *dev = filp->private_data;
if( p >= BUFFERSIZE )

return count ? -ENIX : 0;

if (count > BUFFERSIZE - p )

count = BUFFERSIZE - p;
if ( copy_from_user( dev->mem + p, buf, count))

{

ret = -EFAULT;

}

else

{

*ppos += count;

ret = count;

}
return ret

}
static loff_t my_cdev_llseek(struct file *filp, loff_t offset, int orig)

{

loff_t ret = 0;

switch (orig)

{

case 0:

if ( offset < 0 )

{

ret = -EINVAL;

break;

}

if (offset > BUFFERSIZE)

{

ret = -EINVAL;

break;

}

filp->f_pos = (unsigned int)offset;

ret = fips->f_pos;

break;

default:

ret = -EINVAL;

break;

}
return ret;

}
/*文件操作结构体*/

static const struct file_operstions my_cdev_fops =

{

.owner = THIS_MODULE,

.open = my_cdev_open,

.release = my_cdev_release,

.read = my_cdev_read,

.write = my_cdev_write,

.llseek = my_cdev_llseek,

};
/*初始化并注册cdev,就是注册我们自己的字符设备*/

static void my_cdev_setup( struct my_cdev *dev, int index )

{

int err;

dev_t devno = MKDEV(DEVICE_MAJOR, index);
cdev_init( &dev->cdev, &my_cdev_fops );

dev->cdev.owner = THIS_MODULE;

dev->cdev.ops = &my_cdev_fops; /*我认为在cdev_init里应该做过赋值,应该可以不用写*/

err = cdev_add( &dev->cdev, devno, 1 );

if (err)

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

}
static int __init my_cdev_init(void)

{

int result;

dev_t devno = MKDEV(DEVICE_MAJOR, index);
/*申请设备号*/

if ( device_major )

{

result = register_chrdev_region(devno, 1, "my_cdev");

}

else

{

result = alloc_chrdev_region( &devno, 0, 1, "my_cdev");

device_major = MAJOR(devno);

}

if ( result < 0 )

{

return result;

}
my_cdevp = kmalloc(sizeof(struct my_cdev), GFP_KERNEL);

if ( !my_cdevp )

{

result = -ENOMEM;

goto fail_malloc;

}

memset(my_cdevp, 0, sizeof(struct my_cdev));
my_cdev_setup(my_cdevp, 0);

return 0;
fail_malloc:

unregister_chrdev_region(devno, 1);

return result;

}
static void __exit my_cdev_exit(void)

{

cdev_del(&my_cdevp->cdev);

kfree(my_cdevp);

unregister_chrdev_region(MKDEV(device_major, 0), 1);

}
MODULE_AUTHOR("Song Baohua");

MODULE_LICENSE("Dual BSD/GPL");
module_init(my_cdev_init);

module_exit(my_cdev_exit);
*********************************************************************************

然后可以写一个简单的内核模块的Makefile,编译make后生成mycdev.ko文件,insmod mycdev.ko,

装上我们自己的驱动,注意有可能在装的时候提示说device busy什么的,这就是我们前面指定的主设备号现在

有设备在用,我们就在重新指定一个在编译。

那怎么看有那些设备呢?可以用cat /proc/devices,就可以查看已经有哪些主设备号已被占用。
然后就可以自己先创建一个虚拟的字符设备mknod /dev/mycdev c 250 0
******************************************************

下面就可以自己写一个应用程序来看我们自己的字符设备驱动是否OK。
#include <stdio.h>

#include <fctl.h>

#include <string.h>

#include <sys/stat.h>
#define BUFFERSIZE 200
int main( void )

{

int fp = 0 ;

char str[BUFFERSIZE];
fp = open( "/dev/mycdev", O_RDWR, S_IRUSR|S_IWUSR );

if ( !fp )

{

printf("Open device failed\n");

return -1;

}
write( fp, "Hello, my devices", strlen("Hello, my devices") );
lseek( fp, 0, 0 );/*修改字符设备里字符数组的位置,将字符数据位置设到开始的位置,不然下面的read操作将读不到数据*/
read( fp, str, BUFFERSIZE );
printf("Read content: %s\n", str );
close(fp);

}
gcc -o sample sample.c
最后运行./sample
应该会输出:Read content: Hello, my devices
总结一下,我个人觉得字符设备驱动相关的API和数据结构。

1. struct cdev 一个设备对应一个这个的数据结构,结构体是重要的两个字段ops 和dev(设备号)

2. struct file_opertions 文件操作结构体

3. cdev_init(struct cdev *, struct file_opertions *) 主要就是把字符设备和对这个设备的文件操作结构体对应起来

4. cdev_add(struct cdev *, dev_t, unsigned) 注册设备

5. register_chrdev_region(dev_t, unsigned, const char *name)/alloc_chrdev_region() 申请设备号,为注册设备(cdev_add())准备

6. 就是内核空间的数据和用户空间的数据交换
 
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: