(七)linux函数接口的使用
前面我們講解了字符設備的驅動模型,有了前面的基礎后,今天學習函數接口就比較容易了
目錄
- (一)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 :文件光標定位狀態
在這里面可以實現文件偏移操作,例如:
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
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青年
總結
以上是生活随笔為你收集整理的(七)linux函数接口的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 平安保险购买后多久可以退保
- 下一篇: 学开花店没基础可以吗 可以从零开始积累