嵌入式Linux:字符设备驱动


一、作业

完善例子中的字符设备程序,使之满足以下功能:

  • 打开关闭:设备关闭前不能被多次打开;
  • 读:安装设备后用户可以读出最近写入到设备中的字符
  • 写:设备支持每次写入字符不超过1024个,超过部分被丢弃;
  • 控制:设备支持系统调用ioctl(int d, int req,…)
    req = 0x909090, 清除设备中写入的字符串。

二、解题过程

1.编译模块(设备驱动程序)

(1)创建模块文件xxx.c

gedit rwbuf.c

区分版本:这里不同,低版本内核2.6以下的是ioctl,高版本内核2.6及其以上的是unlocked_ioctl
在这里插入图片描述
高版本内核2.6及其以上的:

// 模块
#include <linux/module.h>
// 内核
#include <linux/kernel.h>
// struct file_operations
#include <linux/fs.h>
// copy_to_user() & copy_from_user
#include <linux/uaccess.h>

// rwbuf.c,  driver for virtual char-device
#define RW_CLEAR 0x123
// 设备名
#define DEVICE_NAME "rwbuf"

// buffer数组的最大长度
#define RWBUF_MAX_SIZE 200
// buffer数组的实际长度
static int rwlen = 0;
// 设备中存储字符串的相关数组
static char rwbuf[RWBUF_MAX_SIZE];

// 同一时间只允许一个打开的锁机制:0表示未被使用,1表示已被使用
static int inuse = 0;

// 设备的打开:返回-1表示错误;返回0表示成功
int rwbuf_open(struct inode *inode, struct file *file)
{
    // 如果已被使用,报错
    if (inuse == 1)
    {
        return -1;
    }
    // 未被使用
    else
    {
        // 上锁,标志为已被使用
        inuse = 1;
        // increase the use count in struct module
        try_module_get(THIS_MODULE);
        return 0;
    }
}

// 设备的关闭:只返回0表示成功无法表示报错
int rwbuf_release(struct inode *inode, struct file *file)
{
    // 去锁,标志为未被使用
    inuse = 0;
    // decrease the use count in struct module
    module_put(THIS_MODULE);
    return 0;
}

// 设备的读操作:返回-1表示错误;返回(0, RWBUF_MAX_SIZE]表示成功
ssize_t rwbuf_read(struct file *file, char *buf, size_t count, loff_t *f_pos)
{
    // 判断读取的长度是否有效
    if (rwlen > 0 && rwlen <= RWBUF_MAX_SIZE)
    {
        // 从内核空间rwbuf复制到用户空间buf
        copy_to_user(buf, rwbuf, count);
        printk("[rwbuf_read-success]the size of rwlen after read = %d\n", rwlen);
        return count;
    }
    else
    {
        printk("[rwbuf_read-error] rwlen = %d\n", rwlen);
        return -1;
    }
}

// 设备的写操作接:返回-1表示错误;返回(0, RWBUF_MAX_SIZE]表示成功
ssize_t rwbuf_write(struct file *file, const char *buf, size_t count, loff_t *f_pos)
{
    // 判断写入的长度是否有效
    if (count > 0 && count <= RWBUF_MAX_SIZE)
    {
        // 从用户空间buf复制到内核空间rwbuf
        copy_from_user(rwbuf, buf, count);
        // rwlen字符串的长度为buf的长度
        rwlen = count;
        printk("[rwbuf_write-success] the size of rwlen after write = %d\n", rwlen);
        return count;
    }
    else
    {
        printk("[rwbuf_write-error] length of string = %d\n", count);
        return -1;
    }
}

// 设备的ioctl:返回-1表示错误;返回0表示成功
long rwbuf_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    printk("[RW_CLEAR:%x],[cmd:%x]\n", RW_CLEAR, cmd);
    // 命令
    if (cmd == RW_CLEAR)
    {
        // 字符串的长度设置为0,表示清零
        rwlen = 0;
        printk("[rwbuf_ioctl-success] the size of rwlen after ioctl = %d\n", rwlen);
        return 0;
    }
    // 无此命令时
    else
    {
        printk("[rwbuf_ioctl-error] the size of rwlen after ioctl = %d\n", rwlen);
        return -1;
    }
}

// rwbuf_fops要在rwf_buf_init()前面声明,因为register_chrdev()函数要使用到
static struct file_operations rwbuf_fops =
    {
        open : rwbuf_open,
        release : rwbuf_release,
        read : rwbuf_read,
        write : rwbuf_write,
        unlocked_ioctl : rwbuf_ioctl
    };

// module_init()内的初始化函数:返回-1表示错误;返回0表示成功
static int __init rwbuf_init()
{
    printk("Hello world\n");

    // 表示注册成功与否:-1表示失败,0表示成功(同register_chrdev返回值)。初始化为-1
    int ret = -1;
    /*
	参数1:设备的种类,即主设备号
	参数2:设备的名称
	参数3:和VFS对接的接口,即上面的结构体变量
	*/
    ret = register_chrdev(60, DEVICE_NAME, &rwbuf_fops);
    // 注册失败
    if (ret == -1)
    {
        printk("[rwbuf_init-register-failed]\n");
    }
    // 注册成功
    else
    {
        printk("[rwbuf_init-register-success]\n");
    }
    // 返回ret(同register_chrdev返回值)
    return ret;
}

