Linux内核模块编程-字符设备驱动

原创 2015年11月07日 17:30:34

设备驱动简介

设备被大概的分为两类: 字符设备和块设备。

  • 字符设备
    提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调器都是典型的字符设备。

  • 块设备
    应用程序可以随机访问设备数据,程序可自行确定读取数据的位置。硬盘、软盘、CD-ROM驱动器和闪存都是典型的块设备,应用程序可以寻址磁盘上的任何位置,并由此读取数据。此外,数据的读写只能以块(通常是512B)的倍数进行。与字符设备不同,块设备并不支持基于字符的寻址。

这两种类型的设备的根本区别在于它们是否可以被随机访问。字符设备只能顺序读取,块设备可以随机读取。其它的一些区别:

  • 字符设备只能以字节为最小单位访问,而块设备以块为单位访问,例如512字节,1024字节等
  • 块设备可以随机访问,但是字符设备不可以
  • 字符和块没有访问量大小的限制,块也可以以字节为单位来访问

对于设备来说,linux抽象为一个个文件,放在/dev目录下,每个文件都有一定的属性其中比较重要的有主设备号和次设备号两个。主设备号标明了设备的类型,次设备号表示同类型设备的不同设备

[root@localhost blog]# ls -al /dev/sda
brw-rw----. 1 root disk 8, 0 Apr 22 22:49 /dev/sda
8是主设备号表示这是一个磁盘,0是次设备号区别相同类型设备的不同设备

开始编写字符设备驱动

注册和撤销设备

通过注册设备驱动模块,可以将一组操作设备的函数和某一设备进行关联起来。
注册一个设备:

linux/fs.h 中的函数 register_chrdev 
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
其中major是主设备号,name是设备的名字,fops则是该设备对应的一些列操作函数,成功返回主设备号
major如果为0的话,系统会给我们随机分配主设备号

问题是主设备号怎么去定义,难道可以自己随便填吗?如果大家都这样,那么设备好像就乱了要申请一个没有使用的设备号可以去查看文档:Documentation/devices.txt
撤销一个设备:

int unregister_chrdev(unsigned int major, const char *name);

在linux中把对设备的操作,抽象成对文件的操作,写设备驱动程序,其实本质是去填充file_operations这个结构体

设备操作

实现一个最基本的设备驱动程序的话,那么至少应该实现下面四个操作设备的函数打开一个设备,向设备写入数据,从设备读取数据,关闭设备等

