linux driver char dev demo

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!

你可能感兴趣的:(linux driver char dev demo)