20 字元裝置驅動相關的函式和引數及實現(虛擬檔案)
字元裝置驅動相關的函式和引數及實現(虛擬檔案)
使用者程序呼叫函式順序:
open ---> kernel ---> cdev.ops->open(..)
read ---> kernel ---> cdev.ops->read(..)
ioctl ---> kernel ---> cdev.ops->unlocked_ioctl(..)
裝置驅動實現好功能後,基本上由使用者程序通過系統呼叫後進來呼叫的。
如需要迴圈操作硬體,則應是在使用者程序裡迴圈。
使用者程序操作裝置驅動,網路通訊等與普通的文字檔案操作的程式設計介面基本一樣(open, read, write, ioctl …),這套介面就是所謂的VFS(虛擬檔案系統)。
當用戶程序open裝置檔案時,核心會根據開啟的裝置檔案的裝置號找到對應的cdev物件,檢查cdev.ops->open,如果不為空,則呼叫驅動裡的open函式,如果為空,核心直接返回fd。
注意:使用者程序開啟裝置檔案得到檔案描述符不是由裝置驅動裡指定的,裝置驅動裡的open函式僅僅是告訴核心是否已正常開啟。
在linux核心,用一個inode節點物件描述一個要操作的檔案/裝置檔案,包括許可權,裝置號等資訊。
一個檔案可以開啟很多次,但都是共用一個inode物件來描述屬性的。
檔案描述符屬於一個程序的資源,不同程序裡有可能相同的檔案描述符。
struct inode {
...
dev_t i_rdev; //裝置檔案對應的裝置號,驅動裡即可通過區分次裝置號來區別不同的具體硬體
struct cdev *i_cdev; //指向對應的字元裝置驅動cdev物件的地址
...
};
在使用者程序裡用一個int型別來表示檔案描述符,但檔案描述符裡有還存有對檔案位置的偏移,開啟標誌等資訊,用一個int數無法記錄下來的,所在每個檔案描述符的資訊都是由核心裡用file物件描述檔案描述符,在檔案開啟時建立,關閉時銷燬。
struct file {
...
struct path f_path;
const struct file_operations *f_op; //對應的檔案操作物件的地址
unsigned int f_flags; //檔案開啟的標誌
fmode_t f_mode; //許可權
loff_t f_pos; //檔案描述符的偏移
struct fown_struct f_owner; //屬於哪個程序
unsigned int f_uid, f_gid;
void *private_data; //給驅動程式設計師使用
...
};
如果開啟裝置檔案,那麼得到的file物件:
file物件裡的成員f_path.dentry->d_inode->i_rdev可以獲取到裝置檔案的裝置號
file物件裡的成員f_path.dentry->d_inode可以獲取到裝置檔案的inode物件的地址
注意:一個檔案只有一個inode節點物件,但是可以開啟多次,得到不同的檔案描述符物件(也就是多個struct file物件)。
虛擬檔案系統實現相關函式:
struct file_operations裡函式引數:
int (*open) (struct inode *, struct file *);
//inode表示應用程式開啟的檔案的節點物件,file表示開啟檔案獲取到的檔案描述符
//返回值0表示成功開啟,負數表示開啟失敗
//核心根據open函式的返回值來確定是否給呼叫的使用者程序分配檔案描述符
//在驅動可以不實現此函式,如不實現,則表示每次開啟都是成功的
ssize_t (*read) (struct file *fl, char __user *buf, size_t len, loff_t *off);
//buf指向使用者程序裡的緩衝區,len表示buf的大小(由使用者呼叫read時傳進來的)
//off表示fl檔案描述符的操作偏移,返回值為實際給使用者的資料位元組數
//注意:必須通過off指標來改變檔案描述符的偏移(*off += 操作位元組數),不可以直接通過"fl->f_pos"來設定
ssize_t (*write) (struct file *, const char __user *buf, size_t len, loff_t *off);
//使用者程序把資料給驅動,也就是讓驅動存放使用者程序傳進來的資料
//buf指向使用者程序裡的緩衝區,len表示buf的大小(由使用者呼叫write時傳進來的)
//off表示fl檔案描述符的操作偏移,返回值為實際給驅動的資料位元組數
//注意:必須通過off指標來改變檔案描述符的偏移(*off += 操作位元組數),不可以直接通過"fl->f_pos"來設定
long (*unlocked_ioctl) (struct file *fl, unsigned int cmd, unsigned long arg);
//cmd表示使用者程序呼叫ioctl時的第二個引數,arg表示第三個引數(可選)
//返回值為0表示ioctl成功,返回負數表示失敗
loff_t (*llseek) (struct file *fl, loff_t offset, int whence);
//如:lseek(fd, 54, SEEK_SET)
在驅動裡操作使用者資料緩衝區的函式:
#include <asm/uaccess.h>
extern inline long copy_to_user(void __user *to, const void *from, long n);
//返回值為還有多少位元組沒有複製成功,正常情況下返回0
//to指使用者程序的緩衝區,from指驅動裡裝資料的緩衝區,n要複製多少位元組
extern inline long copy_from_user(void *to, const void __user *from, long n);
//返回值為還有多少位元組沒有複製成功,正常情況下返回0
//to指驅驅動裡裝資料的緩衝區,from指使用者程序的緩衝區,n指多少位元組
put_user(x, p);
//x為值,p為地址
//如果與使用者程序互動的資料是1, 2, 4, 8位元組的話,可用
get_user(x,p);
//如果從使用者程序獲取1, 2, 4位元組的話,可用
驅動裡動態申請緩衝區的函式:
#include <linux/slab.h>
void *kmalloc(size_t size, gfp_t flags);//申請後要記憶體要清零
void *kzalloc(size_t size, gfp_t flags);//申請出來的記憶體已清零
void kfree(const void *objp);//回收kmalloc/kzalloc的記憶體
void *vmalloc(unsigned long size);//申請大記憶體空間
void vfree(const void *addr);//回收vmalloc的記憶體
//kmalloc申請出來的記憶體是實體地址連續的,vmalloc不一定是連續的
//動態申請記憶體,並清零,size為申請多大(不要超過128K)
//flags為標誌(常為GFP_KERNEL),成功返回地址,失敗返回NULL
//GFP_ATOMIC,使用系統的記憶體緊急池
在驅動裡用資料緩衝區代替文字檔案的資料存取
虛擬檔案系統的實現(test.c):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define MYMA 1234
#define MYMI 5500
#define COUNT 1
dev_t devid; //裝置號id
struct cdev mycdev; //裝置物件
u8 *data; //驅動資料緩衝區
int data_len = 26; //驅動資料緩衝區長度
//當用戶程序讀時,應把驅動裡的緩衝區資料複製給使用者程序
//fl->f_pos表示當前檔案描述符對檔案的偏移, len表示使用者程序想要讀的大小
ssize_t myread(struct file *fl, char __user *buf, size_t len, loff_t *off)
{
int copy_len, ret;
if((fl->f_pos + len) > strlen(data)) //如果剩下沒讀的資料長度少於len,則只複製出剩下沒讀部分
copy_len = strlen(data) - fl->f_pos;
else
copy_len = len; //如果剩下的資料長度超出len,則本次複製len位元組
ret = copy_to_user(buf, data+fl->f_pos, copy_len); //讀取驅動資料緩衝區內容
//內容複製後,需要改變檔案描述符的位置偏移
*off += copy_len - ret; //在read/write函式裡必須通過off來改變
return copy_len - ret;
}
//當用戶程序write時,驅動裡應把使用者程序的資料存放起來
ssize_t mywrite(struct file *fl, const char __user *buf, size_t len, loff_t *off)
{
int copy_len, ret;
if((fl->f_pos + len) > data_len) //如果要複製的內容超出資料緩衝區的大小
copy_len = data_len - fl->f_pos; //只複製剩下空間大小的內容
else
copy_len = len;
ret = copy_from_user(data+ fl->f_pos, buf, copy_len); //將資料寫入驅動資料緩衝區
*off += len_copy - ret;
return len_copy - ret;
}
//當用戶程序lseek(fd, 54, SEEK_SET)時觸發此函式
loff_t myllseek(struct file *fl, loff_t offset, int whence)
{
//除了read/write函式,可以通過fl->f_pos來改變檔案描述符的偏移
u8 *tmp;
int len;
switch(whence)
{
case SEEK_SET:
if(offset < data_len)
fl->f_pos = offset;
else
return -1;
break;
case SEEK_CUR:
if((fl->f_pos + offset) < data_len)
fl->f_pos += offset;
else
return -1;
break;
case SEEK_END:
if(0 == offset)
fl->f_pos = data_len - 1; //資料緩衝區的最後位置
else if(offset > 0)
{
//表示需要把緩衝區變為(原大小+offset)
len = data_len;
data_len += offset;
tmp = kzalloc(data_len, GFP_KERNEL); //分配出新大小的緩衝區
memcpy(tmp, data, len); //把原緩衝區上的資料複製到新緩衝區
kfree(data); //回收原空間
data = tmp; //取代原緩衝區
fl->f_pos = data_len - 1;
}
else
return -1;
break;
}
return fl->f_pos;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.read = myread,
.write = mywrite,
.llseek = myllseek,
};
static int __init test_init(void)
{
int ret, i;
devid = MKDEV(MYMA, MYMI); //生成裝置號
ret = register_chrdev_region(devid, COUNT, "mydev"); //申請裝置號
if(ret < 0)
goto err0;
cdev_init(&mycdev, &fops); //初始化裝置物件
mycdev.owner = THIS_MODULE;
ret = cdev_add(&mycdev, devid, COUNT); //將裝置物件加到核心
if (ret < 0)
goto err1;
data = kzalloc(dlen, GFP_KERNEL); //申請資料緩衝區, 此緩衝區用於與使用者程序資料互動.
if (NULL == data)
goto err2;
//初始化驅動緩衝區內容
for (i = 0; i < 26; i++)
data[i] = 'A' + i;
printk("init cdev success\n");
return 0;
err2:
cdev_del(&mycdev); //從核心中移除裝置物件
err1:
unregister_chrdev_region(devid, COUNT); //回收裝置號
err0:
return ret;
}
static void __exit test_exit(void)
{
unregister_chrdev_region(devid, COUNT); //回收裝置號
cdev_del(&mycdev); //從核心中移除裝置物件
kfree(data); //釋放核心驅動緩衝區
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");
使用者功能實現(app_test.c):
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
int fd;
int ret;
char ch;
char buf[100];
fd = open("/dev/mydev", O_RDWR);
if(fd < 0)
{
printf("open file failed\n");
return -1;
}
ret = read(fd, buf, 100); //呼叫核心驅動的myread函式
if(ret < 0)
{
perror("read");
return -1;
}
printf("%s\n", buf);
lseek(fd, 0, SEEK_SET);//呼叫核心驅動的myllseek函式
ret = write(fd, "aaaaaaaaaaaat", 13);//呼叫核心驅動的mywrite函式
if(ret < 0)
{
perror("write");
return -1;
}
lseek(fd, 0, SEEK_SET);//呼叫核心驅動的myllseek函式
while(1)
{
ret = read(fd, &ch, 1);//呼叫核心驅動的myread函式
if(ret < 0)
{
perror("read");
break;
}
else if (ret == 0)
break;
printf("%c\n", ch);
}
close(fd);
return 0;
}
Makefile檔案:
obj-m += test.o
KSRC := /目錄路徑/orangepi_sdk/source/linux-3.4.112/
export ARCH := arm
export CROSS_COMPILE := arm-linux-gnueabihf-
all :
make -C $(KSRC) modules M=`pwd`
.PHONY : clean
clean :
make -C $(KSRC) modules clean M=`pwd`
寫好test.c和Makefile檔案後,進行測試步驟:
1.編譯核心驅動test.ko模組:
make
2.交叉編譯生成使用者功能檔案app:
arm-linux-gnueabifh-gcc app.c -o app
3.載入驅動模組:
insmod test.ko
4.執行功能檔案:
./app
5.解除安裝驅動模組:
rmmod test 或 rmmod test.ko
一個驅動支援多個虛擬檔案的實現(test.c):
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>
#define MYMA 1234
#define MYMI 7788
#define COUNT 3 //三個裝置號,每個裝置號對應一個裝置檔案和一個數據緩衝區
dev_t devid;
struct cdev mycdev;
u8 *data[COUNT]; //驅動資料緩衝區
int dlen = 1024; //驅動資料緩衝區長度
ssize_t myread(struct file *fl, char __user *buf, size_t len, loff_t *off)
{
int mi = MINOR(fl->f_path.dentry->d_inode->i_rdev);
int len_copy, ret, n;
n = mi - MYMI;
if (n >= COUNT)
return -ENODEV;
if ((fl->f_pos + len) > strlen(data[n]))
len_copy = strlen(data[n]) - fl->f_pos;
else
len_copy = len;
ret = copy_to_user(buf, data[n]+fl->f_pos, len_copy);
*off += len_copy - ret;
return len_copy - ret;
}
ssize_t mywrite(struct file *fl, const char __user *buf, size_t len, loff_t *off)
{
int mi = MINOR(fl->f_path.dentry->d_inode->i_rdev);
int len_copy, ret, n;
n = mi - MYMI;
if (n >= COUNT)
return -ENODEV;
if ((fl->f_pos + len) > dlen)
len_copy = dlen - fl->f_pos;
else
len_copy = len;
ret = copy_from_user(data[n] + fl->f_pos, buf, len_copy);
*off += len_copy - ret;
return len_copy - ret;
}
struct file_operations fops = {
.owner = THIS_MODULE,
.read = myread,
.write = mywrite,
};
static int __init test_init(void)
{
int ret, i, j;
devid = MKDEV(MYMA, MYMI);
ret = register_chrdev_region(devid, COUNT, "mydev");
if (ret < 0)
goto err0;
cdev_init(&mycdev, &fops);
mycdev.owner = THIS_MODULE;
ret = cdev_add(&mycdev, devid, COUNT);
if (ret < 0)
goto err1;
for (i = 0; i < COUNT; i++)
{
data[i] = kzalloc(dlen, GFP_KERNEL);
for (j = 0; j < 26; j++)
data[i][j] = 'A' + j;
}
return 0;
err1:
unregister_chrdev_region(devid, COUNT);
err0:
return ret;
}
static void __exit test_exit(void)
{
int i;
unregister_chrdev_region(devid, COUNT);
cdev_del(&mycdev);
for (i = 0; i < COUNT; i++)
kfree(data[i]);
}
module_init(test_init);
module_exit(test_exit);
MODULE_LICENSE("GPL");