今天做了Linux课设关于设备驱动的实验。
实验采用模块的方法编写一个可以进行简单读写的字符设备驱动,该设备可以存储一定长的字符串,写入设备即可以将字符串存入设备,读出即可以获取该字符串,并编写了测试程序对其测试。
(1)首先新建并编写了字符驱动设备chardev.c文件,文件代码如下:
/*
* 创建一个字符设备(读写)
*/
/* 必要的头文件,内核模块标准头文件 */
#include<linux/init.h>
#include<linux/kernel.h> /*内核工作*/
#include<linux/slab.h>/**/
#include<linux/vmalloc.h>
#include<linux/module.h> /*明确指定是模块*/
#include<linux/moduleparam.h>
/*对于字符设备*/
#include<linux/fs.h> /*字符设备定义*/
#include<linux/cdev.h>
#include<asm/system.h>
#include<asm/uaccess.h>
MODULE_AUTHOR("author");
MODULE_LICENSE("GPL");
struct char_dev *char_device;
int dev_major=0;
int dev_minor=0;
module_param(dev_major,int,S_IRUGO);
module_param(dev_minor,int,S_IRUGO);
//设备存储区的指针
char *p_mem=NULL;
//设备存储区的大小
long len=1000;
//表示设备的数据结构
struct char_dev
{
char * data; // 模块中的数据
long len; // 数据长度
struct cdev cdev; //Linux 字符设备结构,由系统定义
};
int char_open(struct inode *inode,struct file *filp)
{
struct char_dev *dev;
//从inode 获取设备结构体
dev=container_of(inode->i_cdev,struct char_dev,cdev);
//赋值给file 结构体
filp->private_data=dev;
return 0;
}
//读时将调用的函数
static ssize_t char_read(struct file * filp, char __user *buf, size_t count, loff_t *offset)
{
char * buffer = (char *)filp->private_data;
if(copy_to_user(buf, buffer, count))
{
printk("copy_to_user error/n");
return -EFAULT;
}
printk("You are using the read function!");
return count ;
}
//参数定义和char_read 类似
static ssize_t char_write(struct file * filp, const char __user *buf, size_t count, loff_t *offset)
{
char * buffer = (char *)filp->private_data;
//printk("new %p/n", buffer);
if(copy_from_user(buffer, buf, count))
{
printk("copy_from_user error/n");
return -EFAULT;
}
return count;
}
//读写完毕后调用的函数
int char_release(struct inode *inode,struct file *filp)
{
return 0;
}
//定义设备节点文件的操作
static struct file_operations char_ops={
.owner = THIS_MODULE,
.open = char_open,
.read = char_read,
.write = char_write,
.release =char_release
};
//设备初始化时调用的函数,用于获取存储区内存空间
int memory_init(void)
{
p_mem=vmalloc(len*sizeof(char));
if(!p_mem)
{
printk(KERN_ALERT "error-memory_init/n");
return -1;
}
return 1;
}
//设备初始化
static int dev_init(void)
{
int result=0;
dev_t dev=0;
dev_t devno=0;
int err=0;
//获取存储区
//如果定义了主设备号
if(dev_major)
{
//则按照定义的设备号注册设备
dev=MKDEV(dev_major,dev_minor);
result=register_chrdev_region(dev,1,"char_dev");
}else{
//否则分配新的设备号
result=alloc_chrdev_region(&dev,dev_minor,1,"char_dev");
dev_major=MAJOR(dev);
}
if(result < 0)
{
printk(KERN_ALERT "can't get major %d/n",dev_major);
return result;
}
//返回主设备号
printk("the dev_major %d/n",dev_major);
//获取全局char_dev 结构体
char_device = kmalloc(sizeof(struct char_dev),GFP_KERNEL);
if(!char_device)
{
result=-ENOMEM;
return result;
}
memset(char_device,0,sizeof(struct char_dev));
//使用定义了的文件操作char_ops 初始化cdev
cdev_init(&char_device->cdev, &char_ops);
char_device->cdev.owner = THIS_MODULE;
char_device->cdev.ops = &char_ops;
//使用后区的设备号注册设备
devno=MKDEV(dev_major,dev_minor);
//添加此字符设备到系统
err=cdev_add(&char_device->cdev,devno,1);
char_device->data=p_mem;
char_device->len=len;
return 0;
}
//设备被移出时调用
static void dev_exit(void)
{
dev_t devno=0;
devno = MKDEV(dev_major,dev_minor);
cdev_del(&char_device->cdev);
kfree(char_device);
unregister_chrdev_region(devno,1);
}
//注册模块初始化和卸载函数
module_init(dev_init);
module_exit(dev_exit);
(2)然后编写相应的Makefile文件:
# Makefile
# If KERNELRELEASE is defined, we've been invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)
obj-m := chardev.o
# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
KERNELDIR ?= /lib/modules/`uname -r`/build
default:
make -C $(KERNELDIR) M=`pwd` modules
endif
(3)使用make 命令,生成驱动程序chardev.ko
(4)用root 挂载设备:
insmod chardev.ko
(5)在文件系统为其创建一个代表节点(建立设备文件)。
创建节点命令格式如下:
mknod /dev/<dev_name><type><major_number><minor_number>
例如(若主设备号为249):
mknod mychardev0 c 249 0
(6)修改属性:
chmod 666 mychardev*
(7)设备挂载后,就能够使用系统命令写入数据和读取数据啦~
如读操作:more mychardev0
(友情提示:mychardev0会很大,若想体验计算机编码的奇妙可尝试cat命令)
(8)编写测试程序test.cpp:
//test.cpp
#include<stdio.h>
#include<fcntl.h>
#include<stdlib.h>
#include<iostream>
using namespace std;
int main()
{
int fd;
char buffer_write[20] = "Hello World!";
char buffer_read[20] = "Hello China!";
fd=open("/home/Guest/dev/mychardev0", O_RDWR);
if(fd < 0)
{
cout<<"open dev error!/n";
exit(fd);
}
//向指定设备写入用户输入文本
cout<<"Please input the text:/n";
cin>>buffer_write;
write(fd, buffer_write, 20);
//输出设备中的内容
read(fd, buffer_read, 20);
cout<<"指定设备中的内容为:/n"<<buffer_read<<endl;
close(fd);
return 0;
}
代码功能:实现了设备的读数据与写数据操作,有以下几点值得注意:
①fd=open("/home/Guest/dev/mychardev0", O_RDWR);不能只看对应文件夹下是否有mychardev0设备,若打开不成功可能需要重新mknod一下。
②iostream不能直接写#include<iostream.h>,而要写:
#include<iostream>
using namespace std;
③关于read和write函数的第三个参数n:
其作用要追溯到copy_to_user和copy_from_user两个函数上,其值就表示要写入或读出的字符串大小(以字节为单位)。