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内核模块编程-字符设备驱动

设备驱动简介设备被大概的分为两类: 字符设备和块设备。 字符设备 提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。举例来说,键盘、串口、调制解调...

linux内核ioctl(字符设备驱动)

序言 设备驱动程序的一个基本功能就是管理和控制设备,同时为用户应用程序提供管理和控制设备的接口。我们前面的“Hello World”驱动程序已经可以提供读写功能了,在这里我们将扩展我们的驱动以支持设...

精选:深入理解 Docker 内部原理及网络配置

网络绝对是任何系统的核心,对于容器而言也是如此。Docker 作为目前最火的轻量级容器技术,有很多令人称道的功能,如 Docker 的镜像管理。然而,Docker的网络一直以来都比较薄弱,所以我们有必要深入了解Docker的网络知识,以满足更高的网络需求。

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

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

linux 内核编程之字符设备驱动

【版权声明:转载请保留出处:blog.csdn.net/gentleliu。邮箱:shallnew*163.com】 首先需要注册设备号,有两个函数可以实现该功能:int register_chrd...

Android内核开发 Linux C编程调用内核模块设备驱动

本文目的为Linux系统环境下:1、编写内核模块程序并编译 2、加载内核模块 3、编写C程序调用内核模块 功能为向内核模块虚拟设备写如字符串,再从内核模块虚拟设备读出字符串长度。

Android内核开发 Linux Java编程调用内核模块设备驱动

编写Java程序调用内核模块 功能为向内核模块虚拟设备写字符串,再从内核模块虚拟设备读出字符串长度。 编译加载内核模块 见《 Android内核开发 Linux C编程调用内核模块设备驱动》

Linux内核编程初探:块设备驱动程序——Ramdisk

Linux 内核编程
  • my_xxh
  • my_xxh
  • 2015-09-28 15:47
  • 1048

Linux内核驱动模块

Linux内核驱动模块 Linux设备驱动会以内核模块的形式出现,因此,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。 4.1~4.2节讲解了Linux内核模块的概念和结构,4...

Linux内核驱动模块

Linux设备驱动会以内核模块的形式出现,因此,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。 4.1~4.2节讲解了Linux内核模块的概念和结构,4.3~4.8节对Linux...

Linux内核驱动模块

Linux内核驱动模块 Linux设备驱动会以内核模块的形式出现,因此,学会编写Linux内核模块编程是学习Linux设备驱动的先决条件。 4.1~4.2节讲解了Linux内核模块的概念和...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

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