前面我們講解了字符設備的驅動模型,有了前面的基礎后,今天學習函數接口就比較容易了
目錄
- (一)open函數接口
- (二)read函數接口
- (三)lseek函數接口
- (四)用戶空間和用戶空間交換數據
- (五)通過設備節點提取設備號
- (六)映射ioremap
- (七)實例:LED驅動編程
思考一個問題:當我們應用層調用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和pollint (*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 1if (access_ok(VERIFY_WRITE, to, n))return __copy_to_user(to, from, n);elsereturn 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);elsereturn 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 8unsigned 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).oall:make $(CFLAG) $(KERNEL) M=$(PWD)arm-linux-gcc -o $(TARGET1) $(TARGET1).c
clean:make $(CFLAG) $(KERNEL) M=$(PWD) clean
本文章僅供學習交流用禁止用作商業用途,文中內容來水枂編輯,如需轉載請告知,謝謝合作
微信公眾號:zhjj0729
微博:文藝to青年