现在的位置: 首页 > 综合 > 正文

linux字符设备驱动总结分析

2012年12月22日 ⁄ 综合 ⁄ 共 3269字 ⁄ 字号 评论关闭
    最近,一直在看书学习linux设备驱动,从最简单的字符设备驱动入门,能够对驱动的框架和各元素的功能有个宏观的了解。下面详细分析我写的第一个字符设备驱动,整理一些驱动的基础知识,加深印象。先给出一些宏定义和全局变量:
#define GLB_MEM_SIZE 0x1000
#define HELLO_MAJOR 250
#define MEM_CLEAR 0x1
int hello_major = HELLO_MAJOR;
struct globalmem_dev
{
    struct cdev cdev;
    unsigned char mem[GLB_MEM_SIZE];
};               
 //此处定义一个全局变量,用于构造一个虚拟的字符设备
struct globalmem_dev *devp;
首先,linux设备驱动属于内核的一部分,具有明显的模块特性。字符设备驱动包括以下几大块:
(一)驱动加载和卸载模块
module_init(hello_drv_init);
module_exit(hello_drv_exit);
其中module_init和module_exit是内核定义的宏,用于将自己编写的模块加载到内核或者从内核载。hello_drv_inithello_drv_exit
我们自己定义的函数,hello_drv_init是驱动的初始化函数,主要完成以下几部分工作:
1. 为字符设备向系统申请设备号
dev_t devno = MKDEV(hello_major, 0); 
如果手动分配好主设备号hello_major,则MKDEV宏可生成设备号,其中次设备号为0,而且主设备号占12位,次设备号占20位
register_chrdev_region(devno, 1, "helloworld");
以上函数为向系统手动申请一个设备号记录到chardevs数组当中,设备名为“helloworld”,前提是设备号未被使用
alloc_chrdev_region(devno, 0, 1, "helloworld");
以上函数表示向系统动态申请未被占用的设备号,第一个参数devno用于存放获得的设备号,第二个参数0表示次设备号,第三
个参数1表示只申请一个。
hello_major = MAJOR(devno);
hello_minor = MINOR(devno);
以上两个宏分别表示从设备号devno中提取主设备号hello_major和次设备号hello_major
2. 为字符设备的结构体分配内存并初始化
devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
memset(devp, 0, sizeof(struct globalmem_dev));
上面GFP_KERNEL是内核分配内存空间的一个标识,表示无内存可用时进入休眠状态。
3. 初始化cdev
devno = MKDEV(hello_major, 0);
cdev_init(&devp->cdev, &fops);该函数用于初始化cdev的成员,建立cdev和file_operations之间的联系
devp->cdev.owner = THIS_MODULE;
4. 注册cdev
cdev_add(&devp->cdev, devno, 1); 向系统添加一个cdev,完成字符设备的注册。
同样,模块卸载的时候,会进行与注册时相对应的三个操作
cdev_del(&devp->cdev);//注销cdev
kfree(devp);//释放结构体内存
unregister_chrdev_region(MKDEV(hello_major, 0), 1);//释放设备号
(二)file_operations 数据结构体的填充
本人简单理解,应用程序对底层设备的访问是通过open、close、ioctl等函数操作的,而且应用层的这些函数是通过系统调用以及文件系统的一个桥梁作用,最终调用的正是file_operations中与之相对应的函数,所以file_operations 结构体中的函数实现需要由驱动工程师去实现。
static struct file_operations fops =
{
.owner                = THIS_MODULE,
.llseek                  = hello_llseek,
.read                   = hello_read,
.write                   = hello_write,
.ioctl                    = hello_ioctl,
.release              = hello_release,
}
(三)file_operations 结构体中函数的实现
下面以最常见的read和write函数作为例子来分析,
static int hello_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
unsigned long p = *ppos;//读的位置相对于文件开头的偏移量
int ret = 0;
//读取偏移位置是否越界,或者字节数太大的判断
if(p >= GLB_MEM_SIZE)
    return 0;
if(size > GLB_MEM_SIZE - p)
    size = GLB_MEM_SIZE - p;
//开始读取
if(copy_to_user(buf, (void*)(devp->mem+p), size))
    ret = -EFAULT;
else
{
    *ppos += size;
    ret = size;
    printk(KERN_INFO "read %d bytes from %d\n", size, p);
}
    return ret;
}
其中file是文件结构体指针,buf为用户空间的内存,size是需要读取的字节数,因为内核空间和用户空间的内存不能互相访问,需要借助于copy_to_user函数完成内核空间到用户空间的映射(即拷贝),同理在写的时候,则需要copy_from_user完成用户空间到内存空间的映射,这两个函数返回的都是不能被复制的字节数,所以,如果完全映射(复制)成功,则返回0.
static int hello_write(struct file *file, const char __user *buf, size_t count, loff_t *offset)
{
    int ret;
    unsigned long p = *offset;
    if(p >= GLB_MEM_SIZE)
        return 0;
    if(count> GLB_MEM_SIZE - p)
        count = GLB_MEM_SIZE - p;
    if(copy_from_user((devp->mem+p), buf, count))
        ret = -EFAULT;
    else
    {
        *ppos += count;
        ret = count;
        printk(KERN_INFO "write %d bytes to %d\n", count, p);
    }
    return ret;
}
(四)头文件和宏
驱动模块中常使用到的头文件,可参考我转载的一篇关于linux驱动头文件的文章。另外,还有一些宏如下:
MODULE_AUTHOR("W. Yihong <a5131wyh@163.com>");//模块作者的一些信息声明
MODULE_DESCRIPTION("hello world driver"); //模块功能的一些描述
MODULE_LICENSE("GPL");//模块许可证的声明,如果不声明LICENSE,则模块加载时会收到内核被污染的警告
MODULE_ALIAS("platform:myfirst_driver"); //模块可以调用此宏为自己定义一个或者若干个别称
以上是我对字符设备驱动的一个简单理解,还有很多驱动的机制和内涵并不清楚,需要在以后的实际项目中去慢慢摸索和总结,本人正在入门,希望大家能指出文章不足之处,共同进步。
 

 
 

抱歉!评论已关闭.