嵌入式Linux开发 2 | 编写基于虚拟硬件的驱动程序



编写

mkdir driver && cd driver

1. 简单驱动程序

说明:

  • 只有加载和卸载功能
  • 简单驱动程序hello_driver.c
    vim hello_driver.c
#include<linux/module.h>

//驱动程序初始化函数
 static int __init hello_init(void)
 {
     printk(KERN_INFO"welcome to Hello character driver!\n");
     return 0;
 }

//驱动程序退出函数
 void __exit hello_exit(void)
 {
     printk("Goodbye!Character driver is so easy!\n");
 }

//向系统登记函数
 module_init(hello_init);
 module_exit(hello_exit);

//遵守GPL开源协议
 MODULE_LICENSE("Dual BSD/GPL");
  • 驱动程序Makefile
    vim Makefile
obj-m:=hello_driver.o

KDIR:=/lib/modules/$(shell uname -r)/build
SRCPWD:=$(shell pwd)

all:
     make -C $(KDIR) M=$(SRCPWD) modules
clean:
     rm -f *.o *.mod.o *.mod.c *.symvers Mo* mo*
     echo
     ls -lh
cleanall:
     make clean
     rm -f *.ko
     echo
     ls -lh
insmod:
     insmod hello_driver.ko
     lsmod | more
rmmod:
     rmmod hello_driver

提示:

  • 注意Makefile格式中的Tab位置
  • 示例文件的云盘链接mwcj

2. 驱动程序进阶1

说明:

  • 增加修改设备属性功能
  • 源代码hello_driver.c
#include<linux/module.h>
#include<linux/fs.h>

#define DEVICE_NAME "hello"

static int demoMajor=0;

static long  hello_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    switch(cmd)
    {
        case 0:
            printk("command 0 is run!\n");
            break;
        case 1:
            printk("command 1 is run!\n");
            break;
        default:
            printk("not known command!\n");
            break;
    }
    return 0;
}

//向系统登记hello_ioctl函数
static struct file_operations hello_fops=
{
    owner:THIS_MODULE,
    unlocked_ioctl:hello_ioctl,
};

//驱动程序初始化函数
static int __init hello_init(void)
{
    demoMajor=register_chrdev(0,DEVICE_NAME,&hello_fops);
    if(demoMajor<0)
    {
        printk(KERN_NOTICE DEVICE_NAME"register failure\n");
        return demoMajor;
    }
    return 0;
}
//驱动程序退出函数
void __exit hello_exit(void)
{   
    if(demoMajor>0)
        unregister_chrdev(demoMajor,DEVICE_NAME);
}

//向系统登记函数
module_init(hello_init);
module_exit(hello_exit);

//遵守GPL开源协议
MODULE_LICENSE("Dual BSD/GPL");
  • Makefile不变
  • 编译
    make
  • 加载驱动
    make insmod
  • 查看注册设备
    more /proc/devices
    Enterd翻行

可以看到有hello,成功!!!

  • 使用设备
    由于本驱动程序没有使用次设备号,故建立设备文件时,次设备号的值并不重要,可以取有效范围的任意值,本次就取0
    cd /dev
    mknod hello c 242 0

  • 测试程序testHelloDevice.c
    vim testHelloDevice.c

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>

#define DEVICE_FILE "/dev/hello"

int main()
{
    int fd,cmd;

    //打开设备文件
    fd=open("/dev/hello",O_RDWR);
    if(fd<=0)
    {
        perror(DEVICE_FILE);
        exit(1);
    }

    //使用ioctl修改硬件属性
    for(cmd=0;cmd<=4;cmd++)
    {
        sleep(1);
        ioctl(fd,cmd,NULL);
    }

    //关闭设备文件
    close(fd);
    return 0;
}

gcc -o test testHelloDevice.c
./test
dmesg | tail

提示:

  • mknod命令格式:
    mkmod <设备文件名> <设备类型> <主设备号> <次设备号>
  • 为了方便管理,设备文件名与设备名相同
  • 设备类型为c(字符设备)或b(块设备)

参阅:


3. 驱动程序进阶2

说明:

  • 增加读写接口
  • 源代码hello_driver.c
    vim hello_driver.c
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/poll.h>
#include<linux/string.h>

#define DEVICE_NAME "hello"

//用于保存虚拟硬件返回给应用程序的字符串
static char drv_buff[6]="abcde";
//用于接收应用程序发送给虚拟硬件的字符串数据
static char data_from_user[1024];
//虚拟硬件返回给应用程序的字符串的格式标志
static unsigned char data_format=0;

static int demoMajor=0;

//硬件设备属性修改
static long  hello_ioctl(struct file *filp,unsigned int cmd,unsigned long arg)
{
    switch(cmd)
    {
        case 0:
            if(data_format!=0)
            {
                data_format=0;
                strcpy(drv_buff,"abcde");
            }
            break;
        case 1:
            if(data_format!=1)
            {
                data_format=1;
                strcpy(drv_buff,"ABCDE");
            }
            break;
        default:
            break;
    }
    return 0;
}

