小言_互联网的博客

IMX6ull_Linux驱动编写(1)

366人阅读  评论(0)

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\r\n");
    return 0;
}

 /*驱动release函数(关闭)*/
static int chrdevbase_release(struct inode * inode, struct file * file) 
{
   
    printk("kernerl:release_func:chrdevbase_release\r\n");
    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\r\n");    
    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\r\n");  
    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\r\n");
    /*参数为:主设备号,设备名称,设备文件操作结构体*/
    register_chrdev(CHRDEVBASE_MAJOR,CHARDEVBASE_NAME,&chrdevbase_fops);

    return 0;
}

/*模块退出函数*/
void __exit chrdevbase_exit(void)
{
   
    printk("printk: chrdevbase is exit\r\n");
	/*参数为:主设备号,设备名称*/
    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\r\n",filename);  
    sleep(2);

    printf("app:run open\r\n");
    sleep(1);
    if((fd = open(filename,O_RDWR))<0)
    {
   
        printf("error open fd :%d\r\n",fd);
        return -1;
    }
    sleep(2);

    printf("app:run read\r\n");
    sleep(1);
    if(read(fd,readbuf,10)<0)
    {
   
        printf("error read\r\n");
        return -1;
    }
    sleep(1);

    printf("app:run write\r\n");
    sleep(1);
    if(write(fd,writebuf,12)<0)
    {
   
        printf("error write\r\n");
        return -1;
    }
    sleep(1);

    printf("app:run close\r\n");
    sleep(1);
    if(close(fd)<0)
    {
   
        printf("error close\r\n");
        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/qq_42151264/article/details/123431670
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场