linux 字符设置驱动 demo
Makefile
#!/bin/bash
obj-m += my_dev.o
my_dev-objs := dev_fifo.o
my_dev-$(CONFIG_MY_DEV_TEST) += my_dev_test.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD ?= $(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
clean:
make -C $(KDIR) M=$(PWD) clean
in_proc.h
#ifndef _DEV_FIFO_HEAD_H
#define _DEV_FIFO_HEAD_H
#define DEV_FIFO_TYPE 'k'
#define DEV_FIFO_CLEAN _IO(DEV_FIFO_TYPE,0x10)
#define DEV_FIFO_GETVALUE _IOR(DEV_FIFO_TYPE,0x11,int)
#define DEV_FIFO_SETVALUE _IOW(DEV_FIFO_TYPE,0x12,int)
#define DEV_FIFO_SUM _IOWR(DEV_FIFO_TYPE,0x12,int)
#define DEV_FIFO_DEC _IOWR(DEV_FIFO_TYPE,0x13,int)
#define DEV_SIZE 50
/* Ioctl custom data */
struct ioctl_data {
char signature[10];
int cmd;
int size;
int buf[32];
};
#endif
dev_fifo.c
#include
#include
#include
#include //函數指針存放
#include
#include
#include
#include
#include "in_proc.h"
//指定的主设备号
#define MAJOR_NUM 250
//注册自己的设备号
struct mycdev {
int len;
unsigned int buffer[50];
struct cdev cdev;
};
MODULE_LICENSE("GPL"); //許可說明
//设备号
static dev_t dev_num = {0};
//全局gcd
struct mycdev *gcd;
//设备类
struct class *cls;
//获得用户传递的数据,根据它来决定注册的设备个数
static int ndevices = 1;
module_param(ndevices, int, 0644);
MODULE_PARM_DESC(ndevices, "The number of devices for register.\n");
//打开设备
static int dev_fifo_open(struct inode *inode, struct file *file)
{
struct mycdev *cd;
//用struct file的文件私有数据指针保存struct mycdev结构体指针
cd = container_of(inode->i_cdev,struct mycdev,cdev); //该函数可以根据结构体成员获取结构体地址
file->private_data = cd;
printk( KERN_CRIT "open success!\n");
return 0;
}
//读设备
static ssize_t dev_fifo_read(struct file *file, char __user *ubuf, size_t size, loff_t *ppos)
{
int n ;
int ret;
unsigned int *kbuf;
struct mycdev *mycd = file->private_data;
//printk(KERN_CRIT "read *ppos : %lld\n",*ppos); //光标位置
if(*ppos == mycd->len)
return 0;
//请求大小 > buffer剩余的字节数 :读取实际记的字节数
if(size > mycd->len - *ppos)
n = mycd->len - *ppos;
else
n = size;
//从上一次文件位置指针的位置开始读取数据
kbuf = mycd->buffer+*ppos;
//拷贝数据到用户空间
ret = copy_to_user(ubuf,kbuf,n*sizeof(kbuf));
if(ret != 0)
return -EFAULT;
//更新文件位置指针的值
*ppos += n;
printk(KERN_CRIT "dev_fifo_read success!\n");
return n;
}
//写设备
static ssize_t dev_fifo_write(struct file *file, const char __user *ubuf, size_t size, loff_t *ppos)
{
int n;
int ret;
unsigned int *kbuf;
struct mycdev *mycd = file->private_data;
printk("write *ppos : %lld\n",*ppos);
//已经到达buffer尾部了
if(*ppos == sizeof(mycd->buffer))
return -1;
//请求大小 > buffer剩余的字节数(有多少空间就写多少数据)
if(size > sizeof(mycd->buffer) - *ppos)
n = sizeof(mycd->buffer) - *ppos;
else
n = size;
//从上一次文件位置指针的位置开始写入数据
kbuf = mycd->buffer + *ppos;
//拷贝数据到内核空间
ret = copy_from_user(kbuf, ubuf, n*sizeof(ubuf));
if(ret != 0)
return -EFAULT;
//更新文件位置指针的值
*ppos += n;
//更新dev_fifo.len
mycd->len += n;
printk("dev_fifo_write success!\n");
return n;
}
//linux 内核在2.6以后,已经废弃了ioctl函数指针结构,取而代之的是unlocked_ioctl
long dev_fifo_unlocked_ioctl(struct file *file, unsigned int cmd,unsigned long arg)
{
int ret = 0;
struct mycdev *mycd = file->private_data;
struct ioctl_data val; //定义结构体
printk( KERN_CRIT "in dev_fifo_ioctl,sucessful!\n");
if(_IOC_TYPE(cmd) != DEV_FIFO_TYPE) //检测命令
{
printk(KERN_CRIT "CMD ERROR\n");
return -EINVAL;
}
/*if(_IOC_NR(cmd) > DEV_FIFO_NR)
{
printk(KERN_CRIT "CMD 1NUMBER ERROR\n");
return -EINVAL;
}*/
switch(cmd){
case DEV_FIFO_CLEAN:
printk("CMD:CLEAN\n");
memset(mycd->buffer, 0, sizeof(mycd->buffer));
break;
case DEV_FIFO_SETVALUE:
printk("CMD:SETVALUE\n");
mycd->len = arg;
break;
case DEV_FIFO_GETVALUE:
printk("CMD:GETVALUE\n");
ret = put_user(mycd->len, (int *)arg);
break;
case DEV_FIFO_SUM: //求和
printk( KERN_CRIT "It is CMD:SUM!!!!\n");
//检查指针是否安全|获取arg传递地址
if(copy_from_user(&val,(struct ioctl_data*)arg,sizeof(struct ioctl_data))){
ret = -EFAULT;
goto RET;
}
//printk( KERN_CRIT "CMD:SUM!!!!!\n");
memset(mycd->buffer,0,DEV_SIZE); //清空区域
val.buf[1] = val.buf[0]+val.buf[2]; //计算结果
//printk(KERN_CRIT "result is %d\n",val.buf[1]);
memcpy(mycd->buffer,val.buf,sizeof(val.buf)*val.size);
/*for(i = 0;i < 50;i++)
printk( KERN_CRIT "%d\n",mycd->buffer[i]);*/
mycd->len = val.size;
file->f_pos = 0;
break;
case DEV_FIFO_DEC: //求差
printk(KERN_CRIT "It is CMD:DEC!!!!\n");
//检查指针是否安全|获取arg传递地址
if(copy_from_user(&val,(struct ioctl_data*)arg,sizeof(struct ioctl_data))){
ret = -EFAULT;
goto RET;
}
memset(mycd->buffer,0,DEV_SIZE); //清空区域
val.buf[1] = val.buf[0]-val.buf[2]; //计算结果
memcpy(mycd->buffer,val.buf,sizeof(val.buf)*val.size);
/*printk(KERN_CRIT"size is :%d\n",val.size);
for(i = 0;i < 50;i++)
printk( KERN_CRIT "%d\n",mycd->buffer[i]);*/
mycd->len = val.size;
file->f_pos = 0;
break;
default:
return -EFAULT; //错误指令
}
RET:
return ret;
}
//设备操作函数接口
static const struct file_operations fifo_operations = {
.owner = THIS_MODULE,
.open = dev_fifo_open,
.read = dev_fifo_read,
.write = dev_fifo_write,
.unlocked_ioctl = dev_fifo_unlocked_ioctl,
};
//模块入口
int __init dev_fifo_init(void)
{
int i = 0;
int n = 0;
int ret;
struct device *device;
gcd = kzalloc(ndevices * sizeof(struct mycdev), GFP_KERNEL); //内核内存正常分配,可能通过睡眠来等待,此函数等同于kmalloc()
if(!gcd){
return -ENOMEM; //內存溢出
}
//设备号 : 主设备号(12bit) | 次设备号(20bit)
dev_num = MKDEV(MAJOR_NUM, 0);
//静态注册设备号
ret = register_chrdev_region(dev_num,ndevices,"my_dev");
if(ret < 0){
//静态注册失败,进行动态注册设备号
ret = alloc_chrdev_region(&dev_num,0,ndevices,"my_dev");
if(ret < 0){
printk("Fail to register_chrdev_region\n");
goto err_register_chrdev_region;
}
}
//创建设备类
cls = class_create(THIS_MODULE, "my_dev");
if(IS_ERR(cls)){
ret = PTR_ERR(cls);
goto err_class_create;
}
printk("ndevices : %d\n",ndevices);
for(n = 0;n < ndevices;n ++){
//初始化字符设备
cdev_init(&gcd[n].cdev,&fifo_operations);
//添加设备到操作系统
ret = cdev_add(&gcd[n].cdev,dev_num + n,1);
if (ret < 0){
goto err_cdev_add;
}
//导出设备信息到用户空间(/sys/class/类名/设备名)
device = device_create(cls,NULL,dev_num + n,NULL,"my_dev%d",n);
if(IS_ERR(device)){
ret = PTR_ERR(device);
printk("Fail to device_create\n");
goto err_device_create;
}
}
printk("Register dev_fito to system,ok!\n");
return 0;
err_device_create:
//将已经导出的设备信息除去
for(i = 0;i < n;i ++){
device_destroy(cls,dev_num + i);
}
err_cdev_add:
//将已经添加的全部除去
for(i = 0;i < n;i ++)
{
cdev_del(&gcd[i].cdev);
}
err_class_create:
unregister_chrdev_region(dev_num, ndevices);
err_register_chrdev_region:
return ret;
}
void __exit dev_fifo_exit(void)
{
int i;
//删除sysfs文件系统中的设备
for(i = 0;i < ndevices;i ++){
device_destroy(cls,dev_num + i);
}
//删除系统中的设备类
class_destroy(cls);
//从系统中删除添加的字符设备
for(i = 0;i < ndevices;i ++){
cdev_del(&gcd[i].cdev);
}
//释放申请的设备号
unregister_chrdev_region(dev_num, ndevices);
}
module_init(dev_fifo_init);
module_exit(dev_fifo_exit);
app.c
#include
#include
#include
#include
#include
#include
#include
#include
#include "in_proc.h"
static void Hello_Panel(void) //操作提示界面
{
printf("Welcome to the panel!\n");
printf("eg:A +(-) B\n");
}
static int Char_prosess(char po) //符号读取
{
if(po == '+')
return 0;
else{
if(po == '-')
return 1;
else
return -1;
}
}
int main(int argc, const char* argv[])
{
int ret = 0,fd = 0,n = 0,Char_error = 2;
unsigned int Result_buf[20];
struct ioctl_data my_data;
my_data.size = argc-1;
Char_error = Char_prosess(*argv[2]);
printf("%d\n", Char_error);
for(n = 0;n < argc-1;n++){ //参数读取
my_data.buf[n] = atoi((argv[n+1]));
}
switch(Char_error){ //测试使用
case 0:
printf("right:+ \n");
break;
case 1:
printf("right:- \n");
break;
case -1:
printf("error!!!\n");
Hello_Panel();
return -1;
break;
}
fd = open("/dev/my_dev0",O_RDWR);
if(fd < 0){
perror("Fail to open");
return -1;
}
if(Char_error == 0) //加法
ioctl(fd,DEV_FIFO_SUM,&my_data);
else
ioctl(fd,DEV_FIFO_DEC,&my_data);
ret = read(fd,Result_buf,3*4);
if(Char_error == 0)
printf("%d + %d = %d\n",Result_buf[0],Result_buf[2],Result_buf[1]);
else
if(Char_error == 1)
printf("%d - %d = %d\n",Result_buf[0],Result_buf[2],Result_buf[1]);
close(fd);
return 0;
}
test.sh
#!/bin/bash -xe
make
sudo rmmod my_dev || echo no my_dev
sudo dmesg -C
sudo insmod my_dev.ko
dmesg
sudo ./a.out 1 + 2
测试效果
留一个bug
$ ./test.sh
+ make
make -C /lib/modules/4.15.4/build M=/home/higon/apple/project/driver/char_dev modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.4'
CC [M] /home/higon/apple/project/driver/char_dev/dev_fifo.o
LD [M] /home/higon/apple/project/driver/char_dev/my_dev.o
Building modules, stage 2.
MODPOST 1 modules
CC /home/higon/apple/project/driver/char_dev/my_dev.mod.o
LD [M] /home/higon/apple/project/driver/char_dev/my_dev.ko
make[1]: Leaving directory '/usr/src/linux-headers-4.15.4'
+ sudo rmmod my_dev
rmmod: ERROR: Module my_dev is not currently loaded
+ echo no my_dev
no my_dev
+ sudo dmesg -C
+ sudo insmod my_dev.ko
+ dmesg
[324765.918518] ndevices : 1
[324765.918585] Register dev_fito to system,ok!
+ sudo ./a.out 1 + 2
0
right:+
1 + 2 = 3
./test.sh: line 8: 11739 Segmentation fault (core dumped) sudo ./a.out 1 + 2
bug_fix 补丁
diff --git a/dev_fifo.c b/dev_fifo.c
index c35ad78..eb4904c 100644
--- a/dev_fifo.c
+++ b/dev_fifo.c
@@ -82,7 +82,7 @@ static ssize_t dev_fifo_read(struct file *file, char __user *ubuf, size_t size,
//从上一次文件位置指针的位置开始读取数据
kbuf = mycd->buffer+*ppos;
//拷贝数据到用户空间
- ret = copy_to_user(ubuf,kbuf,n*sizeof(kbuf));
+ ret = copy_to_user(ubuf,kbuf,n*sizeof(*kbuf));
if(ret != 0)
return -EFAULT;
@@ -180,7 +180,7 @@ long dev_fifo_unlocked_ioctl(struct file *file, unsigned int cmd,unsigned long a
memset(mycd->buffer,0,DEV_SIZE); //清空区域
val.buf[1] = val.buf[0]+val.buf[2]; //计算结果
//printk(KERN_CRIT "result is %d\n",val.buf[1]);
- memcpy(mycd->buffer,val.buf,sizeof(val.buf)*val.size);
+ memcpy(mycd->buffer,val.buf,sizeof(val.buf[0])*val.size);
/*for(i = 0;i < 50;i++)
printk( KERN_CRIT "%d\n",mycd->buffer[i]);*/
mycd->len = val.size;
测试结果
$ ./test.sh
+ make
make -C /lib/modules/4.15.4/build M=/home/higon/apple/project/driver/char_dev modules
make[1]: Entering directory '/usr/src/linux-headers-4.15.4'
Building modules, stage 2.
MODPOST 1 modules
make[1]: Leaving directory '/usr/src/linux-headers-4.15.4'
+ sudo rmmod my_dev
+ sudo dmesg -C
+ sudo insmod my_dev.ko
+ dmesg
[ 2670.041314] ndevices : 1
[ 2670.041394] Register dev_fito to system,ok!
+ sudo ./a.out 1 + 2
0
1
0
2
right:+
1 + 2 = 3
$ dmesg
[ 2670.041314] ndevices : 1
[ 2670.041394] Register dev_fito to system,ok!
[ 2670.050647] open success!
[ 2670.050656] in dev_fifo_ioctl,sucessful!
[ 2670.050659] It is CMD:SUM!!!!
[ 2670.050662] dev_fifo_read success!