//读接口函数
static ssize_t hello_read(struct file *filp,char *buffer,size_t count,loff_t *ppos)
{
    int real_count,i;
    real_count=sizeof(drv_buff);
    i=copy_to_user(buffer,drv_buff,real_count);
    if(i<0)
        printk("copy_to_user() failure!\n");
    return(ssize_t)real_count;
}

//写接口函数
static ssize_t hello_write(struct file *filp,const char *buffer,size_t count,loff_t *ppos)
{
    int i;
    memset(data_from_user,'\0',sizeof(data_from_user));
    i=copy_from_user(data_from_user,buffer,count);
    if(i<0)
        printk("copy_from_user()failure!\n");
    printk(data_from_user);
    return(ssize_t)count;
}


//向系统注册ioctl、read、write函数
static struct file_operations hello_fops=
{
    owner:THIS_MODULE,
    unlocked_ioctl:hello_ioctl,
    read:hello_read,
    write:hello_write,
};

static int __init hello_init(void)
{
    demoMajor=register_chrdev(0,DEVICE_NAME,&hello_fops);
    if(demoMajor<0)
    {
        printk(KERN_NOTICE DEVICE_NAME"register failure\n");
        return demoMajor;
    }
    return 0;
}

void __exit hello_exit(void)
{   
    if(demoMajor>0)
        unregister_chrdev(demoMajor,DEVICE_NAME);
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("Dual BSD/GPL");
  • 重新编译
    make
  • 卸载旧版本
    make rmmod
  • 加载驱动
    make insmod

提醒:

  • 加载新驱动程序前记得卸载旧版本
  • 在系统硬件没有太大的改变的情况下,每次加载驱动程序动态获得的主设备号一般不会改变,即对应的设备文件仍可用,若主设备号改变,则需删除旧版本的设备文件,以新主设备号重建设备文件.
  • 测试程序testHelloDevice.c
    vim testHelloDevice.c
#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>

#define DEVICE_FILE "/dev/hello"
#define BUFFER_SIZE 1024

int main()
{
    int fd;
    char buff[BUFFER_SIZE];

    //打开设备文件
    fd=open("/dev/hello",O_RDWR);
    if(fd<=0)
    {
        perror(DEVICE_FILE);
        exit(1);
    }

    //读硬件设备默认状态下的数据
    sleep(1);
    memset(buff,'\0',BUFFER_SIZE);
    read(fd,buff,BUFFER_SIZE);
    printf("read a default string:%s\n",buff);

    //发送修改硬件属性的命令1
    sleep(1);
    ioctl(fd,1,NULL);
    
    //读命令1后硬件设备的数据
    memset(buff,'\0',BUFFER_SIZE);
    read(fd,buff,BUFFER_SIZE);
    printf("read a string after cmd 1:%s\n",buff);
    
    //发送修改硬件属性的命令0
    sleep(1);
    ioctl(fd,0,NULL); 

    //读命令0后硬件设备的数据
    memset(buff,'\0',BUFFER_SIZE);
    read(fd,buff,BUFFER_SIZE);
    printf("read a string after cmd 0:%s\n",buff);

    //往硬件设备写入字符串
    memset(buff,'\0',BUFFER_SIZE);
    strcpy(buff,"I love Linux!");
    printf("write string \"%s\" to the device\n",buff);
    write(fd,buff,strlen(buff));

    //关闭设备文件
    close(fd);
    return 0;
}
  • 编译运行
    gcc -o test testHelloDevice.c
    ./test
  • 查看日志
    dmesg | tail

调试

  • 使用dmesg命令查看系统消息缓冲区中prink()的输出。当缓冲区已满的时候才真正写入日志文件,因此通过日志文件有时候会看到消息,有时候看不到消息.
    dmesg | tail

    tail -f /var/log/messages

Q&A


1. *** /lib/modules/3.10.0-957.21.3.el7.x86_64/build: 没有那个文件或目录。

  • 调试-查看内核版本信息
    uname -r
  • 查看系统内核源码
    cd /lib/modules/ && ls
  • 进入当前使用的内核目录

rm build
ln -s /usr/src/kernels/3.10.0-957.21.3.el7.x86_64 /lib/modules/3.10.0-957.21.3.el7.x86_64/build

  • 编译
    make

成功!!!

参阅:


2. disagrees about version of symbol modulelay

  • 加载驱动程序出错
    insmod hello_driver.ko
  • 调试-查看出错的日志信息
    cat /var/log/messages | tail

  • 调试-查看/usr/src/kernels下内核源码

发现不存在内核开发包
yum install kernel-devel -y
再次查看结果

  • 重新生成软链接编译后,开始加载
    insmod hello_driver.ko
    lsmod | more

没有报错,且系统内核已加载的模块中有hello_driver,说明成功!!!

参阅:

3. 错误:初始值设定项里有未知的字段‘ioctl’

  • 查看系统内核
    uname -r
  • 查看file_operations
    vim /usr/src/kernels/$(uname -r)/include/linux/fs.h
    /_ioctl

可以看出在当前系统内核中fs.h中不存在
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

  • 对应自己系统中的fs.h文件修改驱动程序中ioctl部分

提示:


更新中......


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,874评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,102评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,676评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,911评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,937评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,935评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,860评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,660评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,113评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,363评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,506评论 1 346
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,238评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,861评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,486评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,674评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,513评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,426评论 2 352