Linux字符驱动设备开发
如pc上的uart设备文件:
crw-rw—- 1 root dialout 4, 64 Jun 8 09:20 /dev/ttyS0
crw-rw—- 1 root dialout 4, 65 Jun 8 09:20 /dev/ttyS1
crw-rw—- 1 root dialout 4, 66 Jun 8 09:20 /dev/ttyS2
crw-rw—- 1 root dialout 4, 67 Jun 8 09:20 /dev/ttyS3
|
1、设备驱动模块的加载和卸载
include <linux/module.h>
默认函数的方式:int __init init_module(void) 和 void __exit cleanup_module(void);
使用内核宏来指定自定义的函数:module_init(static int __init xxx(void)) 和 module_exit(static void __exit xxx(void))
///
2、字符设备的注册和注销
include <linux/fs.h>
① 指定主设备号,次设备号默认0~255全包:
函数原型:
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
static inline void unregister_chrdev(unsigned int major, const char *name);
参数解释:
major:指定主设备号
*name:设备驱动的名字,指向一串字符串
*fops:指向字符设备操作结构体file_operations
② 主次设备号都需要指定,比较灵活:
函数原型:
int register_chrdev_region(dev_t from, unsigned count, const char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
参数解释:
from:dev_t是设备号指定结构体,本质上是一个unsigned int,高12位是主设备号,低20位是次设备号
count: 用于设定从指定的次设备号开始,需要连续创建多少个设备
*name:设备驱动的名字,指向一串字符串
③ 动态自动寻找并分配空闲的主次设备号:
函数原型:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
void unregister_chrdev_region(dev_t from, unsigned count);
参数解释:
*dev:返回获取到的主次设备号;
baseminor:指定次设备号的起始值
count:需要申请的设备数量
*name:设备驱动的名字,指向一串字符串
from:需要注销的设备号
///
3、字符设备具体操作函数的实现
include <linux/fs.h>
结构体:struct file_operations
结构体原型:
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);
......
};
使用方法:
① 在驱动程序中申明一个file_operations的对象,并指定成员变量中对应的函数有具体的实现
② 将此结构体对象注册到设备驱动中
///
4、Linux字符设备号
驱动设备号分为主设备号与次设备号;在Linux内核中提供了一个结构体用来规范描述设备号struct dev_t,它的本质是一个unsigned int,高12位属于主设备号,低20位属于次设备号。
Linux内核还提供了一系列的宏定义来方便我们开发定义设备号,在 include/linux/kdev_t.h中:
![]()
宏功能解释:
MINORBITS:表示次设备号的位数
MINORMASK:这个是次设备号的掩码
MAJOR(dev):提取dev对象里的主设备号
MINOR(dev):提取dev对象里的次设备号
MKDEV(ma,mi):将主设备号和次设备号组装起来
///
5、添加 LICENSE 和作者信息
宏定义的原型:
#define MODULE_LICENSE(__MODULE_LICENSE_value) //申明需要准寻的协议,通常是"GPL"
#define MODULE_AUTHOR(_author) MODULE_INFO(author, _author) //注明驱动开发者信息
|
一个简单的例子:
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#define CHARDEVBASE_MAJOR 200u //主设备号
#define CHARDEVBASE_NAME "chardevbase" //设备名
static unsigned char readbuf[100u]; //读数据缓冲区
static unsigned char writebuf[100u]; //写数据缓冲区
static unsigned char kerneldata[100u] = {"kernel data!"};
/*================================================================
* @description : 打开设备
* @param - inode : 传递给驱动的inode
* @param - filp :设备文件,file结构体有个叫做private_data的成员
* 变量,一般在open的时候将private_data指向设备结
* 构体。
* @return : 0=成功;其他=失败
=================================================================*/
static int chrdevbase_open(struct inode *inode,struct file *filp)
{
//printk("The chrdevbase device is opened!\r\n");
return 0;
}
/*===============================================================
* @description : 读取设备的数据
* @param - filp : 要打开的设备文件(文件描述符结构体)
* @param - buf : 返回给用户空间的数据缓冲区
* @param - cnt : 要读取的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 读取的字节数,如果为负值,表示读取失败
=================================================================*/
static ssize_t chrdevbase_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
int retvalue = 0;
/* 向用户空间的程序发送数据 */
memcpy(readbuf,kerneldata,sizeof(kerneldata));
retvalue = copy_to_user(buf,readbuf,cnt);
if(0>retvalue)
printk("kernel senddata failed!\r\n");
else
{
retvalue = cnt;
printk("kernel senddata ok!\r\n");
}
return retvalue;
}
/*================================================================
* @description : 写数据给设备
* @param - filp : 设备文件,表示打开的文件描述符
* @param - buf : 写个设备的数据
* @param - cnt : 要写入的数据长度
* @param - offt : 相对于文件首地址的偏移
* @return : 写入的字节数,如果为负值,表示写入失败
=================================================================*/
static ssize_t chrdevbase_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *offt)
{
int retvalue = 0;
/* 接收用户空间程序写入的是数据存放在缓存区 */
retvalue = copy_from_user(writebuf,buf,cnt);
if(0>retvalue)
printk("kernel recevdata failed!\r\n");
else
{
retvalue = cnt;
memcpy(kerneldata,writebuf,sizeof(writebuf));
printk("kernel recevdata ok!\r\n");
}
return retvalue;
}
/*================================================================
* @description : 关闭/释放设备
* @param - inode : 传递给驱动的inode
* @param - filp : 要关闭的设备文件(文件描述符结构体)
* @return : 0=成功 其他=失败
=================================================================*/
static int chrdevbase_release(struct inode *inode,struct file *filp)
{
//printk("The chrdevbase device is closeed!\r\n");
return 0;
}
/*=================================================================
* 申明一个字符操作文件结构体对象file_operations
==================================================================*/
static struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};
/*=================================================================
* @description : 驱动注册入口函数
* @param : 无
* @return : 0=成功 其他=失败
===================================================================*/
static int __init chrdevbase_init(void)
{
int retvalue = 0;
/* 向Linux注册字符设备驱动 */
retvalue = register_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME,&chrdevbase_fops);
if(0>retvalue)
printk("chrdevbase device register failed!\r\n");
else
printk("chrdevbase device register ok!\r\n");
return retvalue;
}
/*=================================================================
* @description : 驱动模块卸载函数
* @param : 无
* @return :无
==================================================================*/
static void __exit chrdevbase_exit(void)
{
/* 注销字符设备驱动 */
unregister_chrdev(CHARDEVBASE_MAJOR,CHARDEVBASE_NAME);
printk("chrdevbase exit...\r\n");
return;
}
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("WangXiaokun");
|
字符驱动访问外设寄存器(基于LED驱动实验)
在Linux内核里,我们不能直接像单片机一样去操作那些外设寄存器,必须先通过Linux提供的架构函数向MMU内存管理单元申请对应的虚拟地址才可以;这里会使用到两个函数ioremap和iounmap。
1、申请虚拟地址和释放虚拟地址:
#include <asm/io.h>
函数原型:
#define ioremap(cookie,size) __arm_ioremap((cookie), (size), MT_DEVICE) //申请cookie对应的虚拟地址空间,大小为size个字节,返回 __iomem*
void __iomem * __arm_ioremap(phys_addr_t phys_addr, size_t size, unsigned int mtype)
{
return arch_ioremap_caller(phys_addr, size, mtype, __builtin_return_address(0));
}
void iounmap(const void __iomem *addr) //释放不用的虚拟地址空间addr
{
if (addr >= (void __force __iomem *)ARC_UNCACHED_ADDR_SPACE)
return;
vfree((void *)(PAGE_MASK & (unsigned long __force)addr));
}
参数解释:
cookie :物理寄存器地址的首地址
size :需要申请的虚拟地址空间字节大小
addr :需要释放的虚拟地址空间首地址
2、对虚拟地址读写操作:
#include <asm/io.h>
读虚拟地址:
u8 readb(const volatile void __iomem *addr) //读取addr地址,读一个字节
u16 readw(const volatile void __iomem *addr) //读取addr地址,读一个字
u32 readl(const volatile void __iomem *addr) //读取addr地址,读双字
u64 readq(const volatile void __iomem *addr) //读取addr地址,读64位
写虚拟地址:
void writeb(u8 b, volatile void __iomem *addr) //写数据到虚拟地址,写入一个字节
void writew(u16 b, volatile void __iomem *addr) //写数据到虚拟地址,写入一个字
void writel(u32 b, volatile void __iomem *addr) //写数据到虚拟地址,写入两个字
void writeq(u64 b, volatile void __iomem *addr) //写数据到虚拟地址,写入64位数据
|
/
向Linux申请设备号:
1、由Linux根据系统实际情况,动态分配:int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name);
参数解释:
*dev:用于返回申请到的设备号;
baseminor:指定申请的起始次设备号;
count:指定需要申请多少个设备;
*name:驱动设备的名字
2、在了解系统情况下,人工指定请求设备号:int register_chrdev_region(dev_t from, unsigned count, const char *name);
参数解释:
from:指定具体的设备号;
count:指定需要申请多少个设备;
*name:驱动设备的名字
3、释放申请到的设备号(统一的函数接口):void unregister_chrdev_region(dev_t from, unsigned count);
参数解释:
from:指定具体要释放的设备号;
count:指定需要释放多少个设备;
///
向Linux注册驱动设备:
#include <linux/cdev.h>
1、struct cdev 字符设备结构体:
该结构体在linux里用于描述管理一个字符设备;结构体原型如下:
struct cdev{
struct kobject kobj; //kobj由linux内核自动管理,具体细节不知
struct module *owner;
const struct file_operations *ops;
struct list_head list; //猜想是将cdev作为节点加入管理哈希表用的
dev_t dev;
unsigned int count;
}
2、初始化cdev结构体:
函数原型:
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
cdev->owner = THIS_MODULE;
参数解释:
*cdev : 需要初始化的cdev结构体对象
*fops : 字符驱动设备使用的字符操作结构体对象
3、将cdev注册到linux系统:
函数原型:
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
参数解释:
*p : 需要注册的字符设备描述结构体对象
dev : 字符设备的设备号
count : 需要注册到linux的设备个数
4、注销cdev字符设备:
函数原型:
void cdev_del(struct cdev *p);
参数解释:
*p :指定需要注销的字符设备对象结构体
///
标准的驱动设备注册和注销流程:
1、注册一个字符驱动设备:
① 使用上面的函数申请一个设备号;
②初始化一个cdev;
③ 实现file_operations并和设备号一起,通过cdev_add注册字符设备
2、注销一个字符驱动设备:
① 先使用函数cdev_del注销设备
② 取消对设备号的使用
|
使用mdev工具实现字符驱动设备的自动生成:
前面所记录的创建一个字符设备驱动的方法,驱动挂载后,linux的设备目录/dev下面并不会自动生成对应的设备描述文件,需要通过mknod /dev/xxx c major minor 命令手动创建;其实我们可以使用一个叫udev的工具来实现自动生成设备描述文件,BusyBOX这个文件系统有一个udev的简化版本mdev,也就是热拔插事件管理。
#include <linux/device.h>
1、class类的创建和销毁:
函数原型:
#define class_create(owner, name) //struct class的创建是一个宏定义函数,返回struct *class,在/sys/class/下会创建一个name的软连接到/devices/virtual/name/name,实际上是一个虚拟设备
void class_destroy(struct class *cls);//用于销毁释放*cls对应的内存,也可以用class_unregister(struct class *cls)
参数解释:
owner :固定为THIS_MODULE
name :创建的设备的名字
*cls : 需要被销毁的class对象
2、device对象的创建和销毁:
函数原型:
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...) //在class的基础上创建/dev/目录下的设备
void device_destroy(struct class *class, dev_t devt); //自动索引到class上对应设备号的device并销毁
参数解释:
*class : 指定device所依赖的class结构体对象
*parent : 如果没有父设备的话,这里为NULL
devt : 设备的设备号
*drvdata : 可以根据实际需要,寄存一些数据在这里,一般为NULL
char *fmt : 用于设定设备的名字
3、热拔插设备的插入与拔出:
虚拟设备插入:
① class_create申请一个class对象
② device_create基于class对象申请一个设备对象
虚拟设备拔出:
① device_destroy注销device设备
② class_destroy销毁class对象
|
一个LED驱动的例子:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#define LED_MINOR 0 //设备的次设备号
#define LED_NAME "led" //设备驱动的名字
#define COUNT 1 //驱动设备要创建的设备数
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/* 需要操作的相关物理寄存器与地址 */
#define CCM_CCGR1_BASE (0x020c406cu)
#define SW_MUX_GPIO1_IO03_BASE (0x020e0068u)
#define SW_PAD_GPIO1_IO03_BASE (0x020e02f4u)
#define GPIO1_DR_BASE (0x0209c000u)
#define GPIO1_GDIR_BASE (0x0209c004u)
typedef struct{
dev_t led_devid; //led驱动设备号
struct cdev led_cdev; //led字符驱动设备的注册结构体
struct file_operations led_fops; //led字符设备的操作实现结构体
struct class *led_class; //led字符驱动设备热拔插class
struct device *led_device; //led字符驱动设备热拔插device
/* led gpio寄存器的虚拟地址 */
void __iomem *VIRTUAL_CCM_CCGR1;
void __iomem *VIRTUAL_SW_MUX_GPIO1_IO03;
void __iomem *VIRTUAL_SW_PAD_GPIO1_IO03;
void __iomem *VIRTUAL_GPIO1_DR;
void __iomem *VIRTUAL_GPIO1_GDIR;
}LED_DRIVER_T;
LED_DRIVER_T led_driver_t;
/*====================================================================================
* 字符驱动设备具体功能的实现
=====================================================================================*/
/*=======================================================
* @description : 打开led设备
========================================================*/
static int led_open(struct inode *inode,struct file *filp)
{
filp->private_data = &led_driver_t;
return 0;
}
/*======================================================
* @description : 关闭led设备
=======================================================*/
static int led_close(struct inode *inode,struct file *filp)
{
filp->private_data = NULL;
return 0;
}
/*=====================================================
* @description : 读取led设备的值
=======================================================*/
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *loff)
{
int retvalue = 0;
u32 var = 0u;
u8 databuf[1];
LED_DRIVER_T *led_driver_t = (LED_DRIVER_T*)(filp->private_data);
if(NULL == led_driver_t)
{
printk("The led device buff is NULL!\r\n");
return -1;
}
/* 从寄存器里读取值返回给应用层 */
var = readl(led_driver_t->VIRTUAL_GPIO1_DR);
if(var&(0x1u<<3u))
databuf[0] = LEDOFF;
else
databuf[0] = LEDON;
retvalue = copy_to_user(buf,databuf,1);
return retvalue;
}
/*======================================================
* @description : 写入控制led设备
=======================================================*/
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *loff)
{
int retvalue = 0;
u32 var = 0u;
u8 databuf[1];
LED_DRIVER_T *led_driver_t = (LED_DRIVER_T*)(filp->private_data);
if(NULL == led_driver_t)
{
printk("The led device buff is NULL!\r\n");
return -1;
}
/* 根据用户写入数据控制LED */
retvalue = copy_from_user(databuf,buf,1);
if(0>retvalue)
{
printk("write data to device error!\r\n");
return retvalue;
}
var = readl(led_driver_t->VIRTUAL_GPIO1_DR);
if(LEDOFF == databuf[0])
var |= (0x1u<<3u);
else if(LEDON == databuf[0])
var &= ~(0x1u<<3u);
else
printk("Pluse cotrl led by 0 or 1\r\n");
writel(var,led_driver_t->VIRTUAL_GPIO1_DR);
return retvalue;
}
/*====================================================================================
* 字符驱动设备的注册与注销管理
======================================================================================*/
/*=========================================================
* @description : led驱动设备的注册
==========================================================*/
static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0u;
memset(&led_driver_t,0u,sizeof(LED_DRIVER_T));
/* 1 向MMU申请led物理寄存器的虚拟地址 */
led_driver_t.VIRTUAL_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
led_driver_t.VIRTUAL_SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
led_driver_t.VIRTUAL_SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
led_driver_t.VIRTUAL_GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
led_driver_t.VIRTUAL_GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);
/* 2 字符设备操作函数结构体的配置 */
led_driver_t.led_fops.owner = THIS_MODULE;
led_driver_t.led_fops.open = led_open;
led_driver_t.led_fops.release = led_close;
led_driver_t.led_fops.read = led_read;
led_driver_t.led_fops.write = led_write;
/* 3 开始初始化配置LED的相关寄存器 */
/* 使能GPIO1的时钟电源 */
val = readl(led_driver_t.VIRTUAL_CCM_CCGR1);
val &= ~(0x3u<<26u);
val |= (0x3u<<26u);
writel(val,led_driver_t.VIRTUAL_CCM_CCGR1);
/* 设置GPIO1_IO03的复用功能,复用为普通IO */
writel(5u,led_driver_t.VIRTUAL_SW_MUX_GPIO1_IO03);
/* 配置GPIO1_IO03的电气属性 */
writel(0x10b0u,led_driver_t.VIRTUAL_SW_PAD_GPIO1_IO03);
/* 配置GPIO1_IO03为数字量输出功能 */
val = readl(led_driver_t.VIRTUAL_GPIO1_GDIR);
val &= ~(0x1u<<3u);
val |= (0x1u<<3u);
writel(val,led_driver_t.VIRTUAL_GPIO1_GDIR);
/* 默认熄灭LED */
val = readl(led_driver_t.VIRTUAL_GPIO1_DR);
val |= (0x1u<<3u);
writel(val,led_driver_t.VIRTUAL_GPIO1_DR);
/* 4 动态申请系统可用的主设备号 */
retvalue = alloc_chrdev_region(&(led_driver_t.led_devid),LED_MINOR,COUNT,LED_NAME);
if(0>retvalue)
{
printk("The led driver alloc dev id failed!\r\n");
goto err;
}
/* 5 向linux注册申请到的设备号 */
cdev_init(&(led_driver_t.led_cdev),&(led_driver_t.led_fops));
led_driver_t.led_cdev.owner = THIS_MODULE;
retvalue = cdev_add(&(led_driver_t.led_cdev),led_driver_t.led_devid,COUNT);
if(0>retvalue)
{
printk("The led driver insmod cdev failed!\r\n");
goto err1;
}
/* 6 mdev工具,实现设备的热拔插 */
led_driver_t.led_class = class_create(THIS_MODULE,LED_NAME);
if(NULL == led_driver_t.led_class)
{
printk("The led driver create class failed!\r\n");
goto err2;
}
led_driver_t.led_device = device_create(led_driver_t.led_class,NULL,led_driver_t.led_devid,NULL,LED_NAME);
if(NULL == led_driver_t.led_device)
{
printk("The led driver create device failed!\r\n");
goto err3;
}
printk("The led driver insmod ok!\r\n");
return retvalue;
err3:
//class_destroy(led_driver_t.led_class);
class_unregister(led_driver_t.led_class);
err2:
cdev_del(&(led_driver_t.led_cdev));
err1:
unregister_chrdev_region(led_driver_t.led_devid,COUNT);
err:
iounmap(led_driver_t.VIRTUAL_CCM_CCGR1);
iounmap(led_driver_t.VIRTUAL_SW_MUX_GPIO1_IO03);
iounmap(led_driver_t.VIRTUAL_SW_PAD_GPIO1_IO03);
iounmap(led_driver_t.VIRTUAL_GPIO1_DR);
iounmap(led_driver_t.VIRTUAL_GPIO1_GDIR);
return retvalue;
}
/*=========================================================
* @description : led驱动设备的注销
==========================================================*/
static void __exit led_exit(void)
{
u32 val = 0u;
/* 1 熄灭LED灯 */
val = readl(led_driver_t.VIRTUAL_GPIO1_DR);
val |= (0x1u<<3u);
writel(val,led_driver_t.VIRTUAL_GPIO1_DR);
device_destroy(led_driver_t.led_class,led_driver_t.led_devid);
//class_destroy(led_driver_t.led_class);
class_unregister(led_driver_t.led_class);
cdev_del(&(led_driver_t.led_cdev));
unregister_chrdev_region(led_driver_t.led_devid,COUNT);
iounmap(led_driver_t.VIRTUAL_CCM_CCGR1);
iounmap(led_driver_t.VIRTUAL_SW_MUX_GPIO1_IO03);
iounmap(led_driver_t.VIRTUAL_SW_PAD_GPIO1_IO03);
iounmap(led_driver_t.VIRTUAL_GPIO1_DR);
iounmap(led_driver_t.VIRTUAL_GPIO1_GDIR);
return;
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("WangXiaokun");
|
Linux虚拟文件系统的几个结构体的介绍:
从前面的章节中,我们可以知道,应用从操作字符设备驱动需要通过file_operations提供的标准接口实现,这些接口函数里,包含了两十分重要的结构体对象:struct inode 和 struct file。
1、struct inode 结构体:
结构体原型:
struct inode{
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid;
kgid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
......
dev_t i_rdev;
loff_t i_size;
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
......
atomic_t i_count;
atomic_t i_dio_count;
atomic_t i_writecount;
......
const struct file_operations *i_fop;
......
struct cdev *i_cdev;
......
struct file_lock_context *i_flctx;
struct address_space i_data;
struct list_head i_devices;
......
__u32 i_generation;
__u32 i_fsnotify_mask;
......
void *i_private;
};
功能解释:
inode作为一个节点用来描述一个要操作的文件/设备文件,包括权限、设备号等信息,就是描述一个要操作的文件的属性。每个设备文件可以被打开很多次,但只有一个inode节点。
1、struct file 结构体:
结构体原型:
struct file {
......
struct path f_path;
struct inode *f_inode;
const struct file_operations *f_op;
......
void *private_data;
......
};
功能解释:
每个用户进程打开一个文件设备,在用户层会得到一个int类型的文件描述符,但文件描述符里有还存有对文件位置的偏移,打开标志等信息, 用一个int数无法记录下来的,所以在每个文件描述符的信息都是由内核里用stauct file对象描述文件描述符,这个对象返回到内核驱动代码中,设备驱动就可以识别打开和操作设备的进程的一些状态了。
从结构体原型中我们可以找到inode节点结构体对象,也就是说,可以找到设备文件的一系列属性信息,这个对于面向对象的驱动程序开发非常有用。
|
字符设备驱动程序的面向对象编程:
逻辑思路:
① 每个具体的设备驱动使用自定义的结构体将所有相关的变量组织起来成为结构体的成员
② 结构体对象和一些设备cdev等结构体使用动态分配内存的方式(kzalloc和kfree #include <linux/slab.h>),在驱动注册挂载的时候和卸载的时候
③ 利用前面所说的字符设备文件描述结构体inode和file,在具体的操作函数中索引到结构体对象(Linux内核提供了一个宏container_of可以根据某个结构体成员变量定位到结构体对象的基地址);
④ 以上步骤就可以实现全局的驱动工程通过自定义的驱动结构体对象来交互数据和操作设备了。
实例代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
/*=================================================================================================
* 工程是一个NXP的GPIO1_IO03的led设备驱动,使用面向对象的方式来编程
==================================================================================================*/
#define LED_MINOR 0 //设备的次设备号
#define LED_NAME "led" //设备驱动的名字
#define COUNT 1 //驱动设备要创建的设备数
#define LEDOFF 0 //关灯
#define LEDON 1 //开灯
/* 需要操作的相关物理寄存器与地址 */
#define CCM_CCGR1_BASE (0x020c406cu)
#define SW_MUX_GPIO1_IO03_BASE (0x020e0068u)
#define SW_PAD_GPIO1_IO03_BASE (0x020e02f4u)
#define GPIO1_DR_BASE (0x0209c000u)
#define GPIO1_GDIR_BASE (0x0209c004u)
/* 统一LED设备驱动结构体,动态分配 */
typedef struct{
dev_t led_devid; //led驱动设备号
struct cdev led_cdev; //led字符驱动设备的注册结构体
struct file_operations led_fops; //led字符设备的操作实现结构体
struct class *led_class; //led字符驱动设备热拔插class
struct device *led_device; //led字符驱动设备热拔插device
/* led gpio寄存器的虚拟地址 */
void __iomem *VIRTUAL_CCM_CCGR1;
void __iomem *VIRTUAL_SW_MUX_GPIO1_IO03;
void __iomem *VIRTUAL_SW_PAD_GPIO1_IO03;
void __iomem *VIRTUAL_GPIO1_DR;
void __iomem *VIRTUAL_GPIO1_GDIR;
}LED_DRIVER_T;
static LED_DRIVER_T *led_driver_t = NULL;
/*======================================================================
* 字符设备功能操作函数接口的实现
=======================================================================*/
static int led_open(struct inode *inode,struct file *filp)
{
struct cdev *led_cdev = inode->i_cdev;
LED_DRIVER_T *led_driver_t = container_of(led_cdev,LED_DRIVER_T,led_cdev);
if(NULL == led_driver_t)
{
printk("The led device open failed!\r\n");
return -1;
}
printk("The led device is open:%d:%d\r\n",MAJOR(led_driver_t->led_devid),MINOR(led_driver_t->led_devid));
return 0;
}
static int led_close(struct inode *inode,struct file *filp)
{
struct cdev *led_cdev = inode->i_cdev;
LED_DRIVER_T *led_driver_t = container_of(led_cdev,LED_DRIVER_T,led_cdev);
if(NULL == led_driver_t)
{
printk("The led device close error!\r\n");
return -1;
}
return 0;
}
static ssize_t led_read(struct file *filp,char __user *buf,size_t cnt,loff_t *loff)
{
ssize_t retvalue = 0;
u32 val = 0u;
u8 databuf[1];
//struct cdev *led_cdev = filp->f_path.dentry->d_inode->i_cdev;
struct cdev *led_cdev = filp->f_inode->i_cdev;
LED_DRIVER_T *led_driver_t = container_of(led_cdev,LED_DRIVER_T,led_cdev);
if(NULL == led_driver_t)
{
printk("read led device error!\r\n");
return -1;
}
/* 读取led设备对应的输出值寄存器 */
val = readl(led_driver_t->VIRTUAL_GPIO1_DR);
if(val&(0x1u<<3u))
databuf[0] = LEDOFF;
else
databuf[0] = LEDON;
retvalue = copy_to_user(buf,databuf,1);
return retvalue;
}
static ssize_t led_write(struct file *filp,const char __user *buf,size_t cnt,loff_t *loff)
{
ssize_t retvalue = 0;
u32 val = 0u;
u8 databuf[1];
//struct cdev *led_cdev = filp->f_path.dentry->d_inode->i_cdev;
struct cdev *led_cdev = filp->f_inode->i_cdev;
LED_DRIVER_T *led_driver_t = container_of(led_cdev,LED_DRIVER_T,led_cdev);
if(NULL == led_driver_t)
{
printk("write led device error!\r\n");
return -1;
}
retvalue = copy_from_user(databuf,buf,1);
if(0>retvalue)
{
printk("write led device data is fail!\r\n");
return retvalue;
}
val = readl(led_driver_t->VIRTUAL_GPIO1_DR);
if(LEDON == databuf[0])
val &= ~(0x1u<<3u);
else if(LEDOFF == databuf[0])
val |= (0x1u<<3u);
writel(val,led_driver_t->VIRTUAL_GPIO1_DR);
return retvalue;
}
/*======================================================================
* 驱动设备的注册与注销
=======================================================================*/
static int __init led_init(void)
{
int retvalue = 0;
u32 val = 0u;
/* 1 动态申请一个LED设备驱动对象LED_DRIVER_T */
led_driver_t = kzalloc(sizeof(LED_DRIVER_T),GFP_KERNEL);
if(NULL == led_driver_t)
{
printk("The led driver malloc struct failed!\r\n");
retvalue = -1;
goto err;
}
memset(led_driver_t,0u,sizeof(LED_DRIVER_T));
/* 2 LED相关的寄存器申请虚拟地址 */
led_driver_t->VIRTUAL_CCM_CCGR1 = ioremap(CCM_CCGR1_BASE,4);
led_driver_t->VIRTUAL_SW_MUX_GPIO1_IO03 = ioremap(SW_MUX_GPIO1_IO03_BASE,4);
led_driver_t->VIRTUAL_SW_PAD_GPIO1_IO03 = ioremap(SW_PAD_GPIO1_IO03_BASE,4);
led_driver_t->VIRTUAL_GPIO1_DR = ioremap(GPIO1_DR_BASE,4);
led_driver_t->VIRTUAL_GPIO1_GDIR = ioremap(GPIO1_GDIR_BASE,4);
/* 初始化LED相关寄存器 */
/* 使能GPIO1的时钟电源 */
val = readl(led_driver_t->VIRTUAL_CCM_CCGR1);
val &= ~(0x3u<<26u);
val |= (0x3u<<26u);
writel(val,led_driver_t->VIRTUAL_CCM_CCGR1);
/* 设置GPIO1_IO03的复用功能,复用为普通IO */
writel(5u,led_driver_t->VIRTUAL_SW_MUX_GPIO1_IO03);
/* 配置GPIO1_IO03的电气属性 */
writel(0x10b0u,led_driver_t->VIRTUAL_SW_PAD_GPIO1_IO03);
/* 配置GPIO1_IO03为数字量输出功能 */
val = readl(led_driver_t->VIRTUAL_GPIO1_GDIR);
val &= ~(0x1u<<3u);
val |= (0x1u<<3u);
writel(val,led_driver_t->VIRTUAL_GPIO1_GDIR);
/* 默认熄灭LED */
val = readl(led_driver_t->VIRTUAL_GPIO1_DR);
val |= (0x1u<<3u);
writel(val,led_driver_t->VIRTUAL_GPIO1_DR);
/* 3 动态向Linux申请空闲的设备号 */
retvalue = alloc_chrdev_region(&(led_driver_t->led_devid),LED_MINOR,COUNT,LED_NAME);
if(0>retvalue)
{
printk("The led driver alloc devid failed!\r\n");
goto err0;
}
/* 4 配置led字符驱动设备的操作函数功能 */
(led_driver_t->led_fops).open = led_open;
(led_driver_t->led_fops).release = led_close;
(led_driver_t->led_fops).read = led_read;
(led_driver_t->led_fops).write = led_write;
/* 5 初始化cdev字符驱动描述结构体 */
cdev_init(&(led_driver_t->led_cdev),&(led_driver_t->led_fops));
led_driver_t->led_cdev.owner = THIS_MODULE;
retvalue = cdev_add(&(led_driver_t->led_cdev),led_driver_t->led_devid,COUNT);
if(0>retvalue)
{
printk("The led driver cdev insmod failed!\r\n");
goto err1;
}
/* 6 使用mdev工具制造虚拟设备热拔插事件 */
led_driver_t->led_class = class_create(THIS_MODULE,LED_NAME);
if(NULL == (led_driver_t->led_class))
{
printk("The led driver alloc class failed!\r\n");
goto err2;
}
led_driver_t->led_device = device_create(led_driver_t->led_class,NULL,led_driver_t->led_devid,NULL,LED_NAME);
if(NULL == (led_driver_t->led_device))
{
printk("The led driver alloc device failed!\r\n");
goto err3;
}
printk("The led driver install OK! Device path is /dev/%s\r\n",LED_NAME);
return retvalue;
err3:
class_destroy(led_driver_t->led_class);
err2:
cdev_del(&(led_driver_t->led_cdev));
err1:
unregister_chrdev_region(led_driver_t->led_devid,COUNT);
err0:
iounmap(led_driver_t->VIRTUAL_CCM_CCGR1);
iounmap(led_driver_t->VIRTUAL_SW_MUX_GPIO1_IO03);
iounmap(led_driver_t->VIRTUAL_SW_PAD_GPIO1_IO03);
iounmap(led_driver_t->VIRTUAL_GPIO1_DR);
iounmap(led_driver_t->VIRTUAL_GPIO1_GDIR);
kfree(led_driver_t);
err:
return retvalue;
}
static void __exit led_exit(void)
{
u32 val = 0u;
if(NULL != led_driver_t)
{
device_destroy((led_driver_t->led_class),led_driver_t->led_devid);
class_destroy(led_driver_t->led_class);
cdev_del(&(led_driver_t->led_cdev));
unregister_chrdev_region(led_driver_t->led_devid,COUNT);
/* 默认熄灭LED */
val = readl(led_driver_t->VIRTUAL_GPIO1_DR);
val |= (0x1u<<3u);
writel(val,led_driver_t->VIRTUAL_GPIO1_DR);
iounmap(led_driver_t->VIRTUAL_CCM_CCGR1);
iounmap(led_driver_t->VIRTUAL_SW_MUX_GPIO1_IO03);
iounmap(led_driver_t->VIRTUAL_SW_PAD_GPIO1_IO03);
iounmap(led_driver_t->VIRTUAL_GPIO1_DR);
iounmap(led_driver_t->VIRTUAL_GPIO1_GDIR);
kfree(led_driver_t);
led_driver_t = NULL;
}
return;
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("WangXiaokun");
宏container_of的原型:
#define container_of(ptr, type, member) \
(type *)((char *)(ptr) - (char *) &((type *)0)->member)
#endif
|
///
七、Linux的/DEV目录下,自动产生设备名
#include <linux/device.h>
struct class *class_create(owner, name); //在"/sys/class/"目录下创建出名为name的子目录
void class_destroy(struct class *cls); //移除目录
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...); //在class目录下创建出设备信息和产生热插拔事件,mdev会根据设备信息创建出相应的设备文件
// class指定在哪个class目录下创建设备信息, parent指定父设备节点(可以为NULL), devt为设备文件的设备号. drvdata用于设备驱动用的参数(可设为NULL), 后面"const char *fmt, ..."是用printf的方式来设置设备文件的文件名. 如(..., "mydev%d", i):表示设备文件名由mydev与变量i的值组成.
void device_destroy(struct class *class, dev_t devt); //指定在class目录下移除指定设备号的设备信息. mdev就会移除相应的设备文件.
|
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h> //提供printk//
#include <linux/fs.h> //提供字符设备驱动//
#include <linux/cdev.h> //用于开发字符驱动设备//
#include <linux/device.h> //用于产生热拔插事件//
#define MYMA 1234
#define MYMI 1234
#define COUNT 3
dev_t devid; //这里存放设备号//
struct cdev test_cdev; //描述一个字符设备驱动//
struct class *test_class = NULL;
/*创建结构体对象file_operations,为用户提供操作设备驱动的接口*/
int test_open(struct inode *ind, struct file *fl)
{
printk("test open %s\n",__func__);
return 0;
}
ssize_t test_read(struct file *fl, char *__user buf, size_t len, loff_t *off)
{
printk("test read %s\n",__func__);
return len;
}
ssize_t test_write(struct file *fl, const char __user *buf, size_t len, loff_t *off)
{
printk("test write %s\n",__func__);
return len;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.open = test_open,
.read = test_read,
.write = test_write,
};
static int __init test_init(void)
{
int ret_var=0,
num=0;
/*1 向linux申请一个字符设备号*/
devid = MKDEV(MYMA,MYMI);
ret_var = register_chrdev_region(devid,COUNT,"hello_test");
if(0>ret_var)
goto err1;
/*2 初始化字符设备对象cdev,关联设备操作用户接口*/
cdev_init(&test_cdev,&fops);
test_cdev.owner = THIS_MODULE;
/*3 将这个字符设备驱动对象加入linux设备管理器中,同时关联设备号*/
ret_var = cdev_add(&test_cdev,devid,COUNT);
if(0>ret_var)
goto err2;
/*4 在系统/sys/class/目录下创建一个子目录*/
test_class = class_create(THIS_MODULE,"hello_word");
if(NULL == test_class)
goto err3;
/*5 制造热拔插事件,自动在/dev下面生成驱动字符设备文件*/
for(num=0;num<COUNT;++num)
device_create(test_class,NULL,MKDEV(MYMA,MYMI+num),NULL,"hello_test%d",num);
printk("Hello word,%u\n",(unsigned int)(devid>>20));
return 0;
err3:
cdev_del(&test_cdev);
err2:
unregister_chrdev_region(devid,COUNT);
err1:
return ret_var;
}
static void __exit test_exit(void)
{
int num=0;
for(num=0;num<COUNT;++num)
device_destroy(test_class,MKDEV(MYMA,MYMI+num)); //取消设备热拔插,拔出设备//
class_destroy(test_class); //注销并删除/sys/class/下的目录//
cdev_del(&test_cdev);
unregister_chrdev_region(devid,COUNT);
printk("Good Bye!!\n");
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
|
///
1、参考的网络资源
① Linux字符设备驱动file_operations - GreenHand# - 博客园
② Linux驱动程序三大结构:inode,file,file_operations_Linux编程_Linux公社-Linux系统门户网站
③ Linux_Struct file()结构体详解-szu_zhyi-ChinaUnix博客
/
2、几个常用的函数接口详解:
① struct module *owner; //通常直接赋值THIS_MODULE,用于阻止设备驱动在被操作中被卸载(THIS_MODULE = (*struct module)0)
② loff_t (*llseek)(struct fuile *,loff_t,int);
//指针参数filp为进行读取信息的目标文件结构体指针;参数 p 为文件定位的目标偏移量;参数orig为对文件定位的起始地址,这个值可以为文件开头(SEEK_SET,0,当前位置(SEEK_CUR,1),文件末尾(SEEK_END,2); llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值
③ ssize_t (*read)(struct file *,char __user *,size_t,loff_t *);
参数解释:
struct file *: 指针参数 filp 为进行读取信息的目标文件
char __user: 指针参数buffer 为对应放置信息的缓冲区(即用户空间内存地址)
size_t: 参数size为要读取的信息长度
loff_t *: 参数 p 为读的位置相对于文件开头的偏移,在读取信息后,这个指针一般都会移动(驱动层来移动)
返回值:非负数代表成功读取的字节数
④ ssize_t (*write)(struct file *,const char __user *,size_t,loff_t *);
参数解释:
struct file *:指针参数 filp 为进行读取信息的目标文件
const char __user *: buffer为要写入文件的信息缓冲区
size_t: count为要写入信息的长度
loff_t *: ppos为当前的偏移位置,这个值通常是用来判断写文件是否越界
返回值:非负数代表成功写入文件的字节数
⑤ int (*open)(struct inode *,struct file *);
参数解释:
struct inode *: inode 为文件节点,这个节点只有一个,无论用户打开多少个文件,都只是对应着一个inode结构
struct file *: filp就不同,只要打开一个文件,就对应着一个file结构体,file结构体通常用来追踪文件在运行时的状态信息
返回值:返回0代表打开设备成功;
⑥ int (*release)(struct inode *,struct file *);// void release(struct inode inode,struct file *file),release函数的主要任务是清理未结束的输入输出操作,释放资源,用户自定义排他标志的复位等
⑦ long (*compat_ioctl)(struct file *,unsigned int,unsigned long);
参数解释:
struct file *: 对应应用程序传递的文件描述符 fd 的值
unsigned int: cmd 参数从用户那里不改变地传下来(重要)
unsigned long: 可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的
返回值:如果驱动程序未实现ioctl,而应用程序调用了,则返回 -ENOTTY
⑧ unsigned int (*poll)(struct file *, struct poll_table_struct *);
功能解释:
可以用于实现驱动设备的被非阻塞IO访问,当应用程序使用select、poll和epoll来对设备尽心非阻塞轮询访问时,实际上会调用到这个函数接口实现。
参数解释:
struct file *: 要打开的设备文件(文件描述符)
struct poll_table_struct *:结构体 poll_table_struct 类型指针,由应用程序传递进来的。一般将此参数传递给poll_wait 函数。
返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:
|
/
3、file_operations内相关结构体:
① struct file(linux/fs.h):每打开一个应用程序就会有一个
结构体原型:
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
} __attribute__((aligned(4)));
成员的解析:
② struct inode(linux/fs.h):一个驱动设备有且只有一个,每创建一个字符设备文件,就会生成一个
结构体原型:
struct inode {
umode_t i_mode;
unsigned short i_opflags;
kuid_t i_uid;
kgid_t i_gid;
unsigned int i_flags;
#ifdef CONFIG_FS_POSIX_ACL
struct posix_acl *i_acl;
struct posix_acl *i_default_acl;
#endif
const struct inode_operations *i_op;
struct super_block *i_sb;
struct address_space *i_mapping;
#ifdef CONFIG_SECURITY
void *i_security;
#endif
/* Stat data, not accessed from path walking */
unsigned long i_ino;
/*
* Filesystems may only read i_nlink directly. They shall use the
* following functions for modification:
*
* (set|clear|inc|drop)_nlink
* inode_(inc|dec)_link_count
*/
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};
dev_t i_rdev;
loff_t i_size;
struct timespec i_atime;
struct timespec i_mtime;
struct timespec i_ctime;
spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */
unsigned short i_bytes;
unsigned int i_blkbits;
blkcnt_t i_blocks;
#ifdef __NEED_I_SIZE_ORDERED
seqcount_t i_size_seqcount;
#endif
/* Misc */
unsigned long i_state;
struct mutex i_mutex;
unsigned long dirtied_when; /* jiffies of first dirtying */
unsigned long dirtied_time_when;
struct hlist_node i_hash;
struct list_head i_io_list; /* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
struct bdi_writeback *i_wb; /* the associated cgroup wb */
/* foreign inode detection, see wbc_detach_inode() */
int i_wb_frn_winner;
u16 i_wb_frn_avg_time;
u16 i_wb_frn_history;
#endif
struct list_head i_lru; /* inode LRU list */
struct list_head i_sb_list;
union {
struct hlist_head i_dentry;
struct rcu_head i_rcu;
};
u64 i_version;
atomic_t i_count;
atomic_t i_dio_count;
atomic_t i_writecount;
#ifdef CONFIG_IMA
atomic_t i_readcount; /* struct files open RO */
#endif
const struct file_operations *i_fop; /* former ->i_op->default_file_ops */
struct file_lock_context *i_flctx;
struct address_space i_data;
struct list_head i_devices;
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
};
__u32 i_generation;
#ifdef CONFIG_FSNOTIFY
__u32 i_fsnotify_mask; /* all events this inode cares about */
struct hlist_head i_fsnotify_marks;
#endif
void *i_private; /* fs or device private pointer */
};
成员解释:
umode_t i_mode: i_mode表示访问权限控制
kuid_t i_uid: UID
kgid_t i_gid: GID
unsigned int i_flags: i_flags文件系统标志
union {
const unsigned int i_nlink;
unsigned int __i_nlink;
};: 硬链接数计数
loff_t i_size: 以字节为单位的文件大小
struct timespec i_atime: 最后access时间
struct timespec i_mtime: 最后modify时间
struct timespec i_ctime: 最后change时间
struct hlist_head i_dentry: 目录项链表
atomic_t i_count: i_count引用计数,当引用计数变为0时,会释放inode实例
atomic_t i_writecount: i_writecount写者计数
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
char *i_link;
unsigned i_dir_seq;
};: 特殊文件类型的union,pipe,cdev,blk.link etc,i_cdev表示这个inode属于一个字符设备文件,本文中创建设备文件的时候会把与之相关的设备号的驱动对象cdev拿来填充
void *i_private: inode的私有数据
/
|
Linux字符驱动设备开发相关推荐
- Linux字符驱动开发学习总结
linux驱动编写(虚拟字符设备编写) 昨天我们说了一些简单模块编写方法,但是终归没有涉及到设备的编写内容,今天我们就可以了解一下相关方面的内容,并且用一个实例来说明在linux上面设备是如何编写的. ...
- 嵌入式linux led驱动有几种写法,嵌入式Linux字符驱动LED灯设计
一.任务要求 完成一个字符IO口驱动,在开发板上该IO口对应LED灯.该驱动程序通过控制IO口的高低电平来控制亮灭.同时要写一个应用层的测试程序,用来测试驱动程序.我的测试程序为myled_test. ...
- [Linux字符驱动] DIDO 74HC595实现遥控遥信功能
项目中经常会使用YK和YX功能,DI操作,简单来说就是外部输入高电平,软件检测信号就为1:外部信号输入低电平,软件检测信号就为0:依据这样的设计,我们来看一下字符驱动该如何完成. 下面介绍一种有IO控 ...
- Linux字符驱动开发
Linux字符驱动简介 字符设备驱动简介 举个栗子 file_operations 结构体 字符设备驱动开发步骤 驱动模块的加载和卸载 字符设备的注册和注销 添加 LICENSE 和作者信息 Linu ...
- 关于linux字符驱动中read函数filp->f_pos 和 loff_t *ppos的关系
在学习linux 字符驱动的时候会有这样的困惑 比如我们实现一个字符驱动的读函数,如下 static ssize_t globalmem_read(struct file *filp, char __ ...
- linux字符驱动之点亮LED
上一节中,我们讲解了如何自动创建设备节点,这一节我们在上一节的基础上,实现点亮LED. 上一节文章链接:https://blog.csdn.net/qq_37659294/article/detail ...
- linux字符驱动之概念介绍
一.字符驱动框架 问:应用程序open.read.write如何找到驱动程序的open.read.write函数? 答:应用程序的open.read.write是在C库里面实现的,它里面通过swi v ...
- Linux字符驱动中动态分配设备号与动态生成设备节点
在编写Linux内核驱动程序的时候,如果不动态生成设备号的话,需要自己手动分配设备号,有可能你分配的设备号会与已有设备号相同而产生冲突.因此推荐自动分配设备号.使用下面的函数: int alloc_c ...
- linux 字符驱动阻塞型 等待队列
2019独角兽企业重金招聘Python工程师标准>>> 内核等待队列 等待队列 在linux驱动程序设计中,可以使用等待队列来实现进程的阻塞,等待队列可看作保存进程的容器,在阻塞进程 ...
最新文章
- las格式测井曲线_邹榕,等:顺北和托甫台区块奥陶系断裂结构单元测井响应特征初探...
- 盘点《头号玩家》里的 VR 技术,现在就能造个 Oasis 出来
- C++ main函数中参数argc和argv含义及用法( argument count和 argument vector)
- SAP Customer Data Cloud(Gigya)的用户搜索实现
- AI Challenger 2018 机器翻译参赛总结
- 5G发展是绵绵秋雨 应循序渐进
- 关于技术文章“标题党”一事我想说两句
- 怎样寻回win8因为删除后清空回收站的数据
- 将rm -f or -rf 删除命令改为放入回收站,并可通过命令将其撤回
- 第四十六章:SpringBoot RabbitMQ完成消息延迟消费
- android wear iphone7,iOS 10.1可修复iPhone7系列与Android Wear配对问题
- 我用了20年ERP系统,但是用它做报表,我却后悔了
- 汇编考试一星题目对字母操作,输入字符并在屏幕上显示
- win10备份为wim_在PE中使用CGI进行系统备份和还原
- 访问服务器 信号灯超时时间已到,win7系统分区提示信号灯超时时间已到怎么办...
- IPX9K IP69K:ISO 20653:2006
- 编程入门书籍:大学学习计算机基础必读 5 本经典入门书籍,收藏
- html缓存的图片放在哪里,浏览器图片缓存在哪
- 判断空间上三个点是否共线问题【找bug篇】
- 计算机数制及其转换,计算机基础知识数制转换
热门文章
- 《原则》读后感(一)
- 省钱兄(APP+H5+公众号+小程序)自营商城源码分销系统社区团购线上线下核销吃喝玩乐系统源码前端模板
- java 克隆对象 list_我想动态创建对象,先在List创建空对象,然后使用createEquipment复制,返回List,但是不会写了...
- diag矩阵(Diag矩阵计算公式)
- 企业微信 android2.3,企业微信2.3版本发布
- can和could的用法_情态动词could的用法(包含can与could的区别)
- 华为手机锁屏下拉怎么设置_华为手机怎么设置会滚动的锁屏文字?设置步骤超简单,一看就会...
- SAP BDC 数据导入
- Minix进程间通信分析
- Mac 打开safari浏览器直接卡死解决方法,解决Safari浏览器访问网页卡死重新再打开浏览器还是卡死实例演示