字符驅動程序用于與Linux內核中的設備進行交互;
字符設備指的是像內存區(qū)域這樣的硬件組件,通常稱為偽設備;
用戶空間應用程序通常使用open
read
write
等系統調用與這些設備通信;
虛擬文件系統 VFS
把用戶空間的系統調用連接到設備驅動的系統調用實現方法上。
內核的虛擬文件系統 virtual file system,在內核空間
設備驅動需要使用內核的API向虛擬文件系統注冊
設備號
Major numbers(指示特定的驅動) + Minor numbers(表示指定的設備文件)
設備創(chuàng)建時候在VFS注冊設備號,虛擬文件系統,將設備文件的設備號與驅動程序列表進行比較,選擇正確的驅動程序,并將用戶請求連接到對應驅動程序的文件操作方法。
相關Kernel APIs
kernel functions and data structures(Creation) | (Deletion) | kernel header file |
---|---|---|
alloc_chrdev_region() |
unregister_chrdev_region() |
include/linux/fs.h |
cdev_init() cdev_add()
|
cdev_del() |
include/linux/cdev.h |
device_creat() class_creat()
|
device_destory() class_destory
|
include/linux/device.h |
copy_to_user() copy_from_user()
|
include/linux/uaccess.h |
|
VFS structure definitions |
include/linux/cdev.h |
動態(tài)申請設備號
alloc_chrdev_region()
可以動態(tài)申請主設備號,保證唯一性,傳輸設備號(dev_t [u32]
)地址和次設備號起始(一般0)和個數。
dev_t device_number; //32bit
int minor_no = MINOR(device_number); //后20bit `kdev_t.h`
int major_no = MAJOR(device_number); //前12bit
MKDEV(int major, int minor);
動態(tài)創(chuàng)建設備文件
當收到uevent
,udev
根據uevent內存儲的細節(jié)在dev
目錄下創(chuàng)建設備文件。
class_create
:在sysf中創(chuàng)建一個目錄/sys/Class/<your_class_name>
device_create
:在上面目錄下使用設備名創(chuàng)建一個子目錄/sys/Class/<your_class_name>/<your_device_name> /dev
這里的dev文件存儲設備名主副設備號等udev
:用戶空間的應用,動態(tài)創(chuàng)建設備文件/sys/Class/<your_class_name>/<your_device_name> /dev
--> dev/your_device_name
內核空間和用戶空間的數據交換
用戶空間的指針不是完全可信的,用戶地址空間有時可能無效,虛擬內存管理器可以交換出這些內存位置。
內核級代碼不能直接引用用戶級內存指針;
使用內核數據復制工具copy_to_user
copy_from_user
。工具會檢查用戶空間指針是否有效
系統調用方法
read
用戶級進程執(zhí)行read
系統調用從文件中讀取。文件可以是普通文件,也可以是一個設備文件(處理具體設備)。
例如前面的偽字符設備,有一塊內存數組(設備內存buffer)。當用戶程序在該設備文件上發(fā)出read系統調用時,應該將數據從設備buffer傳到用戶buffer。該數據拷貝發(fā)生在內核端到用戶端。
write
將數據從用戶空間復制到內核空間,
用戶程序想把一些數據寫入設備內存buffer。
lseek
改變f_pos(struct file)
變量的位置,將文件位置指針向前/向后移動。
寫一個偽字符設備驅動
- 動態(tài)申請設備號
- 創(chuàng)建
cdev
結構體變量和file_operiations
結構體變量 - 使用fops初始化字符設備結構體變量
- 向內核VFS注冊設備
- 實現
file operiation
的方法 - 初始化
file operiation
變量 - 創(chuàng)建設備文件
class_create()
device_create()
- 驅動清理函數功能實現
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#include <linux/uaccess.h>
#include <uapi/asm-generic/errno-base.h>
#define DEV_MEM_SIZE 512
/* pseudo device's memory */
char device_buffer[DEV_MEM_SIZE];
/* This hold the device number */
dev_t device_number;
/* Cdev variable */
struct cdev pcd_cdev;
loff_t pcd_llseek(struct file *filp, loff_t offset, int whence)
{
pr_info("%s\n", __func__);
loff_t temp;
switch (whence)
{
case SEEK_SET:
if ((offset > DEV_MEM_SIZE) || (offset < 0))
return -EINVAL;
filp->f_pos = offset;
break;
case SEEK_CUR:
temp = filp->f_pos + offset;
if ((temp > DEV_MEM_SIZE) || (offset < 0))
return -EINVAL;
filp->f_pos = temp;
break;
case SEEK_END:
temp = DEV_MEM_SIZE + offset;
if ((temp > DEV_MEM_SIZE) || (offset < 0))
return -EINVAL;
filp->f_pos = temp;
break;
default:
return -EINVAL;
}
pr_info("New value of the file position = %lld\n", filp->f_pos);
return filp->f_pos;
// return 0;
}
ssize_t pcd_read(struct file *filp, char __user *buff, size_t count, loff_t *f_pos)
{
pr_info("%s :Read requested for %zu bytes\n", __func__, count);
if((*f_pos + count) > DEV_MEM_SIZE)
count = DEV_MEM_SIZE - *f_pos;
if(copy_to_user(buff, &device_buffer[*f_pos], count)){
return -EFAULT;
}
*f_pos += count;
pr_info("Number of bytes successful read = %zu\n", count);
pr_info("Update file position = %lld\n", *f_pos);
return count;
}
ssize_t pcd_write(struct file *filp, const char __user *buff, size_t count, loff_t *f_pos)
{
pr_info("%s :Write requested for %zu bytes, current file position = %lld\n", __func__, count, *f_pos);
if((*f_pos + count) > DEV_MEM_SIZE)
count = DEV_MEM_SIZE - *f_pos;
if(!count)
return -ENOMEM;
if(copy_from_user(&device_buffer[*f_pos], buff, count)){
return -EFAULT;
}
*f_pos += count;
pr_info("Number of bytes successful writtens = %zu\n", count);
pr_info("Update file position = %lld\n", *f_pos);
return count;
}
int pcd_open(struct inode *inode, struct file *filp)
{
pr_info("%s\n", __func__);
return 0;
}
int pcd_release(struct inode *inode, struct file *filp)
{
pr_info("%s\n", __func__);
return 0;
}
/* file operations variable */
struct file_operations pcd_fops = {
.open = pcd_open,
.write = pcd_write,
.read = pcd_read,
.llseek = pcd_llseek,
.release = pcd_release,
.owner = THIS_MODULE
};
struct class *class_pcd;
struct device *device_pcd;
static int __init pcd_driver_init(void)
{
pr_info("pcd_driver_init\n");
/* 1. Dynamically allocate a device number */
alloc_chrdev_region(&device_number, 0, 1, "pcd");
pr_info("Device number <major>:<minor> = %d:%d\n", MAJOR(device_number), MINOR(device_number));
/* 2. Initialize the cdev structure with fops */
cdev_init(&pcd_cdev, &pcd_fops);
/* 3. Register a device(cdev structure) with VFS */
pcd_cdev.owner = THIS_MODULE;
cdev_add(&pcd_cdev, device_number, 1);
/* creat device class under /sys/class / */
class_pcd = class_create(THIS_MODULE, "pcd_class");
/* populate the sysfs with device information */
device_pcd = device_create(class_pcd, NULL, device_number, NULL, "pcd");
pr_info("Module init was successful\n");
return 0;
}
/* This is module clean-up entry point */
static void __exit pcd_driver_exit(void)
{
pr_info("my hello module exit\n");
device_destroy(class_pcd, device_number);
class_destroy(class_pcd);
cdev_del(&pcd_cdev);
unregister_chrdev_region(device_number, 1);
pr_info("module unloaded\n");
}
/* registration */
module_init(pcd_driver_init);
module_exit(pcd_driver_exit);
/* This is description information about the module */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("NAME");
MODULE_DESCRIPTION("A pseudo device driver");
在主機上測試pcd(HOST)
使用echo
命令測試向PCD寫數據
使用cat
命令測試從PCD讀數據
在目標板上測試pcd(TARGET)
需要在用戶空間寫一個應用程序(測試應用)來測試字符設備驅動程序。使用對應目標板的編譯工具鏈編譯.c
文件成目標板上的可執(zhí)行文件,有沒有.exe
后綴都可,自己知道就行。
arm-buildroot-linux-gnueabihf-gcc ./pcd_drv_test.c -o pcd_dev_test
將上面設備驅動編譯出的目標板的.ko
文件和我們的測試應用文件都放到目標板上。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
/*
* ./pcd_drv_test -w hello fpn233~
* ./pcd_drv_test -r
*/
int main(int argc, char **argv)
{
int fd;
char buf[512];
int len;
/* 1. 判斷參數 */
if (argc < 2)
{
printf("Usage: %s -w <string>\n", argv[0]);
printf(" %s -r\n", argv[0]);
return -1;
}
/* 2. 打開文件 */
fd = open("/dev/pcd", O_RDWR);
if (fd == -1)
{
printf("can not open file /dev/pcd\n");
return -1;
}
/* 3. 寫文件或讀文件 */
if ((0 == strcmp(argv[1], "-w")) && (argc == 3))
{
len = strlen(argv[2]) + 1;
len = len < 512 ? len : 512;
write(fd, argv[2], len);
}
else
{
len = read(fd, buf, 512);
buf[1023] = '\0';
printf("APP read : %s\n", buf);
}
close(fd);
return 0;
}
文章來源:http://www.zghlxwxcb.cn/news/detail-787395.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-787395.html
到了這里,關于LDD學習筆記 -- Linux字符設備驅動的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!