前面我们讲解了字符设备的驱动模型,有了前面的基础后,今天学习函数接口就比较容易了
思考一个问题:当我们应用层调用open、read、write、close的时候,内核层是如何实现的呢?
前面学习字符设备驱动模型中有一个file_operation结构体,当我们调用open函数的时候,内核会调用file_operation结构体的open函数指针指向的函数。
我们来看一下file_operation结构体的样子:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int); //llseek对应了系统提供的lseek接口,实现函数指针位置的定位
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//当用户层调用系统层提供的read接口的时候需要通过此函数指针所指向的接口来实现对应的操作
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//当用户层调用系统层提供的write接口的时候需要通过此函数指针所指向的接口来实现对应的操作
unsigned int (*poll) (struct file *, struct poll_table_struct *);// 当需要进行轮询操作的时候调用的底层接口,对应了系统层的select和poll
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);//struct inode *:内核内部用来标识文件的数据结构
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
};
(一)open函数接口
系统层接口:
int open(const char *pathname, int flags, mode_t mode);
//O_CREAT
//O_NONBLOCK or O_NDELAY
内核层接口:
int (*open) (struct inode *, struct file *);
struct inode :内核中用来标识文件的数据结构,此数据结构的成员无需程序员手动赋值,而是内核中已经赋予了与文件相对应的操作值
struct file *:该结构体标识了一个打开的文件,系统会为每一个打开的文件关联一个struct file 数据结构,是在内核打开文件的同时,将该参数传递到和文件操作相关的所有需要该参数的接口中
(二)read函数接口
系统层:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
内核层:
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
read接口可以直接将内核空间的数据传递到用户空间,但是一般在开发驱动的过程中不会直接采用这种方式,原因是本操作需要两个空间地址,即用户空间和内核空间,用户空间直接操作内核地址是非常危险的,常用copy_to_user和copy_from_user进行用户空间和内核空间交换数据。
(三)lseek函数接口
系统层:
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t offset, int whence);
内核层:
loff_t (*llseek) (struct file *, loff_t, int);
struct file *:文件结构体
loff_t:上层传递的偏移量
int :文件光标定位状态
SEEK_SET:将光标定位在文件的开头,此时loff_t的值为正数
SEEK_CUR:将光标定位在当前位置,此时loff_t的值为可正可负
SEEK_EDN:将光标定位在文件的结尾,此时loff_t的值为负数
在这里面可以实现文件偏移操作,例如:
loff_t cdev_lseek(struct file *fp, loff_t offset, int whence)
{
//获取偏移量需要offset和whence结合
loff_t newoff=0;
switch(whence)
{
case SEEK_SET: newoff=offset;break;
case SEEK_CUR: newoff=fp->f_pos+offset;break;
case SEEK_END: newoff=offset+4;break;
}
if(newoff >4)newoff=4;
if(newoff<0)newoff=0;
fp->f_pos = newoff;
return newoff;
}
(四)用户空间和用户空间交换数据
copy_to_user:将内核空间的数据拷贝到用户空间
static inline long copy_to_user(void __user *to,const void *from, unsigned long n)
{
to:用户空间的地址
from:内核空间的地址
n:传递数据的大小
might_sleep();
#define VERIFY_WRITE 1
if (access_ok(VERIFY_WRITE, to, n))
return __copy_to_user(to, from, n);
else
return n;
}
copy_from_user:将用户空间的数据拷贝到内核空间
static inline long copy_from_user(void *to,const void __user * from, unsigned long n)
{
might_sleep();
if (access_ok(VERIFY_READ, from, n))
return __copy_from_user(to, from, n);
else
return n;
}
(五)通过设备节点提取设备号
//通过设备节点提取次设备号
static inline unsigned iminor(const struct inode *inode)
{
return MINOR(inode->i_rdev);
}
//通过设备节点提取次主设备号
static inline unsigned imajor(const struct inode *inode)
{
return MAJOR(inode->i_rdev);
}
(六)映射ioremap
程序中在操作物理硬件地址的时候不要直接操作对应的地址,需要先进行映射操作
static inline void __iomem *ioremap(phys_addr_t offset, unsigned long size)
{
return (void __iomem*) (unsigned long)offset;
}
typedef u32 phys_addr_t;
phys_addr_t offset:指的是映射的物理地址
unsigned long size:映射空间的大小
void __iomem *:接收映射后的起始地址
解除映射:
void iounmap (volatile void __iomem *addr)
(七)实例:LED驱动编程
思路:
首先把需要操作的寄存器物理地址进行映射,然后在open函数中做初始化工作,最后在read/write函数中调用copy_to/from_user函数将用户空间(内核空间)的数据拷贝到内核空间(用户空间),对数据进行操作
led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
int i=0;
dev_t dev=0;
#define CDEVCOUNT 5
#define CDEVNAME "cdevdevice"
#define CDEVCLASS "myclass"
#define INODENAME "mycdev"
#define ADDRSZIE 8
unsigned int phy_addr = 0x110002E0;//映射的起始地址为GPM4CON
unsigned int * virt_addr = NULL;//用来接收映射后的起始地址
struct cdev * cdev=NULL;
struct class * cdevclass=NULL;
#define GPM4CON (*(volatile unsigned int * )virt_addr)
#define GPM4DAT (*(volatile unsigned int * )(virt_addr +1))
int cdev_open (struct inode *node, struct file *file)
{
//清空配置寄存器
GPM4CON &= ~(0XFFFF<<0);
//设置引脚为输出状态
GPM4CON |= (0x1111<<0);
//给指定寄存器初始化
GPM4DAT |= (0x0F<<0);
printk("cdev_open is install\n");
return 0;
}
ssize_t cdev_read (struct file *fp, char __user *buf, size_t size, loff_t *offset)
{
printk("cdev_read is install\n");
return 0;
}
ssize_t cdev_write (struct file *fp, const char __user * buf, size_t size, loff_t *offset)
{
int i=0;
char str[4]={-1,-1,-1,-1};
int ret =copy_from_user(str,buf,4);
for(i=0;i<4;i++)
{
if(str[i]=='0')
GPM4DAT |= (1<<i);
else
if(str[i]=='1')
GPM4DAT &=~(1<<i);
}
printk("cdev_write is install\n");
return 0;
}
int cdev_release (struct inode *node, struct file *fp)
{
printk("cdev_release is install\n");
return 0;
}
struct file_operations fop={
.open=cdev_open,
.read=cdev_read,
.write=cdev_write,
.release=cdev_release,
};
static int __init cdev_module_init(void)
{
int ret=alloc_chrdev_region(&dev, 0, CDEVCOUNT, CDEVNAME);
if(ret)
{
return -1;
}
cdev=cdev_alloc();
if (!cdev)
goto out;
cdev_init(cdev, &fop);
if(cdev_add(cdev, dev, CDEVCOUNT))
{
goto out1;
}
printk("cdev_add success\n");
cdevclass=class_create(THIS_MODULE,CDEVCLASS);
if (IS_ERR(cdevclass))
{
goto out2;
}
printk("class_create success\n");
for(i=0;i<5;i++)
device_create(cdevclass,NULL, dev+i, NULL, "mycdev%d",i );
printk("device_create success\n");
virt_addr = ioremap(phy_addr, ADDRSZIE);
return 0;
out:
unregister_chrdev_region(dev,CDEVCOUNT);
return -2;
out1:
unregister_chrdev_region(dev,CDEVCOUNT);
kfree(cdev);
out2:
cdev_del(cdev);
unregister_chrdev_region(dev,CDEVCOUNT);
kfree(cdev);
return PTR_ERR(cdevclass);
}
static void __exit cdev_module_cleanup(void)
{
for(--i;i>=0;i--)
device_destroy(cdevclass,dev+i);
printk("device_destroy success\n");
class_destroy(cdevclass);
cdev_del(cdev);
unregister_chrdev_region(dev,CDEVCOUNT);
kfree(cdev);
iounmap(virt_addr);
printk("kfree success\n");
}
module_init(cdev_module_init);
module_exit(cdev_module_cleanup);
MODULE_LICENSE("GPL");
led_app.c
#include<stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
char str[]={'1','1','1','0'};
int fd= open(argv[1],O_RDWR);
if(fd== -1)
{
perror("open");
return -1;
}
write(fd,str,4);
close(fd);
return 0;
}
Makefile
CFLAG =-C
TARGET = led
TARGET1 = led_app
KERNEL = /mydriver/linux-3.5
obj-m += $(TARGET).o
all:
make $(CFLAG) $(KERNEL) M=$(PWD)
arm-linux-gcc -o $(TARGET1) $(TARGET1).c
clean:
make $(CFLAG) $(KERNEL) M=$(PWD) clean
本文章仅供学习交流用禁止用作商业用途,文中内容来水枂编辑,如需转载请告知,谢谢合作
微信公众号:zhjj0729
微博:文艺to青年