【学习笔记】嵌入式linux驱动——驱动模型1——字符驱动模型

在学习字符驱动模型之前,一些关于环境搭建,与最基本的linux驱动框架建议参考这篇文章:

环境搭建、linux驱动基本框架

以下主要包括字符驱动和上位机程序部分

(一)字符驱动模型

关于字符驱动模型,简单提点一下,以实战为主。

驱动原理图:

从结构上来看,字符驱动分为两部分:

第一部分,标准的驱动入口

有一般linux驱动的标准特点,整体入口在module_init、module_exit:

static int memdev_init(void){
    int ret = -1;

    /*动态分配设备号*/
    ret = alloc_chrdev_region(&my_dev.devno,0,1,"my_led_memdev");
    if (ret >= 0){
        cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/
        cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/
	printk(KERN_ALERT "init_success\r\n");
    }
    else
    {
	printk(KERN_ALERT "init_error\r\n");
    }

    return ret;   
}

static void memdev_exit(void){
    cdev_del(&my_dev.cdev);
    unregister_chrdev_region(my_dev.devno,1);
    printk(KERN_ALERT "exit_my_led\r\n");
}

module_init(memdev_init);
module_exit(memdev_exit);

以上以module_init、module_exit作为入口,把memdev_init等两个函数分别传进去作为真实调用的初始化函数。关于内部一些添加设备和动态、静态分配设备号的方法及其原理不再赘述。

第二部分,设备描述符、设备号

设备描述符:struct cdev

设备号:dev_t

都需要单独定义。

设备描述符struct cdev和设备号dev_t在第一部分中的设备初始化函数里面使用,用于添加字符设备:

cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/

实际上设备号是结构体struct cdev(设备描述符)内的一个成员,外部定义的设备号在alloc_chrdev_region函数里面动态分配后,再通过cden_add传回cdev内部那个设备号里面。差不多有一些中间量的意思。

此外还需要定义、开辟一个全局内存。例程里面把他们全都放进一个结构体里面:

#define MEM_SIZE 1024

struct mem_dev{
    struct cdev cdev;
    int mem[MEM_SIZE];//全局内存4k
    dev_t devno;
};

struct mem_dev my_dev;

第三部分,操作函数集——file_operations结构体

简单的操作函数集定义:

const struct file_operations mem_ops = {
    .llseek = mem_llseek,
    .open = mem_open,
    .read = mem_read,
    .write = mem_write,
    .release = mem_release,
};

当然,完整的操作函数集可不止这么一点,但初学入门这点就足够了。

关于操作函数集,这里需要提一个概念,字符驱动他的机制是这样的:应用程序、设备驱动程序之间,是隔着一个字符设备文件的,你使用read、write这些函数对下位进行各种协议(uart等)通讯时,你读的是这个字符设备文件,一般在/dev/xxx这里。但是内核接收到你这个read、write函数会自动调用操作函数集里面指向的函数,比如上面这个就是指向men_write。

应用程序——>字符设备文件——>设备驱动程序

open:打开文件

release:关闭文件

read:读文件

write:写文件

llseek:移动读写文件指针

这部分剩下的就是对各个文件操作函数进行实例化编写。

第二部分、第三部分耦合

显然,第二部分和第三部分被明显区分开来了,目前来说没有发现耦合关系,那么实际上他么是怎么进行耦合的呢?

可以在模块的初始化memdev_init函数处看到:

cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/

cdev_init函数把他们耦合起来了!

编译

对于Ubuntu18.04 LTS来说,默认已经有modules了,不需要额外搭建环境,makefile如下:

obj-m:=my_led.o

KERNELDIR:=/lib/modules/4.15.0-45-generic/build

PWD:=$(shell pwd)

modules:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
        rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.bak *.orde

安装模块

sudo insmod my_led.ko
lsmod  #查看是否加载好
cat /proc/devices    #查看自动分配的号码
sudo mknod /dev/my_led_memdev0 c 243 0 #其中243是上面自动分配的号码

控制台通常是打印不出信息,要从这里查看系统log:

sudo tail /var/log/syslog

卸载模块

sudo rm /dev/my_led_memdev0
sudo rmmod my_led

一些问题

在Ubuntu18.04 LTS下面编译其内核对应的字符驱动,会出现copy_to_user和copy_from_user两个函数的错误提示,但在交叉编译nanopc t4的驱动程序并没有。

对于Ubuntu18.04 LTS我把copy_to_user和copy_from_user改成了raw_copy_to_user和raw_copy_from_user,运行正常。看样子是这个内核把这两个函数改了?

(二)应用程序

一开始我在网络上copy了一份例程,略作修改,但无论怎么运行都没办法达到预期期望:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd=0;
    int val=1;
    char buf_write[5]="only";
    char buf_my[10]={0};

    fd=open("/dev/my_led_memdev0",O_RDWR);
    if(fd<0)
        printf("can't open!\r\n");
    else
    {
	write(fd, buf_write, 4);
	read(fd, buf_my, 4);
	printf("%s", buf_my);
    }
    printf("end\r\n");

    return 0;
}

