最简单的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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值