最简单的scull设备驱动

一. 实验题目:设备管理与驱动编程

该题目是写一个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 3

int 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
else
	KERNELDIR:=/lib/modules/$(shell uname -r)/build
	PWD:=$(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)/build
	KERNELDIR:=~/linux
	PWD:=$(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;
}
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
LINUX设备驱动第三版_ 前言 第一章 设备驱动程序简介 设备驱动程序的作用 内核功能划分 设备和模块的分类 安全问题 版本编号 许可证条款 加入内核开发社团 本书概要 第二章 构造和运行模块 设置测试系统 Hello World模块 核心模块与应用程序的对比 编译和装载 内核符号表 预备知识 初始化和关闭 模块参数 在用户空间编写驱动程序 快速参考 第三章 字符设备驱动程序 scull的设计 主设备号和次设备号 一些重要的数据结构 字符设备的注册 open和release scull的内存使用 read和write 试试新设备 快速参考 第四章 调试技术 内核中的调试支持 通过打印调试 通过查询调试 通过监视调试 调试系统故障 调试器和相关工具 第五章 并发和竞态 scull的缺陷 并发及其管理 信号量和互斥体 completion 自旋锁 锁陷阱 除了锁之外的办法 快速参考 第六章 高级字符驱动程序操作 ioctl 阻塞型I/O poll和select 异步通知 定位设备 设备文件的访问控制 快速参考 第七章 时间、延迟及延缓操作 度量时间差 获取当前时间 延迟执行 内核定时器 tasklet 工作队列 快速参考 第八章 分配内存 kmalloc函数的内幕 后备高速缓存 get_free_page和相关函数 vmalloc及其辅助函数 per-CPU变量 获取大的缓冲区 快速参考 第九章 与硬件通信 I/O端口和I/O内存 使用I/O端口 I/O端口示例 使用I/O内存 快速参考 第十章 中断处理 准备并口 安装中断处理例程 实现中断处理例程 顶半部和底半部 中断共享 中断驱动的I/O 快速参考 第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 SBus NuBus 外部总线 快速参考 第十三章 USB驱动程序 USB设备基础 USB和Sysfs USB urb 编写USB驱动程序 不使用urb的USB传输 快速参考 第十四章 Linux设备模型 kobject、kset和子系统 低层sysfs操作 热插拔事件的产生 总线、设备驱动程序 类 各环节的整合 热插拔 处理固件 快速索引 第十五章 内存映射和DMA Linux的内存管理 mmap设备操作 执行直接I/O访问 直接内存访问 快速参考 第十六章 块设备驱动程序 注册 块设备操作 请求处理 其他一些细节 快速参考 第十七章 网络驱动程序 snull设计 连接到内核 net_device结构细节 打开和关闭 数据包传输 数据包的接收 中断处理例程 不使用接收中断 链路状态的改变 套接字缓冲区 MAC 地址解析 定制 ioctl 命令 统计信息 组播 其他知识点详解 快速参考 第十八章 TTY驱动程序 小型TTY驱动程序 tty_driver函数指针 TTY线路设置 ioctls proc和sysfs对TTY设备的处理 tty_driver结构详解 tty_operations结构详解 tty_struct结构详解 快速参考 参考书目 9112405-1_o.jpg (85.53 KB, 下载次数: 50)

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值