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 <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>        //函數指針存放
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/kernel.h>
#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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#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!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 一、Python简介和环境搭建以及pip的安装 4课时实验课主要内容 【Python简介】: Python 是一个...
    _小老虎_阅读 5,718评论 0 10
  • Linux print system linux中的调试方法有很多种,但我们最常用的也是最关键的调试工具应该就是使...
    Creator_Ly阅读 1,922评论 0 4
  • 简明 VIM 练级攻略搜索文件夹下是否包含某个字符串systemctl鸟哥的 Linux 私房菜学习总结(超赞!!...
    _fhs阅读 303评论 0 0
  • 一、目录结构 目录说明/binBinary的缩写,这个目录存放最经常使用的命令;/sbins就是Super Use...
    CJ21阅读 1,591评论 0 15
  • 一、上堂回顾 安装git​ sudo apt-get install git创建版本库【本地版本库】​ 普通目录,...
    WenErone阅读 387评论 0 0