linux内核模块编程(三)----字符驱动设备开发

先给自己打个广告,本人的微信公众号正式上线了,搜索:张笑生的地盘,主要关注嵌入式软件开发,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题
在这里插入图片描述

一 前言

在正式开始今天的文章之前,先谈谈个人学习linux开发中的心得,将自己学习过程中遇到的问题呈现到大家面前,以便给大家一些启发,让大家少走一些弯路。

之前我学习linux开发的过程,基本是沿用自己学习单片机开发的路径来的:准备好开发板,准备好开发环境,准备好芯片手册pdf,就可以开始开发学习了。我想大多数从单片机开发,嵌入式开发转到嵌入式linux开发,应该所走的路都跟我相似。

但是这条路好像在嵌入式linux开发的过程中实现起来会比较困难,尤其是对于初学者来说,当然对于一些高手来讲,如果按照嵌入式单片机的开发路径,那么他完全可以称为嵌入式linux开发的全栈工程师了,这种人要么是公司的技术大拿,要么就是公司的底层框架设计师了,业务能力水平那是相当厉害了。

但是,我想对初学者来说,我们不适合这样的路径,因为这样会让我们的精力很分散,不能精通于某个角度的学习。还是拿我自己举例,我在开始进入linux内核模块编程之前,大量的时间花在了如何在一个嵌入式开发板平台上搭建好linux环境,从uboot,busybox,再到linux内核移植,我花了很长很长时间才能搭建好这个环境,期间遇到一些自己明白并且能搞定的,也遇到一些自己不明白但是能搞定的,还有一些自己不明白也没法搞定的,最害怕的就是最后一种情况,自己不明白又搞不定,完全卡壳在这里,因为linux环境还没跑起来啊,也就无法做linux内核模块编程了,搞到自己相当郁闷,也开始怀疑自己的业务能力。后期,没有办法,在各大论坛,qq群潜水、提问题、发文章,最后虽然解决了问题,但是对于自己的linux内核模块编程来说,真的是事倍功半,或者说因为是野路子出生,很多基础知识不扎实。

希望我的这些弯路能给大家一些启发,在大家开始看接下来的内容之前,先反思一下自己的学习方法是否正确。当然,我给大家的建议是,如果是做linux内核模块编程的话,先不要把时间花在uboot,如何移植linux搭建linux环境上,也不要在开发板上学习,最简单方便实惠的方法就是创建好本地ubuntu虚拟机后,直接基于此开始linux内核模块编程的学习吧。

二 what

先问自己一个问题:什么是字符驱动设备?或者先抛去字符两字,什么是linux系统驱动?

linux系统驱动是用户访问底层硬件设备的桥梁,它将用户访问的底层硬件进行封装,使得用户不必关心底层硬件的操作,用户层将这些设备完全当做文件来进行读写等操作。

针对这些各种各样的设备驱动,linux系统将它们分为了三类:字符设备,块设备,网络设备(后两个在本篇中将不做介绍),如下图
在这里插入图片描述
字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。

三 why

  1. 为什么需要字符驱动设备程序?我自己写一个驱动不就好了吗?

我想这个问题也是做嵌入式单片机开发的人,最容易会问的问题,因为从他们的工作经验来看,一个跑在设备上的驱动完全是可以由他一个人来进行的,很多时候也没有区分用户空间和内核空间,当然很多人如果有一个很好的框架,他是对app程序和驱动程序有一个比较明显的区分的。

越是庞大的系统,越涉及多层次的协作,如果上层应用层开发人员完全没有底层硬件驱动的概念,如果没有这些设备驱动程序,他们将完全不懂如何开发。

在linux系统下,有一句很经典的话,就是"一切皆文件",上层业务开发人员可以把所有需要访问的硬件资源,当成文件一样去操作,那么对于文件的操作一般都有:打开文件、关闭文件、读文件、写文件,所以linux驱动开发需要对这些硬件资源实现这些操作:打开操作、关闭操作、读操作、写操作,linux系统会帮我们封装好这些调用流程,所以这里不得不说linux系统的强大,以字符驱动设备为例,调用流程大致如下所示:
在这里插入图片描述

