IT虾米网

IMX6ull_Linux驱动编写(1)

haluo1 2022年11月07日 编程语言 82 0

IMX6ull_Linux驱动编写-1

linux系统下程序编写架构分析

前言

Linux属于一个不断发展且较为成熟的系统体系,既然它自身便是一种体系,更加需要我们按照其系统的规则进行开发与维护,所以在学习过程中更多的是完成框架与改写驱动的过程。

整体分析

此处略去了linux环境配置,uboot,linux移植等等步骤,直接进入驱动编写环节。最开始我们开发一般是在裸机上进行,随着设备的主频变快,资源加多,需要我们进行手工管理的资源也更加庞杂,这时便出现了基于MCU的操作系统,如ucOS、FreeRTOS等等。

在这里插入图片描述

对于linux系统来说,主要分为用户态与内核态,内核Kernel帮助我们对处理器的任务调度、资源分配进行自主运行,用户态可以由用户自由操作。

如同我们用windows一样,我们只用点击桌面图标,便能进入应用程序,其中调用显卡、声卡、CPU的具体操作都不用我们亲自进行,而是系统帮助我们进行协调。

在linux下,内核态为用户态提供了大量的API,用户态程序可以调用这些API向内核下达指令,内核态收到指令后,会调用某个具体外设的驱动程序(设置具体的GPIO高低电平等),驱动该外设正常运行。

根据此执行流程便衍生出了应用程序编写与驱动程序编写两个方向。

驱动大致框架介绍

本次主要分析字符型设备的驱动框架,因为所开发驱动程序是运行在MCU(IMX6ULL)上,因此linux驱动程序需要依托MCU运行的linux内核文件进行开发,因此本次选择Ubuntu配置联合交叉编译器作为开发环境,选择vscode作为开发环境。

驱动程序可以作为内核的一部分编译进zImage,也可以挂载。一般开发流程为:驱动测试无误后,便可直接写进内核,所以这里先使用挂载方式进行学习。编译好的linux内核文件文件下有一个文件夹drivers,该文件夹里面为linux自带驱动,也就是我们开发需要参照的模板。

在这里插入图片描述

使用vscode打开drivers文件,这里打开fmc-chrdev.c文件进行分析,由于只关心基本框架,所以略去了函数内容。

/* 
 * Copyright (C) 2012 CERN (www.cern.ch) 
一些………………………… 
*/ 
 
#include <linux/module.h> 
#include <linux/init.h> 
#include <linux/list.h> 
#include <linux/slab.h> 
#include <linux/fs.h> 
#include <linux/miscdevice.h> 
#include <linux/spinlock.h> 
#include <linux/fmc.h> 
#include <linux/uaccess.h>            /* 头文件 */ 
 
static LIST_HEAD(fc_devices); 
static DEFINE_SPINLOCK(fc_lock); 
 
 
 
/* at open time, we must identify our device */ 
static int fc_open(struct inode *ino, struct file *f) 
{ 
 
} 
 
static int fc_release(struct inode *ino, struct file *f) 
{ 
 
} 
 
/* read and write are simple after the default llseek has been used */ 
static ssize_t fc_read(struct file *f, char __user *buf, size_t count, 
		       loff_t *offp) 
{ 
 
} 
 
static ssize_t fc_write(struct file *f, const char __user *buf, size_t count, 
			loff_t *offp) 
{ 
	 
} 
 
static const struct file_operations fc_fops = { 
	.owner = THIS_MODULE, 
	.open = fc_open, 
	.release = fc_release, 
	.llseek = generic_file_llseek, 
	.read = fc_read, 
	.write = fc_write, 
}; 
 
 
 
 
static struct fmc_driver fc_drv = { 
	.version = FMC_VERSION, 
	.driver.name = KBUILD_MODNAME, 
	.probe = fc_probe, 
	.remove = fc_remove, 
	/* no table: we want to match everything */ 
}; 
 
 
 
 
static int fc_probe(struct fmc_device *fmc) 
{ 
	int ret; 
	int index = 0; 
 
} 
 
static int fc_remove(struct fmc_device *fmc) 
{ 
	 
} 
 
 
static int fc_init(void)  /*模块初始化函数*/ 
{ 
	int ret; 
 
	ret = fmc_driver_register(&fc_drv); 
	return ret; 
} 
 
