第一个字符设备驱动

时间:2023-01-04 12:09:16

转载请注明出处:http://blog.csdn.net/ruoyunliufeng/article/details/45054183


         linux驱动分为字符设备、块设备驱动、网络驱动三种,其中以字符驱动最为简单。说起要写驱动自然想到从字符设备驱动写起。看了开发板官方的驱动代码,对新手来说简直是噩梦。新手来说要看懂,实在不容易。其中包含了很多知识和设计思想。所以我想还是尽可能从易到难来写这个系列,相信我,我会努力把我知道的都给大家讲清楚。

一、驱动代码

/* 
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/types.h>
#include <linux/moduleparam.h>
#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/device.h>

#define HELLO_CNT 1 //所请求连续设备编号个数

static int hello_major = 0; //默认内核自动分配
static int hello_minor = 0; //默认此设备号从0开始
static struct cdev hello_cdev;
static struct class *hello_class;

static int hello_open(struct inode *inode, struct file *file)
{
printk("hello open\n");
return 0;
}

static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
};

static int hello_char_init(void)
{
dev_t devid;
int err,result;

/*1.分配主设备号*/
if(hello_major)
{
devid = MKDEV(hello_major, hello_minor);
result = register_chrdev_region(devid,HELLO_CNT,"hello");
}
else
{
result = alloc_chrdev_region(&devid,hello_minor,HELLO_CNT,"hello");
hello_major = MAJOR(devid);
}
if(result < 0)
{
printk(KERN_WARNING"hello:can't get major %d\n", hello_major);
return result;
}

/*2.注册字符设备驱动*/
cdev_init(&hello_cdev, &hello_fops);
err = cdev_add(&hello_cdev, devid, HELLO_CNT);
if(err)
printk(KERN_WARNING"Error %d adding hello",err);

/*3.创建设备*/
hello_class = class_create(THIS_MODULE, "hello");
if (IS_ERR(hello_class))
{
printk(KERN_WARNING "class_create() failed for hello_class\n");
}
device_create(hello_class, NULL, MKDEV(hello_major, 0), NULL, "hello0"); /* /dev/hello0 */
return 0;
}

static void hello_char_exit(void)
{
device_destroy(hello_class, MKDEV(hello_major, 0));
class_destroy(hello_class);

cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(hello_major, 0), HELLO_CNT);

}
module_init(hello_char_init);
module_exit(hello_char_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ruoyunliufeng");


二、测试程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


/*
* hello_test /dev/hello0
*/

void print_usage(char *file)
{
printf("%s <dev>\n", file);
}

int main(int argc, char **argv)
{
int fd;
if (argc != 2)
{
print_usage(argv[0]);
return 0;
}

fd = open(argv[1], O_RDWR);
if (fd < 0)
printf("can't open %s\n", argv[1]);
else
printf("can open %s\n", argv[1]);

return 0;
}


三、驱动讲解

1.驱动的入口与出口

          在用C写的应用程序中,我们程序的入口是main()函数。驱动程序也有入口,那就是module_init();括号里面的函数就是入口函数,有了入口自然就有出口module_exit();每当insmod XXX.ko的时候驱动就会进入入口函数。入口函数主要做一些初始化的工作。rmmod  XXX.ko的时候,驱动就会调用出口函数,出口函数组要做一些注销和清理工作。


2.字符驱动初始化

          字符设备初始化的框架大体就是这样,可以直接当做模板来用,分三步:

           1.分配诸设备号

                     主设备号可以自己定义,也可以交给内核帮你分配,但一般都推荐内核分配,所以这里用了一个if语句来分别处理这两种情况。

           2.注册字符驱动

                     你的驱动要让内核知道就必须注册呀。调用cdev_init();cdev_add()两个函数

           3.创建设备结点

                     这里选择自动创建的方式,你也完全可以在驱动中不写,然后自己去手动创建。首先class_create()然后device_create()。这样在/dev/下就能出现你的设备了。这样就可以对你的设备进行一系列的读写等操作了。

3.如何调用驱动

          应用程序调用open函数,通过你在驱动中写的file_operations hello_fops去调用其中的hello_open。这样应用和驱动就建立了联系,其他函数也是一样。其余的实现也是一样,比如需要写入数据,就在hello_fops中加入写函数。驱动提供的是机制,不提供策略。策略是应用程序该干的事。“需要提供什么功能”即机制,“如何使用这些功能”即策略。每个函数都有它的功能,这里要注意不要乱写。否则你的驱动程序看上去就像一坨翔。


注:这里我并没有详细的去分析每个函数的参数,意义。我觉得这些东西完全可以自己去内核中看,我不想把我的文章变成翻译内核注释,文档。我希望看我的文章你能领会大概的框架,具体细节自己去看内核吧。源码之前,了无秘密。


参考:ldd3