后来仔细探索,发现write和read的读写,是从文件指针loff_t开始的,write调用以后,loff_t文件指针就指向了最后一位,直接读取是没办法读到刚才写进去的数据。

我不知道这份例程的作者有没有运行过他的代码,甚至很多例程都是这样写的。我不清楚是不是驱动函数的策略不同或者是内核不一样的缘故,但其中绝对有不少实在误人子弟!

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fd=0;
    int val=1;
    char buf_write[5]="wond";
    char buf_my[10]={0};

    fd=open("/dev/my_led_memdev0",O_RDWR);
    if(fd<0)
        printf("can't open!\r\n");
    else
    {
	lseek(fd, 0, SEEK_SET);
	write(fd, buf_write, 4);
	lseek(fd, 0, SEEK_SET);
	read(fd, buf_my, 8);
	printf("read:%s\r\n", buf_my);
    }
    printf("end\r\n");

    return 0;

}

附上驱动代码:

#include<linux/module.h>
#include<linux/init.h>
#include<linux/cdev.h>
#include<linux/fs.h>
#include<asm/uaccess.h>

#define MEM_SIZE 1024

MODULE_LICENSE("GPL");

struct mem_dev{
    struct cdev cdev;
    int mem[MEM_SIZE];//全局内存4k
    dev_t devno;
};

struct mem_dev my_dev;

/*打开设备*/
int mem_open(struct inode *inode, struct file *filp){
    int num = MINOR(inode->i_rdev);/*获取次设备号*/
    printk(KERN_ALERT "open:my_led_memdev\r\n");

    if(num == 0){/*判断为那个设备*/
        filp -> private_data = my_dev.mem;/*将设备结构体指针复制给文件私有数据指针*/
    }
    return 0;
}
/*文件关闭函数*/
int mem_release(struct inode *inode, struct file *filp){
      return 0;
      printk(KERN_ALERT "close:my_led_memdev\r\n");
}

static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){
    int * pbase = filp -> private_data;/*获取数据地址*/
    unsigned long p = *ppos;/*读的偏移*/
    unsigned int count = size;/*读数据的大小*/
    int ret = 0;
    
    printk(KERN_ALERT "read:ready to my_led_memdev\r\n");
    
    if(p >= MEM_SIZE)/*合法性判断*/
        return 0;
    if(count > MEM_SIZE - p)/*读取大小修正*/
        count = MEM_SIZE - p;

    if(raw_copy_to_user(buf,pbase + p,size)){
       ret = - EFAULT;
    }else{
        *ppos += count;
        ret = count;
    }
    printk(KERN_ALERT "read:my_led_memdev\r\n");

    return ret;
}

static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos){
    unsigned long p = *ppos;
    unsigned int count = size;
    int ret = 0;
    int *pbase = filp -> private_data;

    if(p >= MEM_SIZE)
        return 0;
    if(count > MEM_SIZE - p)
        count = MEM_SIZE - p;

    if(raw_copy_from_user(pbase + p,buf,count)){
       ret = - EFAULT;
    }else{
        *ppos += count;
        ret = count;
    }
    printk(KERN_ALERT "write:my_led_memdev\r\n");
    return ret;
}

/*seek文件定位函数*/
static loff_t mem_llseek(struct file *filp, loff_t offset, int whence){ 

    loff_t newpos;

    switch(whence) {
        case SEEK_SET:/*从文件头开始定位*/
            newpos = offset;
            break;
        case SEEK_CUR:/*从当前位置开始定位*/ 
            newpos = filp->f_pos + offset;
            break;
        case SEEK_END: 
            newpos = MEM_SIZE * sizeof(int)-1 + offset;/*从文件尾开始定位*/
            break;
        default:
            return -EINVAL;
    }

     if ((newpos<0) || (newpos>MEM_SIZE * sizeof(int)))/*检查文件指针移动后位置是否正确*/
         return -EINVAL;

     printk(KERN_ALERT "llseek:my_led_memdev\r\n");
     
     filp->f_pos = newpos;
     return newpos;

}

const struct file_operations mem_ops = {
    .llseek = mem_llseek,
    .open = mem_open,
    .read = mem_read,
    .write = mem_write,
    .release = mem_release,
};

static int memdev_init(void){
    int ret = -1;

    /*动态分配设备号*/
    ret = alloc_chrdev_region(&my_dev.devno,0,1,"my_led_memdev");
    if (ret >= 0){
        cdev_init(&my_dev.cdev,&mem_ops);/*初始化字符设备*/
        cdev_add(&my_dev.cdev,my_dev.devno,1);/*添加字符设备*/
	printk(KERN_ALERT "init_success\r\n");
    }
    else
    {
	printk(KERN_ALERT "init_error\r\n");
    }

    return ret;   
}

static void memdev_exit(void){
    cdev_del(&my_dev.cdev);
    unregister_chrdev_region(my_dev.devno,1);
    printk(KERN_ALERT "exit_my_led\r\n");
}

module_init(memdev_init);
module_exit(memdev_exit);

好好学习,天天向上

原创文章 26 获赞 9 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_34917736/article/details/87211356