文章目錄
- 摘要
- 背景介紹
-
- I/O結構
- 裝置驅動模型
- 裝置驅動Demo
-
- 字元裝置驅動
- 字元裝置驅動-misc使用
- 塊裝置驅動
摘要
簡述計算機組成原理中的I/O裝置,了解裝置與驅動的關系。
沒有介紹裝置驅動模型的抽象(我不知道),給出了參考連結,可自行閱讀。
貼出了字元驅動和塊裝置驅動的示例代碼。
背景介紹
I/O結構
閱讀:計算機組成原理-03-系統總線| 南橋-wiki | Ch8-IO系統 --ppt | I/O控制方式
I/O裝置總的來說,挂載在總線上。(因為我不知道細節,比如說,兩者旁雜着控制器等。但總體看上去,I/O挂載在總線上)
CPU通過指令(軟體)來控制I/O硬體。這個軟體便是驅動程式。(非常粗糙)
I/O裝置有兩個重要的屬性:端口和記憶體。
- 裝置驅動程式要直接通路外設或其接口卡上的實體電路,通常以寄存器的形式出現通路;外設寄存器也稱為I/O端口,通常包括控制寄存器、狀态寄存器和資料寄存器三類。(x通過端口控制I/O裝置)
- 根據端口的訓示,讀寫裝置的記憶體。I/O記憶體可以編址或獨立編址。
Linux文檔中有一篇:Bus-Independent Device Accesses,我不是很明白它在說什麼。(下面三點,我不清楚)
- 記錄I/O資源:linux下I/O資源管理
- 管理I/O端口資源:request_region()、check_region()、release_region()
- 管理I/O記憶體資源:request_mem_region()、check_mem_region()、release_mem_region()
裝置驅動模型
閱讀:統一裝置模型
關于裝置驅動模型的抽象,我不是很明白,可自行參考:Linux裝置模型(1)_基本概念
下面的三個示例,包含字元裝置驅動和塊裝置驅動。(填充一些資料結構)
裝置驅動Demo
代碼來源:動手實踐-編寫字元裝置驅動程式 、工程實踐-塊裝置驅動
由于沒有和實際硬體結合(即缺少驅動代碼控制端口,對I/O的記憶體進行操作),是以這三個示例驅動程式都比較簡單。
但通過這三個驅動demos,簡單能感受到“什麼是驅動”、“linux中一切皆檔案”。
字元裝置驅動
參考:Linux驅動篇(五)–字元裝置驅動(一)
代碼思路:申請裝置号-申請一個cdev結構體-向系統添加一個字元裝置-登出字元裝置
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>
# define DEMO_NAME "my_demo_dev"
static dev_t dev;
static struct cdev *demo_cdev;
static signed count = 1;
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n",__func__,major,minor);
return 0;
}
static ssize_t demodrv_read(struct file *file, char __user *buf,size_t lbuf,loff_t *ppos)
{
printk("%s enter\n",__func__);
return 0;
}
static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *f_pos)
{
printk("%s enter\n",__func__);
return 0;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.read = demodrv_read,
.write = demodrv_write
};
static int __init simple_char_init(void)
{
int ret;
/**
* 向系統動态申請一個未被使用的裝置号;
* (主裝置号用來表示一個特定的驅動程式。次裝置号用來表示使用該驅動程式的各裝置)
* dev:存放傳回的裝置号
* 0:次裝置号的起始值
* count:次裝置号的個數
* DEMO_NAME:(裝置号标簽)
*/
ret = alloc_chrdev_region(&dev,0,count,DEMO_NAME);
if(ret)
{
printk("failed to allocate char device region\n");
return ret;
}
/**
* 申請一個cdev結構體
*/
demo_cdev = cdev_alloc();
if(!demo_cdev)
{
printk("cdev_alloc failed\n");
goto unregister_chrdev;
}
/**
* 初始化cdev:
* INIT_LIST_HEAD(&cdev->list);
* kobject_init(&cdev->kobj, &ktype_cdev_default);
* cdev->ops = fops; // 設定該驅動的檔案操作
*/
cdev_init(demo_cdev,&demodrv_fops);
/**
* 向系統添加一個字元裝置
*/
ret = cdev_add(demo_cdev,dev,count);
if(ret)
{
printk("cdev_add failed\n");
goto cdev_fail;
}
printk("successed register char device: %s\n",DEMO_NAME);
printk("Major number = %d,minor number = %d\n",MAJOR(dev),MINOR(dev));
return 0;
cdev_fail:
cdev_del(demo_cdev);
unregister_chrdev:
unregister_chrdev_region(dev,count);
return ret;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
if(demo_cdev)
cdev_del(demo_cdev);
unregister_chrdev_region(dev,count); // 登出字元裝置時,需要釋放裝置号
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_LICENSE("GPL");
字元裝置驅動-misc使用
參考: 跟着核心學架構-misc子系統 | Linux核心源碼學習之kfifo
Linux 中有三大類裝置:字元,網絡,塊裝置,每一種裝置又細分為很多類,比如字元裝置就被預先分為很多種類,并在檔案中标記了這些種類都使用了哪個主裝置号,但即便如此,硬體千千萬,總還是有漏網之魚,對于這些難以劃分類别的字元裝置,Linux中使用"混雜",裝置來統一描述,并配置設定給他們一個共同的主裝置号10,隻用此裝置号進行區分裝置,,這些裝置主要包括随機數發生器,LCD,時鐘發生器等。此外,和很多同樣是對cdev進行再次封裝的子系統一樣,misc也會自動建立裝置檔案,免得每次寫cdev接口都要使用class_create()和device_create()等。
# include <linux/module.h>
# include <linux/fs.h>
# include <linux/uaccess.h>
# include <linux/init.h>
# include <linux/cdev.h>
//加入misc機制
# include <linux/miscdevice.h>
# include <linux/kfifo.h>
DEFINE_KFIFO(mydemo_fifo,char,64);
//裝置名
# define DEMO_NAME "my_demo_dev"
static struct device *mydemodrv_device;
static int demodrv_open(struct inode *inode, struct file *file)
{
int major = MAJOR(inode->i_rdev);
int minor = MINOR(inode->i_rdev);
printk("%s: major=%d, minor=%d\n",__func__,major,minor);
return 0;
}
static ssize_t demodrv_read(struct file *file, char __user *buf,size_t count,loff_t *ppos)
{
int actual_readed;
int ret;
ret = kfifo_to_user(&mydemo_fifo,buf, count, &actual_readed); // 摘取隊列資料至使用者空間的函數
if(ret)
return -EIO;
printk("%s,actual_readed=%d,pos=%lld\n",__func__,actual_readed,*ppos);
return actual_readed;
}
static ssize_t demodrv_write(struct file *file, const char __user *buf,size_t count,loff_t *ppos)
{
unsigned int actual_write;
int ret;
ret = kfifo_from_user(&mydemo_fifo,buf, count, &actual_write);
if(ret)
return -EIO;
printk("%s: actual_write=%d,ppos=%lld\n",__func__,actual_write,*ppos);
return actual_write;
}
static const struct file_operations demodrv_fops = {
.owner = THIS_MODULE,
.open = demodrv_open,
.read = demodrv_read,
.write = demodrv_write,
};
static struct miscdevice mydemodrv_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEMO_NAME,
.fops = &demodrv_fops,
};
static int __init simple_char_init(void)
{
int ret;
/**
* 主裝置号為10.
* 提供次裝置号,和檔案操作
*/
ret = misc_register(&mydemodrv_misc_device);
if(ret)
{
printk("failed register misc device\n");
return ret;
}
mydemodrv_device = mydemodrv_misc_device.this_device;
printk("successed register char device: %s\n",DEMO_NAME);
return 0;
}
static void __exit simple_char_exit(void)
{
printk("removing device\n");
misc_deregister(&mydemodrv_misc_device);
}
module_init(simple_char_init);
module_exit(simple_char_exit);
MODULE_LICENSE("GPL");
塊裝置驅動
參考: Linux驅動 | 解讀塊裝置驅動的重要概念
建立一個RAM disk。RAM disk是通過使用軟體将RAM模拟當做硬碟來使用的一種技術。
使用gendisk結構體來描述通用磁盤。該結構體中包含了對磁盤的操作方式,請求隊列(處理),裝置号。
代碼思路:填充gendisk結構-将初始化完成的gendisk結構添加到核心中-釋放gendisk結構。
#include <linux/fs.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/errno.h>
#include <linux/hdreg.h>
#include <linux/version.h>
#define MY_DEVICE_NAME "myramdisk"
/**
* RAM盤是通過使用軟體将RAM模拟當做硬碟來使用的一種技術
*/
static int mybdrv_ma_no, diskmb = 256, disk_size;
static char *ramdisk;
static struct gendisk *my_gd;
static spinlock_t lock;
static unsigned short sector_size = 512;
static struct request_queue *my_request_queue;
module_param_named(size, diskmb, int, 0);
static void my_request(struct request_queue *q)
{
struct request *rq;
int size, res = 0;
char *ptr;
unsigned nr_sectors, sector;
pr_info("start handle request\n");
/**
* 從請求隊列中擷取一個請求。
* 在v5中,這個函數同樣被删除
*/
rq = blk_fetch_request(q);
while (rq) {
nr_sectors = blk_rq_cur_sectors(rq); // 目前要傳遞的sector個數
sector = blk_rq_pos(rq); // blk_rq_pos():the current sector
ptr = ramdisk + sector * sector_size;
size = nr_sectors * sector_size;
if ((ptr + size) > (ramdisk + disk_size)) {
pr_err("end of device\n");
goto done;
}
if (rq_data_dir(rq)) { // 處理寫請求
pr_info("writing at sector %d, %u sectors\n",
sector, nr_sectors);
memcpy(ptr, bio_data(rq->bio), size);
} else { // 處理讀請求
pr_info("reading at sector %d, %u sectors\n",
sector, nr_sectors);
memcpy(bio_data(rq->bio), ptr, size);
}
done:
if (!__blk_end_request_cur(rq, res))
rq = blk_fetch_request(q);
}
pr_info("handle request done\n");
}
static int my_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg)
{
long size;
struct hd_geometry geo;
pr_info("cmd=%d\n", cmd);
// 定義了一個io請求:HDIO_GETGEO-get device geometry
switch (cmd) {
case HDIO_GETGEO:
pr_info("HIT HDIO_GETGEO\n");
/*
* get geometry: we have to fake one...
*/
size = disk_size;
size &= ~0x3f;
geo.cylinders = size>>6;
geo.heads = 2;
geo.sectors = 16;
geo.start = 4;
if (copy_to_user((void __user *)arg, &geo, sizeof(geo)))
return -EFAULT;
return 0;
}
pr_warn("return -ENOTTY\n");
return -ENOTTY;
}
static const struct block_device_operations mybdrv_fops = {
.owner = THIS_MODULE,
.ioctl = my_ioctl,
};
static int __init my_init(void)
{
disk_size = diskmb * 1024 * 1024;
spin_lock_init(&lock);
ramdisk = vmalloc(disk_size);
if (!ramdisk)
return -ENOMEM;
/**
* 塊裝置初始化請求隊列,該函數已被删除
* https://lore.kernel.org/lkml/[email protected]/T/
* 每一塊裝置都會有一個隊列,當需要對裝置操作時,把請求放在隊列中
*/
my_request_queue = blk_init_queue(my_request, &lock);
if (!my_request_queue) {
vfree(ramdisk);
return -ENOMEM;
}
/**
* 設定邏輯塊的大小:這應該設定為儲存設備可以尋址的盡可能低的塊大小;預設值512涵蓋了大多數硬體。
*/
blk_queue_logical_block_size(my_request_queue, sector_size);
/**
* 注冊一個新的塊裝置(号)。
* 第一個參數:主裝置号。等于0,表示嘗試主動申請一個未使用的主裝置号。
* 第二個參數:裝置名
*/
mybdrv_ma_no = register_blkdev(0, MY_DEVICE_NAME);
if (mybdrv_ma_no < 0) {
pr_err("Failed registering mybdrv, returned %d\n",
mybdrv_ma_no);
vfree(ramdisk);
return mybdrv_ma_no;
}
/**
* 配置設定一個gendisk(通用磁盤的資料結構)
*/
my_gd = alloc_disk(16);
if (!my_gd) {
unregister_blkdev(mybdrv_ma_no, MY_DEVICE_NAME);
vfree(ramdisk);
return -ENOMEM;
}
my_gd->major = mybdrv_ma_no; // 填充主裝置号
my_gd->first_minor = 0; // 次裝置号,從0開始
my_gd->fops = &mybdrv_fops; // 該裝置的操作
strcpy(my_gd->disk_name, MY_DEVICE_NAME); // 裝置名
my_gd->queue = my_request_queue; // 裝置操作的請求隊列
set_capacity(my_gd, disk_size / sector_size); // 設定容量,以sector_size為機關
add_disk(my_gd); // 将初始化完成的gendisk結構添加到核心中
pr_info("device successfully registered, Major No. = %d\n",
mybdrv_ma_no);
pr_info("Capacity of ram disk is: %d MB\n", diskmb);
return 0;
}
static void __exit my_exit(void)
{
del_gendisk(my_gd);
put_disk(my_gd);
unregister_blkdev(mybdrv_ma_no, MY_DEVICE_NAME);
pr_info("module successfully unloaded, Major No. = %d\n", mybdrv_ma_no);
blk_cleanup_queue(my_request_queue);
vfree(ramdisk); // ramdisk釋放磁盤空間
}
module_init(my_init);
module_exit(my_exit);
MODULE_AUTHOR("Benshushu");
MODULE_LICENSE("GPL v2");