字元裝置關鍵結構體
struct cdev結構體
// include/linux/cdev.h
struct cdev {
struct kobject kobj;
struct module *owner;
// 驅動裝置的操作接口結構體指針,例如讀、寫、控制等操作
const struct file_operations *ops;
// 連結清單,用來記錄多個cdev結構體對象的記憶體位置
struct list_head list;
// 裝置号,由主裝置号和次裝置号共同合成
dev_t dev;
// 使用該結構體資訊的裝置數量
unsigned int count;
};
struct file_operations結構體
// include/linux/fs.h
struct file_operations {
struct module *owner;
// 定位函數指針,用來定位裝置驅動中的檔案,與使用者端的lseek對應
loff_t (*llseek) (struct file *, loff_t, int);
// 讀函數指針,用來讀裝置驅動中的檔案,與使用者端的read對應
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
// 寫函數指針,用來寫裝置驅動中的檔案,與使用者端的write對應
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
// io控制函數指針,用來控制裝置驅動中的IO檔案,與使用者端的ioctl對應
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
// 打開函數指針,用來打開裝置驅動中的檔案,與使用者端的open對應
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
};
字元裝置驅動概述
- 字元裝置對應着cdev、file_operation這2個結構體。
- 字元裝置驅動對應着file_operation中的操作接口,類似read、write、ioctl等。
- 使用者空間使用字元裝置的方法是通過條用使用者态的read、write、ioctl等接口,來間接調用字元裝置驅動中的接口,進而達到使用驅動的目的。
- 字元裝置使用的前提是,先加載子產品函數,在加載函數中,先初始化cdev、file_operation結構體,建立對象,然後将這些結構體資訊綁定到裝置驅動的kobject map上;如果不想使用,需要解除安裝這個字元裝置時,就要調用解除安裝函數,在解除安裝函數中,先解除與kobject map的綁定,然後将上面那些結構體對象釋放掉。
字元裝置執行個體
/**
** This file is part of the LinuxTrainningHome project.
** Copyright(C) duanzhonghuan Co., Ltd.
** All Rights Reserved.
** Unauthorized copying of this file, via any medium is strictly prohibited
** Proprietary and confidential
**
** Written by ZhongHuan Duan <[email protected]>, 2019-05-12
**/
#include "linux/init.h"
#include "linux/module.h"
#include "linux/kdev_t.h"
#include "linux/cdev.h"
#include "linux/fs.h"
#include "linux/slab.h"
#include "linux/uaccess.h"
#define GLOBALMEM_SIZE (0X1000)
#define GLOBALMEM_MAJOR (230)
#define DEVICE_NUM (1)
#define MEM_CLEAR (0x01)
static int globalmem_major = GLOBALMEM_MAJOR;
#define globalmem_debug
/**
* @brief The globalmem_dev struct - the description of the global memory
*/
struct globalmem_dev
{
struct cdev chrdev;
unsigned char mem[GLOBALMEM_SIZE];
};
static struct globalmem_dev *globalmem_devp = NULL;
/**
* @brief globalmem_llseek - reposition read/write file offset
* @param filep: the file pointer
* @param offset: the offset
* @param whence: SEEK_SET,SEEK_CUR,SEEK_END
* @return: the actual offset, return -1 when failed
*/
static loff_t globalmem_llseek (struct file *filep, loff_t offset, int whence)
{
loff_t ret = 0;
switch (whence)
{
case SEEK_SET:
if (offset < 0 || offset > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filep->f_pos = offset;
ret = offset;
break;
case SEEK_CUR:
if (offset < 0 || filep->f_pos + offset > GLOBALMEM_SIZE)
{
ret = -EINVAL;
break;
}
filep->f_pos += offset;
ret = filep->f_pos;
break;
case SEEK_END:
filep->f_pos = GLOBALMEM_SIZE;
ret = filep->f_pos;
break;
default:
ret = -EINVAL;
break;
}
return ret;
}
/**
* @brief globalmem_read
* @param filep
* @param buf
* @param count
* @param ppos
* @return
*/
static ssize_t globalmem_read (struct file *filep, char __user *buf, size_t count, loff_t *ppos)
{
ssize_t ret = 0;
loff_t curpos = *ppos;
struct globalmem_dev *dev;
#ifdef globalmem_debug
printk("globalmem_read\n");
#endif
if (curpos >= GLOBALMEM_SIZE)
{
return 0;
}
dev = filep->private_data;
if (!dev)
{
ret = -ENOMEM;
return ret;
}
if (curpos + count > GLOBALMEM_SIZE)
{
count = GLOBALMEM_SIZE - curpos;
}
ret = copy_to_user(buf, dev->mem + curpos, count);
*ppos += count;
ret = count;
return ret;
}
static ssize_t globalmem_write (struct file *filep, const char __user *buf, size_t count, loff_t *ppos)
{
ssize_t ret = 0;
loff_t curpos = *ppos;
struct globalmem_dev *dev;
#ifdef globalmem_debug
printk("globalmem_write\n");
#endif
if (curpos >= GLOBALMEM_SIZE)
{
ret = -ENAVAIL;
return ret;
}
dev = filep->private_data;
if (!dev)
{
ret = -ENOMEM;
return ret;
}
if (curpos + count > GLOBALMEM_SIZE)
{
count = GLOBALMEM_SIZE - curpos;
}
ret = copy_from_user(dev->mem + curpos, buf, count);
*ppos += count;
ret = count;
return ret;
}
/**
* @brief globalmem_unlocked_ioctl
* @param filep
* @param cmd
* @param arg
* @return
*/
static long globalmem_unlocked_ioctl (struct file *filep, unsigned int cmd, unsigned long arg)
{
struct globalmem_dev *dev = filep->private_data;
switch (cmd)
{
case MEM_CLEAR:
memset(dev->mem, 0, GLOBALMEM_SIZE);
printk("mem clear success\n");
break;
default:
break;
}
return 0;
}
static int globalmem_open (struct inode *inode, struct file *filep)
{
struct globalmem_dev *dev = container_of(inode->i_cdev, struct globalmem_dev, chrdev);
filep->private_data = dev;
#ifdef globalmem_debug
printk("globalmem_open\n");
#endif
return 0;
}
static int globalmem_release (struct inode *inode, struct file *filep)
{
return 0;
}
static struct file_operations chrdev_file_operations =
{
.owner = THIS_MODULE,
.llseek = globalmem_llseek,
.read = globalmem_read,
.write = globalmem_write,
.unlocked_ioctl = globalmem_unlocked_ioctl,
.open = globalmem_open,
.release = globalmem_release,
};
/**
* @brief globalmem_init_dev - initialize the global memory decice
* @param chrdev: out parameter for a char device
* @param index: the minor device id
*/
static void globalmem_init_dev(struct globalmem_dev *chrdev, int index)
{
int ret = 0;
dev_t devno = MKDEV(globalmem_major, index);
cdev_init(&chrdev->chrdev, &chrdev_file_operations);
// add the globalmem device structure to the kobj_map
ret = cdev_add(&chrdev->chrdev, devno, 1);
if (ret)
{
printk("Error code = %d when adding globalmem %d\n", ret, index);
}
}
/**
* @brief globalmem_init - the function of initializing the global memory
* @return: the status of initializing the global memory
*/
static int __init globalmem_init(void)
{
int i = 0;
int ret = 0;
// 1. get the device id
dev_t devno = MKDEV(globalmem_major, 0);
#ifdef globalmem_debug
printk(KERN_NOTICE "globalmem_init\n");
#endif
// 2. register the DEVICE_NUM of the char device
if (globalmem_major)
{
ret = register_chrdev_region(devno, DEVICE_NUM, "globalmem");
}
else
{
// automatic register char device
ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, "globalmem");
}
// check the error code
if (ret < 0)
{
return ret;
}
// 3. construct the globalmem devices structure in the heap
globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * DEVICE_NUM, GFP_KERNEL);
if (!globalmem_devp)
{
ret = -ENOMEM;
goto fail_malloc;
}
// 4. add the globalmem decices structure pointer to the kobjct map
for (i = 0; i < DEVICE_NUM; i++)
{
globalmem_init_dev(globalmem_devp + i, i);
}
printk("globalmem_init success\n");
return 0;
fail_malloc:
unregister_chrdev_region(devno, DEVICE_NUM);
return ret;
}
/**
* @brief globalmem_exit - exit the glboalmem device
*/
static void __exit globalmem_exit(void)
{
int i = 0;
#ifdef globalmem_debug
printk(KERN_NOTICE "globalmem_exit\n");
#endif
// 1. remove the globalmem structure from teh kobject map
for (i = 0; i < DEVICE_NUM; i++)
{
cdev_del(&(globalmem_devp + i)->chrdev);
}
// 2. free the glboalmem structure in the heap
kfree(globalmem_devp);
// 3. unregister the device id
unregister_chrdev_region(MKDEV(globalmem_major, 0), DEVICE_NUM);
// 4. remove the device id
printk("globalmem_exit success\n");
}
module_init(globalmem_init)
module_exit(globalmem_exit)
// the declaration of the author
MODULE_AUTHOR("ZhongHuan Duan <[email protected]>");
// the declaration of the licence
MODULE_LICENSE("GPL v2");
編譯該檔案使用的Makefile内容為:
KVERS = $(shell uname -r)
obj-m += globalmem.o
#EXTRA_CFLAGS=-g -o0
build: kernel_modules
kernel_modules:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules
clean:
make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
編譯得到.ko檔案後,調整.ko檔案權限,然後測試驗證。
執行個體測試結果
- 加載.ko子產品,建立globamem裝置節點
sudo insmod globalmem.ko
sudo mknod /dev/globalmem c 230 0
sudo chmod 777 /dev/globalmem
- 往globalmem節點 中寫入hello wrold
echo "hello world" > /dev/globalmem
- 讀取glboalmem節點中的資料
cat /dev/globalmem
虛拟檔案系統有關globalmem的資訊
- 檢視/proc檔案中關于globalmem的資訊
cat /proc/devices | grep globalmem
- 檢視/sys檔案中關于globalmem的資訊
tree /sys/module/globalmem
總結
- 字元裝置是3大類裝置中的一類,驅動加載初始化步驟為申請裝置号,構造并初始化相關結構體對象,綁定結構體對象到kobject map上。
- 驅動解除安裝的步驟與前面相反,解綁定kobject map上的結構體對象,釋放結構體空間,登出申請的裝置号。
- 字元裝置驅動函數主要實作file_operations結構體中的函數指針即可;實作形式可以參考使用者空間調用的對應接口來實作。