美文网首页工作生活
嵌入式Linux开发 2 | 编写基于虚拟硬件的驱动程序

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

作者: Ricsy | 来源:发表于2019-07-01 19:23 被阅读0次


编写

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部分

提示:


更新中......


相关文章

网友评论

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

    本文链接:https://www.haomeiwen.com/subject/vtpncctx.html