static void fc_exit(void)  /*模块退出函数*/ 
{ 
	fmc_driver_unregister(&fc_drv); 
} 
 
module_init(fc_init); 
module_exit(fc_exit);  /*向kernel申请模块初始化和退出函数*/ 
 
MODULE_LICENSE("GPL"); 

首先需要调用**module_init();module_exit();**向内核申请模块驱动初始化和退出函数,在初始化函数和退出函数中需要完成对字符驱动的注册(register)与(unregister)。而对驱动的读写需要借助结构体file_operations fc_fops进行完成,结构体内建立了驱动的读写函数可与用户态的App进行数据交互。

根据现有驱动程序可以构建如下所示的驱动大致框架:

include <linux/types.h> 
#include <linux/kernel.h> 
#include <linux/delay.h> 
#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/fs.h> 
 
#define CHRDEVBASE_MAJOR 199 			 /*定义设备主设备号*/ 
#define CHARDEVBASE_NAME "chrdevbase"	/*定义设备名称*/ 
 
/*驱动open函数*/ 
static int chrdevbase_open(struct inode * inode, struct file * file)   
{ 
    printk("kernerl:open_func:chrdevbase_open 
"); 
    return 0; 
} 
 
 /*驱动release函数(关闭)*/ 
static int chrdevbase_release(struct inode * inode, struct file * file)  
{ 
    printk("kernerl:release_func:chrdevbase_release 
"); 
    return 0; 
} 
 
 /*驱动write函数(写)*/ 
static ssize_t chrdevbase_write(struct file * file, const char __user * buf, 
		        size_t count, loff_t *ppos) 
{ 
    printk("kernerl:write_func:chrdevbase_write 
");     
    return 0;         
} 
 
 /*驱动read函数(读)*/ 
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off) 
{ 
    printk("kernerl:readfunc:chrdevbase_read 
");   
    return 0; 
} 
 
/*文件操作结构体*/ 
/*.owner为chrdevbase_fops.owner的简写*/ 
/*这里是为了实现只给结构体中某些成员进行幅值*/ 
static struct file_operations chrdevbase_fops = 
{ 
    .owner = THIS_MODULE, 
    .open  = chrdevbase_open, 
    .release = chrdevbase_release, 
    .write  = chrdevbase_write, 
    .read   = chrdevbase_read,            
}; 
 
 
/*模块初始化函数*/ 
static int __init chrdevbase_init(void) 
{ 
    printk("printk: chrdevbase is init 
"); 
    /*参数为:主设备号,设备名称,设备文件操作结构体*/ 
    register_chrdev(CHRDEVBASE_MAJOR,CHARDEVBASE_NAME,&chrdevbase_fops); 
 
    return 0; 
} 
 
/*模块退出函数*/ 
void __exit chrdevbase_exit(void) 
{ 
    printk("printk: chrdevbase is exit 
"); 
	/*参数为:主设备号,设备名称*/ 
    unregister_chrdev(CHRDEVBASE_MAJOR, CHARDEVBASE_NAME); 
} 
 
 
/*模块入口函数*/ 
module_init(chrdevbase_init); 
module_exit(chrdevbase_exit); 
 
MODULE_LICENSE("GPL"); 

如下图所示,便是驱动的整体编写的流程,这里只是叙述了编写的流程,不代表系统的执行流程便是如此。需要注意的是file_operations结构体含有多个操作函数,定义时需要按需求进行实现(具体参见fs.h文件[1588行])。

在这里插入图片描述

下面代码段即是file_operations结构体的具体定义,可以看到结构体中含有大量的文件操作函数指针,因此在.c文件进行创建具体的实例时,需要将==函数地址(函数名)==传递给函数指针

struct file_operations { 
	struct module *owner; 
	loff_t (*llseek) (struct file *, loff_t, int); 
	ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 
	ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 
	ssize_t (*read_iter) (struct kiocb *, struct iov_iter *); 
	ssize_t (*write_iter) (struct kiocb *, struct iov_iter *); 
	int (*iterate) (struct file *, struct dir_context *); 
	unsigned int (*poll) (struct file *, struct poll_table_struct *); 
	long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 
	long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 
	int (*mmap) (struct file *, struct vm_area_struct *); 
	int (*mremap)(struct file *, struct vm_area_struct *); 
	int (*open) (struct inode *, struct file *); 
	int (*flush) (struct file *, fl_owner_t id); 
	int (*release) (struct inode *, struct file *); 
	int (*fsync) (struct file *, loff_t, loff_t, int datasync); 
	int (*aio_fsync) (struct kiocb *, int datasync); 
	int (*fasync) (int, struct file *, int); 
	int (*lock) (struct file *, int, struct file_lock *); 
	ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 
	unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 
	int (*check_flags)(int); 
	int (*flock) (struct file *, int, struct file_lock *); 
	ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 
	ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 
	int (*setlease)(struct file *, long, struct file_lock **, void **); 
	long (*fallocate)(struct file *file, int mode, loff_t offset, 
			  loff_t len); 
	void (*show_fdinfo)(struct seq_file *m, struct file *f); 
#ifndef CONFIG_MMU 
	unsigned (*mmap_capabilities)(struct file *); 
#endif 
}; 

为了调用底层驱动程序,需要编写应用程序app进行测试(chrdevapp.c):

#include <stdio.h> 
#include <sys/types.h> 
#include <sys/stat.h> 
#include <fcntl.h> 
#include <unistd.h> 
 
 
char writebuf[20] = "hello word!"; 
 
int main(int argc,char *argv[]) /* argc 为输入参数个数  argv为输出参数内容*/ 
{ 
    int fd; 
    char *filename; 
    char readbuf[20]; 
 
    filename = argv[1]; 
    printf("filename:%s 
",filename);   
    sleep(2); 
 
    printf("app:run open 
"); 
    sleep(1); 
    if((fd = open(filename,O_RDWR))<0) 
    { 
        printf("error open fd :%d 
",fd); 
        return -1; 
    } 
    sleep(2); 
 
    printf("app:run read 
"); 
    sleep(1); 
    if(read(fd,readbuf,10)<0) 
    { 
        printf("error read 
"); 
        return -1; 
    } 
    sleep(1); 
 
    printf("app:run write 
"); 
    sleep(1); 
    if(write(fd,writebuf,12)<0) 
    { 
        printf("error write 
"); 
        return -1; 
    } 
    sleep(1); 
 
    printf("app:run close 
"); 
    sleep(1); 
    if(close(fd)<0) 
    { 
        printf("error close 
"); 
        return -1; 
    } 
    sleep(1); 
    return 0; 
} 

以上的==open()、read()、wrtie()、close()==函数都是用户态的API函数,其主要使用方式可以通过Ubuntu下的man指令进行查看(因为我们的Ubuntu系统运行在用户态,所以两者可运行的函数一致,可以进行借鉴)

man 2 open 

在这里插入图片描述

Makefile文件

KERNELDIR := /home/xxx/linux/linux_boot/linux-imx-rel_imx_4.1.15_2.1.0_ga  
CURRENT_PATH := $(shell pwd) 
 
obj-m := chrdevbase.o   #编译目标 
 
build: kernel_modules   #编译内核模块 
 
kernel_modules: 
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules  #以模块的形式进入linux内核目录进行编译 
clean: 
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	 

app使用 gcc命令编译即可

arm-linux-gnueabihf-gcc chrdevapp.c chrdevapp 

运行流程测试

在这里插入图片描述

上述过程中有两个地方:

①手动创建了设备号为199的文件节点;在内核函数fs.h文件中,可以struct file_operations的定义以及man 2 open手册中看到open()函数:

int (*open)(struct inode*, struct file *);  //fs.h文件中定义 
int open(const char *pathname,int flags);	 //man手册中定义 

可以发现在内核态open函数需要文件节点参数作为函数参数,而用户态需要文件路径进行操作。因此这里需要将/dev/chrdevbase 与设备号 199联系起来,因此便可以通过文件操作实现对外设的操作。

②运行App指令:./chrdevapp /dev/chrdevbase 该指令会传递给main函数(argc与argv[]),即:

argv[0] = ./chrdevapp 
argv[1] = /dev/chrdevbase 

其执行流程大致入下图,app不断调用底层驱动程序,而API函数完成内核态与用户态的交互:

在这里插入图片描述


本文参考链接:https://blog.csdn.net/m0_67391870/article/details/123483664
评论关闭
IT虾米网

微信公众号号:IT虾米 (左侧二维码扫一扫)欢迎添加!

Java多线程中Lock锁如何使用