// module_exit()内的退出函数。
static void __exit rwbuf_exit()
{
    unregister_chrdev(60, DEVICE_NAME);
    printk("[rwbuf_exit-unregister-success]\n");
}

// 内核模块入口,相当于main()函数,完成模块初始化
module_init(rwbuf_init);
// 卸载时调用的函数入口,完成模块卸载
module_exit(rwbuf_exit);
// GPL协议证书
MODULE_LICENSE("GPL");

意思:

  • 结构体static struct file_operations rwbuf_fops是定义函数指针
    比如将open指向rwbuf_open,我们调用的时候就打open就是调用rwbuf_open。其中ioctl特殊,调用的时候用ioctl才对,用unlocked_ioctl反而不对,而且定义成旧版本的ioctl,对高版本内核更不对(编译不报错,但读进入的数cmd就成一个莫名其妙的8位十六进制数)。
  • 结构体rwbuf_fopsrwf_buf_init_module()中被用到,生效对结构体变量的操作。
    if(register_chrdev(60, DEVICE_NAME, &rwbuf_fops))

(2)Makefile

gedit Makefile

内容:

obj-m := rwbuf.o
KERNELDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules.order  Module.symvers

(3)编译

sudo make

2.创建设备文件(设备进入点)

(1)创建

sudo mknod /dev/rwbuf c 60 0

意思:

  • /dev目录下创建一个名为rwbuf的设备文件
  • 设备文件的类型是c(字符型设备)
  • 主设备号是60
  • 次设备号是0

(2)赋权

sudo chmod 777 /dev/rwbuf

更改设备的权限。没有读写权限的话,就会读出一堆无意义的乱码。

3.插入内核模块(加载设备驱动程序)

先清理一下缓存,不然一会就可能输出一大堆多余东西,影响到我们想要看到的输出东西

sudo dmesg -c

插入内核模块(加载设备驱动程序)

sudo insmod rwbuf.ko

查看是否成功

dmesg

在这里插入图片描述

4.测试用户应用程序(调用驱动程序)

(1)读写

gedit writeAndRead.c
#include <stdio.h>
// 为了open()中的O_RDWR
#include <fcntl.h>
// #include <unistd.h>
// #include <sys/types.h>
// #include <sys/stat.h>

// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"

int main()
{
    // 声明文件描述符
    int fd;
    // 记录关闭设备的返回值
    int ret;
    // 读取到设备的结果
    char buff[100];

    // 调用打开设备函数。注意O_RDWR是字母O
    fd = open(DEVICE_NAME, O_RDWR);
    // 打开失败
    if (fd == -1)
    {
        printf("[open device %s error]\n", DEVICE_NAME);
        return 0;
    }

    // 调用驱动程序的写操作接口函数
    if (write(fd, "mytest", 5) == -1)
    {
        printf("[write device %s error]\n", DEVICE_NAME);
        return 0;
    }

    // 调用驱动程序的读操作接口函数
    if (read(fd, buff, 5) == -1)
    {
        printf("[read device %s error]\n", DEVICE_NAME);
        return 0;
    }
    // 读取成功
    else
    {
        buff[5] = '\0';
        printf("[buff] %s\n", buff);
    }

    // 若ret的值为0,表示设备成功关闭
    ret = close(fd);
    printf("[close device %s] ret = %d\n", DEVICE_NAME, ret);

    return 0;
}
gcc -o writeAndRead.out -g writeAndRead.c
./writeAndRead.out

在这里插入图片描述

(2)清除程序

gedit ioctl.c
#include <stdio.h>
// 为了open()中的O_RDWR
#include <fcntl.h>
// #include <unistd.h>
// #include <sys/types.h>
// #include <sys/stat.h>

// 定义设备进入点(设备名)
#define DEVICE_NAME "/dev/rwbuf"

int main()
{
    // 声明文件描述符
    int fd;
    // 记录关闭设备的返回值
    int ret;
    // 控制命令
    unsigned int cmd = 0x123;

    // 调用打开设备函数。注意O_RDWR是字母O
    fd = open(DEVICE_NAME, O_RDWR);
    // 打开失败
    if (fd == -1)
    {
        printf("[open device %s error]\n", DEVICE_NAME);
        return 0;
    }

    // 调用驱动程序的写操作接口函数
    if (ioctl(fd, cmd) == -1)
    {
        printf("[ioctl device %s error]\n", DEVICE_NAME);
        return 0;
    }

    // 若ret的值为0,表示设备成功关闭
    ret = close(fd);
    printf("[close device %s] ret = %d\n", DEVICE_NAME, ret);

    return 0;
}
gcc -o ioctl.out -g ioctl.c
./ioctl.out

