linux塊裝置驅動程式示例(適用於高版本核心3.16.0
(1)塊裝置只能以塊為單位接受輸入和返回輸出,而字元裝置則以位元組為單位。大多數裝置是字元裝置,因為它們不需要緩衝而且不以固定塊大小進行操作。
(2)塊裝置對於 I/O 請求有對應的緩衝區,因此它們可以選擇以什麼順序進行響應,字元裝置無須緩衝且被直接讀寫。對於儲存裝置而言調整讀寫的順序作用巨大,因為在讀寫連續的扇區比分離的扇區更快。
(3)字元裝置只能被順序讀寫,而塊裝置可以隨機訪問。雖然塊裝置可隨機訪問,但是對於磁碟這類機械裝置而言,順序地組織塊裝置的訪問可以提高效能,而對 SD 卡、RamDisk 等塊裝置而言,不存在機械上的原因,進行這樣的調整沒有必要。
2. 塊裝置例程
將ldd3(linxu device driver 3)中塊裝置驅動部分的程式碼在kerne 3.19.0 編譯時,會出現很多問題,主要是由於ldd3示例程式碼使用的核心版本較低(2.6.10版本),對於塊裝置子系統,很多介面都已經發生了改變,修改後的驅動程式碼如下ramdisk.c(親測可用):
#include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/types.h> #include <linux/fcntl.h> #include <linux/vmalloc.h> #include <linux/blkdev.h> #include <linux/hdreg.h> #define RAMDISK_NAME "ramdisk" #define RAMDISK_MAX_DEVICE 1 #define RAMDISK_MAX_PARTITIONS 4 #define RAMDISK_SECTOR_SIZE 512 #define RAMDISK_SECTORS 16 #define RAMDISK_HEADS 4 #define RAMDISK_CYLINDERS 256 #define RAMDISK_SECTOR_TOTAL (RAMDISK_SECTORS * RAMDISK_HEADS * RAMDISK_CYLINDERS * 50) //16383 sectors #define RAMDISK_SIZE (RAMDISK_SECTOR_SIZE * RAMDISK_SECTOR_TOTAL) //8MB = 16383 x 512k typedef struct { unsigned char* data; struct request_queue* queue; struct gendisk* gd; }RAMDISK_DEV; static char* sdisk[RAMDISK_MAX_DEVICE] = {NULL}; static RAMDISK_DEV* rdev[RAMDISK_MAX_DEVICE] = {NULL}; static dev_t ramdisk_major; static int ramdisk_space_init(void) { int i; int err = 0; for(i = 0; i < RAMDISK_MAX_DEVICE; i++){ sdisk[i] = vmalloc(RAMDISK_SIZE); printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); if(!sdisk[i]){ err = -ENOMEM; return err; } memset(sdisk[i], 0, RAMDISK_SIZE); } return err; } static void ramdisk_space_clean(void) { int i; printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); for(i = 0; i < RAMDISK_MAX_DEVICE; i++){ vfree(sdisk[i]); } } static int ramdisk_open(struct block_device* bdev, fmode_t mode) { printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); return 0; } static void ramdisk_release(struct gendisk*gd, fmode_t mode) { printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); return; } static int ramdisk_ioctl(struct block_device* bdev, fmode_t mode, unsigned int cmd, unsigned long arg) { int err; struct hd_geometry geo; printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); switch(cmd) { case HDIO_GETGEO: err = !access_ok(VERIFY_WRITE, arg, sizeof(geo)); if(err) return -EFAULT; geo.cylinders = RAMDISK_CYLINDERS; geo.heads = RAMDISK_HEADS; geo.sectors = RAMDISK_SECTORS; geo.start = get_start_sect(bdev); printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); if(copy_to_user((void*)arg, &geo, sizeof(geo))) return -EFAULT; return 0; } return -ENOTTY; } static struct block_device_operations ramdisk_fops = { .owner = THIS_MODULE, .open = ramdisk_open, .release = ramdisk_release, .ioctl = ramdisk_ioctl, }; static int ramdisk_make_request(struct request_queue* q, struct bio* bio) { char* pRHdata; char* pBuffer; struct bio_vec bvec; int err = 0; printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); struct block_device* bdev = bio->bi_bdev; RAMDISK_DEV* pdev = bdev->bd_disk->private_data; if(((bio->bi_iter.bi_sector * RAMDISK_SECTOR_SIZE) + bio->bi_iter.bi_size) > RAMDISK_SIZE){ err = -EIO; return err; } pRHdata = pdev->data + (bio->bi_iter.bi_sector * RAMDISK_SECTOR_SIZE); bio_for_each_segment(bvec, bio, bio->bi_iter){ pBuffer = kmap(bvec.bv_page) + bvec.bv_offset; switch(bio_data_dir(bio)){ case READ: printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); memcpy(pBuffer, pRHdata, bvec.bv_len); flush_dcache_page(bvec.bv_page); break; case WRITE: printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); flush_dcache_page(bvec.bv_page); memcpy(pRHdata, pBuffer, bvec.bv_len); break; default: printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); kunmap(bvec.bv_page); goto out; } kunmap(bvec.bv_page); pRHdata += bvec.bv_len; } out: bio_endio(bio, err); return 0; } static int alloc_ramdev(void) { int i; printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); for(i = 0; i < RAMDISK_MAX_DEVICE; i++){ rdev[i] = kzalloc(sizeof(RAMDISK_DEV), GFP_KERNEL); if(!rdev[i]){ return -ENOMEM; } } return 0; } static void clean_ramdev(void) { int i; printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); for(i = 0; i < RAMDISK_MAX_DEVICE; i++){ if(rdev[i]) kfree(rdev[i]); } } static int __init ramdisk_init(void) { int i; ramdisk_space_init(); alloc_ramdev(); printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); ramdisk_major = register_blkdev(0, RAMDISK_NAME); for(i = 0; i < RAMDISK_MAX_DEVICE; i++){ rdev[i]->data = sdisk[i]; rdev[i]->queue = blk_alloc_queue(GFP_KERNEL); blk_queue_make_request(rdev[i]->queue, ramdisk_make_request); rdev[i]->gd = alloc_disk(RAMDISK_MAX_PARTITIONS); rdev[i]->gd->major = ramdisk_major; rdev[i]->gd->first_minor = i * RAMDISK_MAX_PARTITIONS; rdev[i]->gd->fops = &ramdisk_fops; rdev[i]->gd->queue = rdev[i]->queue; rdev[i]->gd->private_data = rdev[i]; sprintf(rdev[i]->gd->disk_name, "ramdisk%c", 'A' +i); rdev[i]->gd->flags |= GENHD_FL_SUPPRESS_PARTITION_INFO; set_capacity(rdev[i]->gd, RAMDISK_SECTOR_TOTAL); add_disk(rdev[i]->gd); } return 0; } static void __exit ramdisk_exit(void) { int i; printk(KERN_EMERG "%s*********Start*******%d \n", __FUNCTION__, __LINE__); for(i = 0; i < RAMDISK_MAX_DEVICE; i++){ del_gendisk(rdev[i]->gd); put_disk(rdev[i]->gd); blk_cleanup_queue(rdev[i]->queue); } clean_ramdev(); ramdisk_space_clean(); unregister_blkdev(ramdisk_major, RAMDISK_NAME); } module_init(ramdisk_init); module_exit(ramdisk_exit); MODULE_AUTHOR("zkj"); MODULE_DESCRIPTION("block device"); MODULE_LICENSE("GPL");
Makefile參考:
KVERS = $(shell uname -r) CURDIR = $(shell pwd) # Kernel modules obj-m += ramdisk.o # Specify flags for the module compilation. #EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: # make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules make -C ../linux-3.16 M=$(CURDIR) modules clean: # make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean make -C ../linux-3.16 M=$(CURDIR) clean
Terminal下編譯(arm平臺):
[email protected]:~/WorkSpace/kernel-src/ramdisk$ make CROSS_COMPILE=arm-linux- ARCH=arm核心模組載入:
insmod ramdisk.ko
檢視載入情況:
#sudo insmod ramdisk.ko
#
下面內容參考:http://blog.csdn.net/rocky_zhm/article/details/50372243
用lsmod看看。這裡我們注意到,該模組的Used by為0,因為它既沒有被其他模組使用,也沒有被mount。
# lsmod
Module Size Used by
simp_blkdev 16784008 0
...
#
如果當前系統支援udev,在呼叫add_disk()函式時即插即用機制會自動為我們在/dev/目錄下建立裝置檔案。
裝置檔案的名稱為我們在gendisk.disk_name中設定的simp_blkdev,主、從裝置號也是我們在程式中設定的72和0。
如果當前系統不支援udev,那麼很不幸,你需要自己用mknod /dev/simp_blkdev b 72 0來建立裝置檔案了。
# ls -l /dev/ramdiskA
brw-rw---- 1 root disk 251, 0 12月 21 14:23 /dev/ramdiskA
建立塊裝置中的分割槽
sudo fdisk /dev/ramdiskADevice contains neither a valid DOS partition table, nor Sun, SGI or OSF disklabel
Building a new DOS disklabel with disk identifier 0x7670efa4.
Changes will remain in memory only, until you decide to write them.
After that, of course, the previous content won't be recoverable.
Warning: invalid flag 0x0000 of partition table 4 will be corrected by w(rite)
Command (m for help): n // 建立一個新分割槽
Partition type:
p primary (0 primary, 0 extended, 4 free)
e extended
Select (default p): p //選擇分割槽型別,主分割槽(p)
Partition number (1-4, default 1): 1 // 分割槽編號,預設為1
First sector (2048-819199, default 2048): 2048 //分割槽起始sector預設為2048
Last sector, +sectors or +size{K,M,G} (2048-819199, default 819199): 200000 //設定分割槽大小
Command (m for help): w //儲存設定的分割槽資訊
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.
在塊裝置中建立檔案系統,這裡我們建立常用的ext3。
當然,作為通用的塊裝置,建立其他型別的檔案系統也沒問題。
# mkfs.ext3 /dev/ramdiskA
mke2fs 1.39 (29-May-2006)
Filesystem label=
OS type: Linux
Block size=1024 (log=0)
Fragment size=1024 (log=0)
4096 inodes, 16384 blocks
819 blocks (5.00%) reserved for the super user
First data block=1
Maximum filesystem blocks=16777216
2 block groups
8192 blocks per group, 8192 fragments per group
2048 inodes per group
Superblock backups stored on blocks:
8193
Writing inode tables: done
Creating journal (1024 blocks): done
Writing superblocks and filesystem accounting information: done
This filesystem will be automatically checked every 38 mounts or
180 days, whichever comes first. Use tune2fs -c or -i to override.
#
如果這是第一次使用,建議建立一個目錄用來mount這個裝置中的檔案系統。
當然,這不是必需的。如果你對mount之類的用法很熟,你完全能夠自己決定在這裡幹什麼,甚至把這個裝置mount成root。
# mkdir -p /mnt/temp1
#
把建立好檔案系統的塊裝置mount到剛才建立的目錄中
# mount /dev/ramdiskA /mnt/temp1
#
看看現在的mount表
# mount
...
/dev/ramdiskA on /mnt/temp1 type ext3 (rw)
#
看看現在的模組引用計數,從剛才的0變成1了,
原因是我們mount了。
# lsmod
Module Size Used by
ramdiskA 16784008 1
...
#
看看檔案系統的內容,有個mkfs時自動建立的lost+found目錄。
# ls /mnt/temp1
lost+found
#
隨便拷點東西進去
# cp /etc/init.d/* /mnt/temp1
#
再看看
# ls /mnt/temp1
acpid conman functions irqbalance mdmpd NetworkManagerDispatcher rdisc sendmail winbind
anacron cpuspeed gpm kdump messagebus nfs readahead_early setroubleshoot wpa_supplicant
apmd crond haldaemon killall microcode_ctl nfslock readahead_later single xfs
atd cups halt krb524 multipathd nscd restorecond smartd xinetd
auditd cups-config-daemon hidd kudzu netconsole ntpd rhnsd smb ypbind
autofs dhcdbd ip6tables lost+found netfs pand rpcgssd sshd yum-updatesd
avahi-daemon dund ipmi lvm2-monitor netplugd pcscd rpcidmapd syslog
avahi-dnsconfd firstboot iptables mcstrans network portmap rpcsvcgssd vmware
bluetooth frecord irda mdmonitor NetworkManager psacct saslauthd vncserver
#
現在這個塊裝置的使用情況是
# df
檔案系統 1K-塊 已用 可用 已用% 掛載點
...
/dev/ramdiskA 15863 1440 13604 10% /mnt/temp1
#
再全刪了玩玩
# rm -rf /mnt/temp1/*
#
看看刪完了沒有
# ls /mnt/temp1
#
好了,大概玩夠了,我們把檔案系統umount掉
# umount /mnt/temp1
#
模組的引用計數應該還原成0了吧
# lsmod
Module Size Used by
ramdiskA 16784008 0
...
#
最後一步,移除模組
# sudo rmmod ramdiskA
#