一. 实验题目:设备管理与驱动编程
该题目是写一个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;
}