在这里插入图片描述

(3)未关闭不能多次打开程序

gedit mtreopen.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main()
{
	int fd1 = open("/dev/rwbuf", O_RDWR);
	if(fd1 == -1)
	{
		printf("first open file failed\n");
		return 0;
	}
	
	int fd2 = open("/dev/rwbuf", O_RDWR);
	if(fd2 == -1)
	{
		printf("reopen file failed\n");
		return 0;
	}
	
	close(fd1);
	
	return 0;
}
gcc -o mtreopen.out -g mtreopen.c
./mtreopen.out

在这里插入图片描述

5.卸载

(1)卸载设备驱动程序

sudo rmmod rwbuf

在这里插入图片描述

(2)删除设备文件(设备进入点)

sudo rm /dev/rwbuf

PS:

  1. 只要在模块中的设备号60和注册设备文件(mknod /dev/rwbuf c 60 0)的60一致就能将设备驱动和设备联系在一起。哪怕前者的设备名和后者的设备名不一致。
  2. 注册设备文件(mknod /dev/rwbuf c 60 0)的rwbuf和用户程序(open("/dev/rwbuf", O_RDWR);)的rwbuf之间,只要两者的设备名一致就行。
  3. 也就是说在模块中设备名可以和用户程序中的设备名不一致。
  • 11
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,它可以与J2EE与J2SE应用程序相结合也可以单独使用。Quartz可以用来创建简单或为运行十个,百个,甚至是好几万个Jobs这样复杂的程序。Jobs可以做成标准的Java组件或 EJBs。 Quartz的优势: 1、Quartz是一个任务调度框架(库),它几乎可以集成到任何应用系统中。 2、Quartz是非常灵活的,它让您能够以最“自然”的方式来编写您的项目的代码,实现您所期望的行为 3、Quartz是非常轻量级的,只需要非常少的配置 —— 它实际上可以被跳出框架来使用,如果你的需求是一些相对基本的简单的需求的话。 4、Quartz具有容错机制,并且可以在重启服务的时候持久化(”记忆”)你的定时任务,你的任务也不会丢失。 5、可以通过Quartz,封装成自己的分布式任务调度,实现强大的功能,成为自己的产品。6、有很多的互联网公司也都在使用Quartz。比如美团 Spring是一个很优秀的框架,它无缝的集成了Quartz,简单方便的让企业级应用更好的使用Quartz进行任务的调度。   课程说明:在我们的日常开发中,各种大型系统的开发少不了任务调度,简单的单机任务调度已经满足不了我们的系统需求,复杂的任务会让程序猿头疼, 所以急需一套专门的框架帮助我们去管理定时任务,并且可以在多台机器去执行我们的任务,还要可以管理我们的分布式定时任务。本课程从Quartz框架讲起,由浅到深,从使用到结构分析,再到源码分析,深入解析Quartz、Spring+Quartz,并且会讲解相关原理, 让大家充分的理解这个框架和框架的设计思想。由于互联网的复杂性,为了满足我们特定的需求,需要对Spring+Quartz进行二次开发,整个二次开发过程都会进行讲解。Spring被用在了越来越多的项目中, Quartz也被公认为是比较好用的定时器设置工具,学完这个课程后,不仅仅可以熟练掌握分布式定时任务,还可以深入理解大型框架的设计思想。
[入门数据分析的第一堂课]这是一门为数据分析小白量身打造的课程,你从网络或者公众号收集到很多关于数据分析的知识,但是它们零散不成体系,所以第一堂课首要目标是为你介绍:Ø  什么是数据分析-知其然才知其所以然Ø  为什么要学数据分析-有目标才有动力Ø  数据分析的学习路线-有方向走得更快Ø  数据分析的模型-分析之道,快速形成分析思路Ø  应用案例及场景-分析之术,掌握分析方法[哪些同学适合学习这门课程]想要转行做数据分析师的,零基础亦可工作中需要数据分析技能的,例如运营、产品等对数据分析感兴趣,想要更多了解的[你的收获]n  会为你介绍数据分析的基本情况,为你展现数据分析的全貌。让你清楚知道自己该如何在数据分析地图上行走n  会为你介绍数据分析的分析方法和模型。这部分是讲数据分析的道,只有学会底层逻辑,能够在面对问题时有自己的想法,才能够下一步采取行动n  会为你介绍数据分析的数据处理和常用分析方法。这篇是讲数据分析的术,先有道,后而用术来实现你的想法,得出最终的结论。n  会为你介绍数据分析的应用。学到这里,你对数据分析已经有了初步的认识,并通过一些案例为你展现真实的应用。[专享增值服务]1:一对一答疑         关于课程问题可以通过微信直接询问老师,获得老师的一对一答疑2:转行问题解答         在转行的过程中的相关问题都可以询问老师,可获得一对一咨询机会3:打包资料分享         15本数据分析相关的电子书,一次获得终身学习

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值