最近在看LDD3,理解了一下,为了加深自己的印象,自己梳理一下。我用的CentOS release 6.6 (Final)系统。

一、编写编译内核模块的Makefile

以下是我用的Makefile

ifneq ($(KERNELRELEASE),)
# 第一次被调用时,KERNELRELEASE为空,所以不会被执行
obj-m := scull.o
else
# 将内核编译用的Makefile路径赋值给KERNELRELEASE
KERNELRELEASE ?= /lib/modules/`uname -r`/build
# 当前工作目录
PWD := `pwd`
default:
# 通过内核编译树编译一个模块
$(MAKE) -C $(KERNELRELEASE) M=$(PWD) modules
endif clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions

Makefile要注意tab键和空格不能互相替换使用。这个Makfile其实会被调用2次,第一次这个Makefile会调用  /lib/modules/`uname -r`/build 目录下的Makefile,也就是linux编译内核用的Makefile并且传入后面参数。linux的内核Makefile会再次调用这个Makefile编译内核模块。

二、编写内核模块

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h> MODULE_LICENSE("GPL"); static int scull_major = ; struct scull_dev {
struct cdev cdev;
}; struct scull_dev scull_device; static ssize_t
scull_read(struct file * filep, char __user * buf, size_t count, loff_t *f_pos)
{
printk(KERN_INFO "scull_read\n");
return ;
} static ssize_t
scull_write(struct file *filep, const char __user *buf, size_t count, loff_t *fpos)
{
printk(KERN_INFO "scull_write\n");
return count;
} static int
scull_open(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "scull_open\n");
return ;
} static int
scull_release(struct inode *inode, struct file *filep)
{
printk(KERN_INFO "scull_release\n");
return ;
} struct file_operations scull_fops = { .owner = THIS_MODULE,
.read = scull_read,
.write = scull_write,
.open = scull_open,
.release = scull_release
}; static void scull_setup_cdev(struct scull_dev *dev, int index)
{
cdev_init(&(dev->cdev), &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops; if (cdev_add(&dev->cdev, MKDEV(scull_major, ), )) {
printk(KERN_NOTICE "error adding scull\n");
}
} static int scull_init(void)
{
if (printk_ratelimit())
printk(KERN_INFO "scull loaded.\n"); if (register_chrdev_region(MKDEV(scull_major, ), , "scullc") != ) {
printk(KERN_NOTICE "register chrdev failed.\n");
} scull_setup_cdev(&scull_device, ); return ;
} static void scull_cleanup(void)
{
if (printk_ratelimit())
printk(KERN_INFO "scull unloaded.\n"); unregister_chrdev_region(MKDEV(scull_major, ), );
} module_init(scull_init);
module_exit(scull_cleanup);

scull.c

在模块的初始化函数scull_init里面注册,字符设备。我这边直接是固定的主设备为66,次设备号为0。注册字符设备函数为register_chrdev_region。另外,在scull_cleanup也就是模块卸载函数里面调用unregister_chrdev_region释放申请主设备号。大家自己要注意主设备号不要和自己机器上已有的设备号重复,否则会注册不成功。注册成功的话,我们可以通过/proc/devices这个文件来看。

从图片中,可以看出来,我们已经注册成功了。注册成功之后,自然是要初始化这个设备,通过使用cdev_init和cdev_add来向内核注册字符设备。同时会将struct file_operations scull_fops赋值给这个设备。也就是说当用到这个驱动的设备打开时,scull_open会被调用,scull_read会在读取设备被调用,scull_write会在写入时被调用。这个后面我有演示。

三、创建设备文件

在第二步中,我们已经编写好模块,并且也向内核注册了,申请了主设备号和次设备号,我们现在只要通过下面命令来创建一个设备文件,对这个设备文件的操作,就会使用我们刚刚写的那个简单字符设备驱动。

mknod /dev/scull0 c  

创建完后,通过命令可以看到

图片中就是我们注册的主设备号为66,次设备号为0.

四、测试字符驱动

insmod scull.ko 加载驱动模块

echo 1 > /dev/scull0 向设备文件写入 1

cat /dev/scull0 输出设备文件内容

rmmod scull.ko卸载驱动

以上四个命令分别对应下图的输出

从图中可以看出来 echo的时候会先打开设备,再写入,最后关闭。cat命令会先打开设备,再读取,最后关闭。后面,我会再模仿LDD3中,把这个驱动继续扩展。

LDD3 字符设备驱动简单分析的更多相关文章

  1. LCD驱动分析(一)字符设备驱动框架分析

    参考:S3C2440 LCD驱动(FrameBuffer)实例开发<一>   S3C2440 LCD驱动(FrameBuffer)实例开发<二> LCD驱动也是字符设备驱动,也 ...

  2. Linux 字符设备驱动简单总结(转)

    http://my.oschina.net/u/1169027/blog/191538

  3. 【转】linux设备驱动程序之简单字符设备驱动

    原文网址:http://www.cnblogs.com/geneil/archive/2011/12/03/2272869.html 一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用 ...

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

    原文:Linux内核分析(五)----字符设备驱动实现 Linux内核分析(五) 昨天我们对linux内核的子系统进行简单的认识,今天我们正式进入驱动的开发,我们今后的学习为了避免大家没有硬件的缺陷, ...

  5. arm-linux字符设备驱动开发之---简单字符设备驱动

    一.linux系统将设备分为3类:字符设备.块设备.网络设备.使用驱动程序: 1.字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据.字符设备是面 ...

  6. linux设备驱动第三篇:如何实现一个简单的字符设备驱动

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  7. linux设备驱动第三篇:如何写一个简单的字符设备驱动?

    在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分配的一些内存 ...

  8. 【Linux-驱动】简单字符设备驱动结构和初始化

    (1)在编写简单字符设备驱动的时候,首先要申请一个设备结构struct cdev: struct cdev { struct kobject kobj; struct module *owner; / ...

  9. linux设备驱动第三篇:写一个简单的字符设备驱动

          在linux设备驱动第一篇:设备驱动程序简介中简单介绍了字符驱动,本篇简单介绍如何写一个简单的字符设备驱动.本篇借鉴LDD中的源码,实现一个与硬件设备无关的字符设备驱动,仅仅操作从内核中分 ...

随机推荐

  1. js中的数据类型

    JS中的数据类型: ——数字  (number)NaN ——字符串(string) ——布尔  (boolean)——函数  (function)     也是对象的一种 ——对象  (object) ...

  2. python字符串前面加r

    在Python的string前面加上'r', 是为了告诉编译器这个string是个raw string,不要转意backslash '\' . 例如,\n 在raw string中,是两个字符,\和n ...

  3. C#中的简单工厂和单例

    下面首先来说说简单工厂 举个例子: 首先是父类 public abstract class Pizza { public abstract string Info(); } } 子类 public c ...

  4. 【5集iCore3_ADP演示视频】5-4 iCore3与应用开发平台的组装与拆卸

    iCore3双核心应用开发平台基于iCore3双核心板,包含ARM.FPGA.7寸液晶屏.双通道数字示波器.任意波发生器.电压表等模块,是一款专为电子爱好者设计的综合性电子学习系统. [视频简介]本视 ...

  5. Vue.js起手式+Vue小作品实战

    本文是小羊根据Vue.js文档进行解读的第一篇文章,主要内容涵盖Vue.js的基础部分的知识的,文章顺序基本按照官方文档的顺序,每个知识点现附上代码,然后根据代码给予个人的一些理解,最后还放上在线编辑 ...

  6. Unity运行时检测Altas使用情况

    UI贴图在游戏中内存大小中占的分量非常非常大,尤其对于前期对UI没有规划的项目,无论是包量还是内存大小都是需要花费很多时间去优化.如果涉及到战斗场景和逻辑场景的情况下,常用的做法就是把两个场景使用的a ...

  7. maxscale

    一.maxscale简介1.MaxScale是maridb开发的一个mysql数据中间件,其配置简单,能够实现读写分离,并且可以根据主从状态实现写库的自动切换.2.官网:https://mariadb ...

  8. nginx开启日志,指定格式,查看执行时间

    nginx配置: access_log on; log_format access '$remote_addr – $remote_user [$time_local] "$request& ...

  9. asp.net mvc 过滤器

    https://docs.microsoft.com/en-us/aspnet/core/mvc/controllers/filters#ordering 1. 对于应用在 Action 上的多个行为 ...

  10. js数组去重的hash方法

    对于 JavaScript 数组去除重复项,现在有多种方法,其中一种是hash,如下: if (!Array.prototype.unique) { Array.prototype.unique = ...