最简单的scull设备驱动
目录
- 一. 实验题目:设备管理与驱动编程
- 二. 实验目的
- 三. 实验内容
- 四. 实验过程及结果
- 1)关键代码
- 2)实验过程
- 第一部分 内核外编译:
- 第二部分 源码树内编译
- 五. 未解决的问题
- 附实验源码
一. 实验题目:设备管理与驱动编程
该题目是写一个scull设备驱动,虽然scull不是实体的硬件驱动,但是具备驱动的各个元素,其实质是在rom中开辟指定size的内存,该博客是对作业过程以及遇到的错误的简短记录:
二. 实验目的
理解设备驱动应包含的基本功能;
学会将设备驱动从内核源码树外加入到内核的方法;
学会将设备驱动从内核源码树内加入到内核的方法。
三. 实验内容
将树莓派设想为智能家居Linux服务器,可用来采集并维护环境数据,如PM2.5、温度、湿度、气味、电器状态数据等。实际环境中,数据来自物理设备,如传感器,本次试验用scull设备模拟。有条件的小组可以通过类似的步骤将物理设备添加到系统中;
创建2个scull设备,一个设备驱动选择从树莓派内核源码树外(Kbuild)编译安装,另一个设备驱动选择从树莓派内核源码树内以模块方式编译安装。驱动函数要求包括:scull_open(),scull_read(),scull_write(),scull_ioctl(),scull_release();
编写测试程序,验证所实现的驱动是否正常工作。
四. 实验过程及结果
注:scull1分为两个版本:主机本地编译和树莓派交叉编译两种,由于两者系统版本不同,因而要分别选择对应版本的源码,更改源码路径。具体体现在Makefile文件的差异
1)关键代码
Makefile(主机本地):
Makefile(交叉编译):
h文件:
c文件:
设备启动程序和写入设备程序:
以上均为对字符文件的操作,函数头存入结构体file_operations中:
设备安装、卸载函数:
2)实验过程
第一部分 内核外编译:
主机本地:
创建设备:
安装和卸载模块的显示:
由于自kernel 2.6.36版本起linux中file_operations的ioctl已经被删除,改用unlocked_ioctl实现特定功能,这个函数的功能是调用之前不再先调用lock_kernel()然后再unlock_kernel()(不需要经过内核的上锁和解锁)。
原先:
改为:
运行测试程序:
交叉编译:
由于对应版本的源码仅仅是官网压缩包解压后的文件,没有config和编译,所以会提示一系列文件错误:
将源码deconfig:
发现仍缺少文件:
完整编译完内核后发现仍然有错误:
这些错误显然是因为编译链没有选对而产生的,原因是当初Makefile未指定平台和编译器信息:
指定后编译可以通过:
随后将ko程序传输到树莓派中,但是在运行insmod scull1时发生insmod: ERROR: could not insert module scull1.ko: Invalid module format这一错误,是由于编译的源码版本和pi的版本不一致所导致。
pi的版本:
解决方案一,将树莓派内核升级到现有的源码版本(不推荐这一方法,因为在实际工程中不可能随意更改目标底层的系统,由于本次时间有限,所以采取此下下策):
解决方案二,重新下载和树莓派版本一致的源码,再重新编译一遍,再通过新的源码make出新的ko文件,传输至树莓派上。(无图,在此采用的方案一)
升级后版本,和源码一致:
此时出现另一个错误:
输入cat /proc/devices,查看设备号使用情况:
发现240号设备已使用。所以应当修改scull1.h的MAJOR的值,改为未占用的设备号。
重新创建设备文件:
安装成功:
查看日志:
运行测试:
Command:
读写:写入ab\0,再读出:
第二部分 源码树内编译
总共6步:
Kconfig:
修改Makefile:
Menuconfig,选择以模块形式添加:
编译内核(包括生成module):
(scull2设备号为258:)
五. 未解决的问题
问题一,Printk打印的信息未在终端显示:
因为printk本身为后台打印函数,所以应当进入后台查看。如果想在终端显示后台信息,有两种方法;
方法1,在一个终端写一个脚本,每秒实时显示日志信息:
方法2:用dmesg查看日志:
问题二,测试中,cmd2不能执行:
Scull1.c:
Scull1.h:
发现如果将cmd2定义成别的数,便可以执行,但具体原因仍未搞清楚。
附实验源码
scull1.c / scull2.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include "scull1.h"struct scull_dev *scull_device;int scull_open(struct inode*inode, struct file *filp)
{struct scull_dev *dev;dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; return 0;
}int scull_release(struct inode*inode, struct file *filp)
{printk(KERN_NOTICE "Scull released.\n");return 0;
}ssize_t scull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{ssize_t retval = -ENOMEM;//.. //???????if (raw_copy_from_user(scull_device->scull_buffer+*f_pos, buf, count)) {retval = -EFAULT;goto out;}retval=count;
out:return retval;
}//fop:?
ssize_t scull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{ssize_t retval = 0;if (raw_copy_to_user(buf, scull_device->scull_buffer+*f_pos, count)) {retval = -EFAULT;goto out;}retval=count;
out:return retval;
}//fop:ioctl
long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{if (cmd == SCULL_CMD1) {printk("running SCULL_CMD1 \n");return 0;}else if (cmd == SCULL_CMD2) {printk("running SCULL_CMD2 \n");return 0;}else if (cmd == SCULL_CMD3) {printk("running SCULL_CMD3 \n");return 0;}else printk("cmd error! \n");return -EFAULT;
}struct file_operations scull_ops = {.owner = THIS_MODULE,.read = scull_read,.write = scull_write,.open = scull_open,.unlocked_ioctl = scull_ioctl,.release = scull_release
};void scull_cleanup_module(void)
{dev_t devno = MKDEV(SCULL_MAJOR, SCULL_MINOR);if (scull_device) {cdev_del(&scull_device->cdev);kfree(scull_device);}unregister_chrdev_region(devno, 1);printk(KERN_NOTICE "Scull module exit.\n");
}//module init
int scull_init_module(void)
{int result;dev_t dev = 0; if (scull_major) {dev = MKDEV(scull_major, scull_minor);result = register_chrdev_region(dev, 1, "scull");} else {result = alloc_chrdev_region(&dev, scull_minor, 1,"scull");scull_major = MAJOR(dev);}if (result < 0) {printk(KERN_WARNING "scull: can't get major %d\n", SCULL_MAJOR);return result;}scull_device = kmalloc(sizeof(struct scull_dev), GFP_KERNEL);if (!scull_device) {result = -ENOMEM;goto fail;}memset(scull_device, 0, sizeof(struct scull_dev));cdev_init(&scull_device->cdev, &scull_ops);scull_device->cdev.owner = THIS_MODULE;scull_device->cdev.ops = &scull_ops;int err = cdev_add (&scull_device->cdev, dev, 1);printk(KERN_NOTICE "Scull module init.\n");return 0;
fail:scull_cleanup_module();return result;
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);MODULE_AUTHOR("0B703");
MODULE_LICENSE("GPL");
scull1.h(和scull2.h一致,只是scull2.h的设备号为258)
#define SCULL_BUFF_MAXSIZE 1024
#define SCULL_MAJOR 256 //设备号
#define SCULL_MINOR 0
#define SCULL_CMD1 1
#define SCULL_CMD2 2
#define SCULL_CMD3 3int scull_major = SCULL_MAJOR;
int scull_minor = 0;struct scull_dev {char scull_buffer[SCULL_BUFF_MAXSIZE];struct cdev cdev; //cdev结构是描述字符设备的数据结构
};
Makefile(主机本地):
ifneq ($(KERNELRELEASE),)obj-m:=scull1.o
elseKERNELDIR:=/lib/modules/$(shell uname -r)/buildPWD:=$(shell pwd)
all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:rm -f *.o
endif
Makefile(交叉编译):
CC = $(CROSS_COMPILE)gcc
ifneq ($(KERNELRELEASE),)obj-m:=scull1.o
else#KERNELDIR:=/lib/modules/$(shell uname -r)/buildKERNELDIR:=~/linuxPWD:=$(shell pwd)
all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-
clean:rm -f *.o
endif
test.c
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#define SCULL_CMD1 1
#define SCULL_CMD2 2
#define SCULL_CMD3 3
int main(){int fd, retval;char W_buffer[26]={'a','b','\0'};char R_buffer[26];fd=open("/dev/scull1", O_RDWR);retval = ioctl(fd, SCULL_CMD1, 0);retval = write(fd, W_buffer, 26);printf("which command do u want?\n");int a;scanf("%d",&a);if(a==1){retval = ioctl(fd, SCULL_CMD1, 0);}else if(a==2)retval = ioctl(fd, SCULL_CMD2, 0);else retval = ioctl(fd, SCULL_CMD3, 0);
// retval = lseek(fd, 0, 0);retval = read(fd, R_buffer, 26);printf("read:%s",R_buffer);close(fd);return 0;
}
最简单的scull设备驱动相关推荐
- 一个简单字符型设备驱动及其测试
驱动对一些人来说很难,而对一些人来说很容易.窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间.对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要 ...
- linux 字符设备驱动测试,一个简单字符型设备驱动及其测试
驱动对一些人来说很难,而对一些人来说很容易.窃以为,理解简单设备驱动模型不难,深入理解并与Linux内核设计联系到一起需要花费时间.对于移植者来说,如何将自己自定义的模块天衣无缝放到内核中,是比较重要 ...
- [设备驱动] 最简单的内核设备驱动--字符驱动
[设备驱动] 最简单的内核设备驱动--字符驱动 概要: x86平台上(linux-2.6.34.14;Linux debian 3.2.0-3-686-pae)编写一个256字节的字符驱动程序.在/ ...
- 编写最简单的字符设备驱动
编写最简单的字符设备驱动 1 编写驱动代码 2 编写makefile 3 编译和加载驱动 4 编写应用程序测试驱动 参考文章: linux驱动开发第1讲:带你编写一个最简单的字符设备驱动 linux驱 ...
- 通过内存模拟硬盘实现一个简单的块设备驱动
本文的主要工作是通过硬盘来模拟内存,按照块设备驱动编程的框架实现一个简单的块设备驱动程序. 一.前期的准备工作 1.基本开发环境 Linux内核版本:Linux-3.4.10 开发板 : JZ2440 ...
- Linux设备驱动——第三章字符驱动
当对幸福的憧憬过于急切,那痛苦就在人的心灵深处升起.--加缪 本章的目的是编写一个完整的字符设备驱动.我们开发一个字符驱动是因为这一类适合大部分简单的硬件设备.字符驱动也比块驱动易于理解.本章的最终目 ...
- Linux设备驱动编程第三版-笔记
第1章 设备驱动简介 1.1 驱动程序的角色 机制:提供什么能力. 策略:如何使用这些能力. 1.2. 划分内核 内核的角色可以划分: 一:进程管理 二:内存管理 三:文件系统 四:设备控制 ...
- linux设备驱动第五篇:驱动中的并发与竟态
目录[-] 综述 信号量与互斥锁 Completions 机制 自旋锁 其他的一些选择 不加锁算法 原子变量与位操作 seqlock(顺序锁) 读取-拷贝-更新(RCU) 小结 综述 在上一篇介绍了l ...
- Linux字符设备驱动框架
字符设备是Linux三大设备之一(另外两种是块设备,网络设备),字符设备就是字节流形式通讯的I/O设备,绝大部分设备都是字符设备,常见的字符设备包括鼠标.键盘.显示器.串口等等,当我们执行ls -l ...
最新文章
- Java学习总结:30
- Spring定时器--时间设置规则
- python飞机大战源代码-制作python程序windows安装包(飞机大战源码)
- 多线路虚拟主机解决方案
- async js 返回值_JS异步编程 | Async / Await / Generator 实现原理解析
- 数组做参数_C语言进阶之路:函数—数组参数!
- HTML文件可通过www进行传输,使用 zssh 进行 Zmodem 文件传输
- golang web php,golang 适合做web开发吗
- jquery getjson php,jquery中调用php json函数的方法分享
- 高通骁龙712移动平台正式发布!整体性能提升10%
- Spring bean注入方式
- Input禁用文本框
- spss常态检验_科学网—如何在SPSS中进行正态分布检验?
- 如何用photoshop做24色环_photoshop制作漂亮色环的教程(2)
- p2p通信原理及实现
- 2020 零基础 Vue综合应用 教开发音乐播放器—悦听(激发编程乐趣)【整理+源码】
- 图片文字翻译的软件有哪些?快收藏这几款实用的软件
- JS 获取当前日期时间/时间日期格式化(时间戳 转 yyyy-MM-dd HH:mm:ss)
- 正则^ [A-Za-z_][A-Za-z_0-9]*integer类型——学JAVA前一定要搞懂的最基本的东西(2)
- TensorFlow Lite编译Android so库
热门文章
- Linux虚拟机上JDK MYSQL TOMCAT REDIS安装
- mysql必了解的日志和备份
- 用curl post 调用接口
- github发起PR(pull request)的教程以及常见操作
- 巴菲特投资理念是什么?投资案例有哪些?《巴菲特估值逻辑-20个投资案例深入复盘》
- vue中$root的用法
- MikroTik RouterOS路由搭建的虚拟专用网络PPTP分支连不上
- 自然人电子税务局(扣缴端)地区选择为灰色解决办法
- matlab图像类型转换以及uint8、double、im2double、im2uint8和mat2gray等说明
- python的五个特点