static struct file_operations fops = {
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

打开设备的实现

static int Device_Open = 0; //表明设备状态,是打开还是关闭
所有打开设备的函数原型都是这样,一个设备的inode,还有一个与设备文件对应的file结构体
static int device_open(struct inode *inode,struct file *file)
{
    static int counter = 0;  //记录设备打开的次数
    if(Device_Open) 
        return -EBUSY;
    Device_Open++; 
    sprintf(msg,"I already told you %d times Hello world\n",counter++);
    msg_Ptr = msg;
    try_module_get(THIS_MODULE);  //增加内核模块的引用的计数
    return SUCCESS;
}

释放设备的实现

//释放设备
static int device_release(struct inode *inode,struct file *file)
{
    Device_Open--;   //更改设备打开状态
    module_put(THIS_MODULE);  //减少内核模块的引用计数
    return 0;
}

读取设备的实现

内核空间和用户空间的数据拷贝使用copy_to_user和copy_from_user两个函数实现 
//读文件,把内核空间的数据拷贝到buffer上
static ssize_t device_read(struct file *filp,char *buffer,size_t length,loff_t *offset)
{
    if(*msg_Ptr == 0) 
        return 0;
    return copy_to_user(buffer,msg_Ptr,length); //把msg_Ptr指向的内容拷贝到buffer中
}

写入设备的实现

//写文件,这里占时还没有去实现这个功能
static ssize_t device_write(struct file *filp,const char *buff,size_t len,loff_t *off)
{
    printk("<1> Sorry this operation isn't supported\n");
    return -EINVAL;
}

到此为止,写一个设备驱动所具备的的要素都完成了。那么最后是写成一个内核模块的形式

内核模块的初始化和注销

#define SUCCESS 0
#define DEVICE_NAME "chardev"
#define BUF_LEN 80

static int Major;
static int Device_Open = 0;
static char msg[BUF_LEN];
static char *msg_Ptr;

//内核模块初始化
int init_module(void)
{
    //注册设备驱动,随机产生主设备号
    Major = register_chrdev(0,DEVICE_NAME,&fops);
    if(Major < 0) {
        printk("Registering the character device failed with %d\n",Major);
    return Major;
    }
    printk("<1> I was assigned major number %d ",Major);
    printk("<1> the drive,create a dev file");
    printk("<1> mknod /dev/hello c %d 0.\n",Major);
    printk("<1> I was assigned major number %d ",Major);
    printk("<1> the device file\n");
    printk("<1> Remove the file device and module when done\n");
    return 0;
}
void cleanup_module(void)
{
    unregister_chrdev(Major,DEVICE_NAME);
}

到此为止一个简单的设备驱动就完成了,剩下的时间是要测试了,

内核模块测试

插入内核模块,使用dmesg查看系统随机分配的主设备号
insmod chardev.ko

dmesg查看内核输出日志
 I was assigned major number 247 
 the drive,create a dev file
 mknod /dev/hello c 247 0.
 I was assigned major number 247 
 the device file
 Remove the file device and module when done

根据输出的主设备号,创建设备
mknod /dev/hello c 247 0

最后你就可以使用用户态程序去open这个设备和read/write这个设备了。

用户态程序进行测试

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
int main()
{
    char buf[4096] = {0};
    int fd = open("/dev/hello",O_RDWR);
    int ret = read(fd,buf,sizeof(buf));
    buf[ret] = '\0';
    printf("%s\n",buf);
}
输入结果:
I already told you 1 times Hello world
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

linux下c —open函数

头文件 #include  /*提供类型pid_t,size_t的定义*/ #include #include 函数原型 int open(const char *path, int ofl...

Linux内核中的常用宏container_of其实很简单

开发平台:Ubuntu11.04     编 译器:gcc version 4.5.2 (Ubuntu/Linaro4.5.2-8ubuntu4)       Container_of在Linu...
  • npy_lp
  • npy_lp
  • 2011年11月27日 19:50
  • 25166

Linux内核编程七:字符设备驱动

个人总结PDF截图,特此声明!

将设备驱动程序模块源码添加到Linux内核模块源码中

1、将设备驱动程序模块globalmem添加到内核源码中,存放路径是kernel/drivers/mydriver/globalmem; 2、globalmem模块包含源码文件:globalmem....

Linux内核开发之简单字符设备驱动(上)

废话少说,先来介绍几个必须要知道的和字符设备有关的结构体,然后结合代码详细讲解。 第一部分 必要的设备结构体 1)linux 2.6内核中使用cdev结构体表示字符设备: struct ...
  • Aniu127
  • Aniu127
  • 2014年06月04日 15:27
  • 521

Linux内核学习-字符设备驱动学习(二)

在Linux内核学习-字符设备驱动学习(一)中编写字符设备驱动的一种方法,但是需要手动创建设备节点。有没有能够自动的创建设备节点的呢?有!使用class_create()和device_create(...

Linux内核学习-字符设备驱动学习(一)

Linux内核学习-字符驱动学习(一) 现在学习一下Linux的字符设备驱动,参考的样本应该就是ldd3这书大概第3章的内容吧。下面的所说的字符设备都是基于2.6内核的,一般的流程都是,呵呵,其实也不...

Linux内核分析(五)----字符设备驱动实现

Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷,我们都会以虚拟的设备为例进行学习,所以大家不必害怕没有...

linux内核字符设备驱动相关的函数以及结构体

1. struct cdev {          struct kobject kobj;          struct module *owner;          const stru...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux内核模块编程-字符设备驱动
举报原因:
原因补充:

(最多只允许输入30个字)