四 how

在正式实现一个字符驱动设备编程之前,先给大家普及一个知识,因为linux内核版本的不断更新,很多接口API的实现以及名字都在不停变化,这样造成的一个结果是,如果你完全不知道自己的内核版本,也不知道我开发时所用的内核版本,直接将我下面的源代码拷贝回去编译的话,会有一大堆报错,相信新手此时也会一头雾水,所以给大家如下建议

1. 先确认自己Ubuntu当前的linux内核版本,在console下输入命令 uname -r,如下面的截图
2. linux内核官网:https://elixir.bootlin.com/linux/v4.15/source
3. 如果你的linux内核版本和我不一致,当你直接拷贝我下面的函数到你的环境下编译出错时,不要慌张,一般都是我调用的API在你的linux内核下面可能没有或者名字不对了,到上面的官网上根据这些关键字搜索即可
4. 再次重申,下面的示例程序,linux内核版本是4.15,请大家一定注意内核版本的差异

在这里插入图片描述
再次强调,请大家一定注意内核版本的差异,我下面的示例程序是在4.15的linux内核版本下开发的。
根据之前的文章《linux内核模块编程》,我们知道linux内核编程的一般框架是init和exit

#include <linux/module.h>
#include <linux/kernel.h>

int test_init(void)
{
    ......
    return 0;
}

void test_exit(void)
{
    ......
}

MODULE_LICENSE("GPL");
module_init(test_init);
module_exit(test_exit);

但是因为我们今天实现的字符驱动设备开发,除了init和exit之外,我们还需要实现open,read,write以及close,今天先实现open,read和close,源代码如下

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/device.h>

static int major;
static struct class *chardev_class;
static struct device *chardrv_class_dev;

static int chardev_drv_open(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "chardev_drv_open\n");
	return 0;
}

static ssize_t chardev_drv_read (struct file *filp, char __user *buf,
	size_t size, loff_t *ppos)
{
	printk(KERN_INFO "chardev_drv_read\n");
	return 0;
}

int chardev_drv_close(struct inode *inode, struct file *file)
{
	printk(KERN_INFO "chardev_drv_close\n");
	return 0;
}

static struct file_operations chardev_drv_fops = {
	.owner   = THIS_MODULE,    /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
	.open    = chardev_drv_open,
	.read    = chardev_drv_read,
	.release = chardev_drv_close,
};

static int chardev_drv_init(void)
{
	printk(KERN_INFO "already chardev_drv_init\n");

	major = register_chrdev(0, "chardev_drv", &chardev_drv_fops);
	chardev_class = class_create(THIS_MODULE, "chardrv");
	chardrv_class_dev = device_create(chardev_class, NULL, MKDEV(major, 0), NULL, "chardev_drv"); /* /dev/xyz */

	return 0;
}

static void chardev_drv_exit(void)
{
	printk(KERN_INFO "new exit chardev_drv\n");
	unregister_chrdev(major, "chardev_drv");
	device_unregister(chardrv_class_dev);
	class_destroy(chardev_class);
}

module_init(chardev_drv_init);
module_exit(chardev_drv_exit);
MODULE_LICENSE("GPL");

Makefile

obj-m:=char_dev.o

KDIR:= /lib/modules/$(shell uname -r)/build
PWD:= $(shell pwd)

all:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
clean:
	$(MAKE) -C $(KDIR) M=$(PWD) clean
	

五 test

  1. 编译make
  2. 加载驱动sudo insmod char_dev.ko
  3. 查看打印demsg | tail,发现内核加载成功
    在这里插入图片描述
  4. 因为我们创建了一个字符驱动设备,这个字符驱动设备在/dev路劲下,开头的"c"表示字符设备
    在这里插入图片描述
  5. 卸载`sudo rmmod char_dev
    在这里插入图片描述
  6. 再次查看/dev, ls -al | grep timer,发现已经没有这个字符设备了。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值