本文主要是介绍嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第五天-ARM Linux编程之字符设备驱动(物联技术666),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
链接:https://pan.baidu.com/s/1V0E9IHSoLbpiWJsncmFgdA?pwd=1688
提取码:1688
教学内容:
1、内核模块的简单框架:
__init __exit执行完后就释放空间
简单框架:包含三个部分
1)模块初始化和模块退出函数
2)注册模块函数
3)模块许可
//***************************************************
#include <linux/module.h> /*module_init()*/
#include <linux/kernel.h> /* printk() */
#include <linux/init.h> /* __init __exit */
static int __init myModule_init(void) //红色名可自定义,使用static保证只能在本文件中调用
{
/* Module init code */
PRINTK("myModule_init\n");
return 0;
}
static void __exit myModule_exit(void) //红色名可自定义
{
/* Module exit code */
PRINTK("myModule_exit\n");
return;
}
module_init(myModule_init); //注册模块函数
module_exit(myModule_exit); //注册
MODULE_AUTHOR("zhangda"); /*模块作者,可选*/
MODULE_LICENSE("GPL"); /*模块许可证明,描述内核模块的许可权限,必须*/
MODULE_DESCRIPTION("A simple Hello World Module"); /*模块说明,可选*/
//******************************************
makefile的编写
驱动有两种方式,一为内核树之内,一为内核树以外,前者有点复杂,涉及到将驱动放到合适的内核树目录,修改相应的Makefile以及Kconfig文件,不过,天下无难易之事,为之,难亦不难了;后者所做的劳动就不用那么多了。这个Makfile只适合于后者。此外,内核的Makefile跟一般的应用程序的Makefile不太一样,就像驱动程序跟应用程序,内核头文件跟应用程序头文件等等,没必然关系,或者说是两码事,两者不能混为一谈。再有一点,驱动是跟内核打交道的,你的系统中必须有一个内核源代码。
//*******************************************
obj-m := module_test.o //目标文件
ifneq ($(KERNELRELEASE),)
KERNELDIR = $(KERNELRELEASE)
else
KERNELDIR = /home/zhangda/linux_kernel/linux-2.6.34 //内核源代码地址
endif
$(PWD) := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
rm -rf *.o *.mod.c *.order *.symvers
clean:
rm -rf *.ko
//***************************************************
2、模块间符号的相互引用
在模块编程或内核编程中经常会遇到需要调用其它模块中符号的情况,在内核中专门有一张表来管理这些符号,可以通过 cat/proc/kallsyms查看该内核符号表我们也可以自己编写一个模块,并导出其符号供其它模块使用。
//****************************************
//****************************************
上图1,通过EXPORT_SYMBOL导出模块符号,方便图2使用符号函数。由于是给文件外函数使用,所以不能使用static修饰函数。
3、linux字符设备驱动结构
描述字符设备的结构体(详细参考内核和宋宝华的设备驱动书)
//**************************
struct cdev {
struct kobject kobj; //内嵌kobject 对象
struct module *owner; //所属模块
const struct file_operations *ops; //文件操作结构
struct list_head list;
dev_t dev; //设备号
unsigned int count;
};
//*****************************
设备号为32位,高12位为主设备号,低20位为次设备号;
此结构体描述了字符设备的信息,其中struct file_operations *ops结构体是向用户提高SPI接口函数的重要结构体。
file_operations :
//*******************************************
#include <linux/fs.h>
struct file_operations {
struct module *owner;
// 指向拥有该结构的模块的指针,避免正在操作时被卸载,一般为初始化为THIS_MODULES
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 (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);//io控制接口
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 (*open) (struct inode *, struct file *);//打开接口
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);//关闭接口
int (*fsync) (struct file *, struct dentry *, 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 **);
};
//****************************************
字符驱动设备的注册:
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
(内联函数)
第一个参数:主设备号,int型,0为内核自动分配
第二个参数:设备名
第三个参数:file_operations的结构体地址
返回分配的主设备号,大于0,错误返回负值。
void unregister_chrdev(MAJOR_NR, DRIVER_NAME);(内联函数)
第一个参数:分配的主设备号
第二个参数:设备名
//*********************
MAJOR_NR = register_chrdev(MAJOR_NR, DRIVER_NAME, &GPG_fops);
if(MAJOR_NR < 0)
{
PRINTK("register char device fail!\n");
return MAJOR_NR;
}
unregister_chrdev(MAJOR_NR, DRIVER_NAME);
//*****************************
自动创建节点:
#include<linux/device.h>
static struct class *my_class;
my_class= class_create(THIS_MODULE, "my_class");
device_create(my_class,NULL,dev_n,NULL,"hello");
注意:dev_n = MKDEV(MAJOR_NR, MINOR_NR);
设备卸载删除类和设备节点
device_destroy(my_class,dev_n);
class_destroy(my_class);
//******************************
my_class=class_create(THIS_MODULE,"udev_gpg");
device_create(my_class,NULL, MKDEV(MAJOR_NR, MINOR_NR), NULL,DRIVER_NAME);
device_destroy(my_class,MKDEV(MAJOR_NR, MINOR_NR));
class_destroy(my_class);
//******************************
用户态与内核态数据的交互:
用户应用程序与驱动程序分属于不同的进程空间,因此二者之间的数据应当采用以下函数进行交换
#include <asm/uaccess.h>
copy_to_user(user_buffer, kernel_buffer, n)
//从内核空间拷贝n字节数据到用户空间
copy_from_user(kernel_buffer, user_buffer, n)
//从用户空间拷贝n字节数据到内核空间
put_user(kernel_value, user_buffer)
//从内核空间拷贝一数据变量到用户空间
get_user(kernel_value, user_buffer)
//从用户空间拷贝一数据变量到内核空间
(内核空间数据可是任意类型)
字符设备驱动的流程:
1)、建立__init和__exit函数;并注册这2个函数module_init,module_exit,声明MODULE_LICENSE;
2)、创建file_operations结构体,并指明函数地址;
3)、字符设备驱动的注册,在__init函数里面注册,在__exit注销;
4)、在__init函数里面完成节点创建,在__exit注销节点;
5)、编写file_operations结构体中的函数,具体功能可以结合裸机驱动编写功能
例如:
//***********************************************
#include <linux/module.h> /*module_init()*/
#include <linux/kernel.h> /* printk() */
#include <linux/init.h> /* __init __exit */
#include <linux/fs.h> /* file_operation */
#include <asm/uaccess.h> /* copy_to_user, copy_from_user */
#include <linux/device.h> /*class ,class_create ,device_create 等*/
#define GPGCON (*(volatile unsigned long *)S3C2410_GPGCON) //虚拟地址
#define GPGDAT (*(volatile unsigned long *)S3C2410_GPGDAT)
#define GPGUP (*(volatile unsigned long *)S3C2410_GPGUP)
#include "my_ioctl.h"
#define MAJOR_NAME "my_ioctl_drv"
static int MAJOR_NR = 0;
static int MINOR_NR = 0;
struct class *myclass;
static int io_open(struct inode *inode, struct file *filp)
{ }
static int io_release(struct inode *inode, struct file *filp)
{ }
static ssize_t io_read(struct file *filp, char *buf,size_t count, loff_t *f_pos)
{ }
static ssize_t io_write(struct file *filp, const char *buf,size_t count, loff_t *f_pos)
{ }
static int io_ioctl(struct inode *inode, struct file *file,unsigned int cmd, unsigned long arg)
{ }
static struct file_operations ioctl_file_opt = {
.owner = THIS_MODULE,
.write = io_write,
.read = io_read,
.ioctl = io_ioctl,
.open = io_open,
.release = io_release,
};
static int __init my_inctl_init(void)
{
PRINTK("come in init\n");
MAJOR_NR = register_chrdev(MAJOR_NR, DRIVER_NAME, &ioctl_file_opt);
if(MAJOR_NR < 0)
{
PRINTK("register char device fail!\n");
return MAJOR_NR;
}
myclass=class_create(THIS_MODULE,"my_ioctl");
device_create(myclass,NULL, MKDEV(MAJOR_NR, MINOR_NR), NULL,"my_ioctl");
return 0;
}
static void __exit my_inctl_exit(void)
{
if(MAJOR_NR > 0)
{
unregister_chrdev(MAJOR_NR, DRIVER_NAME);
device_destroy(myclass,MKDEV(MAJOR_NR, MINOR_NR));
class_destroy(myclass);
}
PRINTK("exit ioctl\n");
return;
}
module_init(my_inctl_init);
module_exit(my_inctl_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("HB");
//***************************************************
配置s3c2410的文件
cp arch/arm/configs/s3c2410_defconfig .config
make menuconfig(执行s3c2410配置)
这篇关于嵌入式培训机构四个月实训课程笔记(完整版)-Linux ARM驱动编程第五天-ARM Linux编程之字符设备驱动(物联技术666)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!