Linux內核版本: 3.5
測驗設備: 友善之臂Tiny4412開發板 (CPU型號: 三星Exynos4412)
2019年三星發布的Galaxy S II和MX采用的就是這款CPU,
一、塊設備介紹
塊是一種具有一定結構的隨機存取設備,對這種設備的讀寫是按塊進行的,他使用緩沖區來存放暫時的資料,待條件成熟后,從快取一次性寫入設備或者從設備一次性讀到緩沖區,
塊設備是與字符設備并列的概念, 這兩類設備在 Linux 中驅動的結構有較大差異,總體而言, 塊設備驅動比字符設備驅動要復雜得多,在 I/O 操作上表現出極大的不同,緩沖、 I/O 調度、請求佇列等都是與塊設備驅動相關的概念,
在Linux中,驅動對塊設備的輸入或輸出(I/O)操作,都會向塊設備發出一個請求,在驅動中用request結構體描述,但對于一些磁盤設備而言請求的速度很慢,這時候內核就提供一種佇列的機制把這些I/O請求添加到佇列中(即:請求佇列),在驅動中用request_queue結構體描述,在向塊設備提交這些請求前內核會先執行請求的合并和排序預操作,以提高訪問的效率,然后再由內核中的I/O調度程式子系統來負責提交 I/O 請求, 調度程式將磁盤資源分配給系統中所有掛起的塊 I/O 請求,其作業是管理塊設備的請求佇列,決定佇列中的請求的排列順序以及什么時候派發請求到設備,
由通用塊層(Generic Block Layer)負責維持一個I/O請求在上層檔案系統與底層物理磁盤之間的關系,在通用塊層中,通常用一個bio結構體來對應一個I/O請求,
Linux提供了一個gendisk資料結構體,用來表示一個獨立的磁盤設備或磁區,用于對底層物理磁盤進行訪問,在gendisk中有一個類似字符設備中file_operations的硬體操作結構指標,是block_device_operations結構體,
撰寫塊設備驅動時,使用的一些單位介紹:
1. 扇區(Sectors):任何塊設備硬體對資料處理的基本單位,通常,1個扇區的大小為512位元組,(對設備而言)
2. 塊 (Blocks):由Linux制定對內核或檔案系統等資料處理的基本單位,通常,1個塊由1個或多個扇區組成,(對Linux作業系統而言)
3. 段(Segments):由若干個相鄰的塊組成,是Linux記憶體管理機制中一個記憶體頁或者記憶體頁的一部分,

IO調度器
就是電梯演算法,我們知道,磁盤是的讀寫是通過機械性的移動磁頭來實作讀寫的,理論上磁盤設備滿足塊設備的隨機讀寫的要求,但是出于節約磁盤,提高效率的考慮,我們希望當磁頭處于某一個位置的時候,一起將最近需要寫在附近的資料寫入,而不是這寫一下,那寫一下然后再回來,IO調度就是將上層發下來的IO請求的順序進行重新排序以及對多個請求進行合并,這樣就可以實作上述的提高效率、節約磁盤的目的,這種解決問題的思路使用電梯演算法,一個運行中的電梯,一個人20樓->1樓,另外一個人15->5樓,電梯不會先將第一個人送到1樓再去15樓接第二個人將其送到5樓,而是從20樓下來,到15樓的時候停下接人,到5樓將第二個放下,最后到達1樓,一句話,電梯演算法最終服務的優先順序并不按照按按鈕的先后順序,
Linux內核中提供了下面的幾種電梯演算法來實作IO調度:
1. No-op I/O scheduler只實作了簡單的FIFO的,只進行最簡單的合并,比較適合基于Flash的存盤
2. Anticipatory I/O scheduler推遲IO請求(大約幾個微秒),以期能對他們進行排序,獲得更高效率
3. Deadline I/O scheduler試圖把每次請求的延遲降到最低,同時也會對BIO重新排序,特別適用于讀取較多的場合,比如資料庫
4. CFQ I/O scheduler為系統內所有的任務分配均勻的IO帶寬,提供一個公平的作業環境,在多媒體環境中,能保證音視頻及時從磁盤中讀取資料,是當前內核默認的調度器
我們可以通過內核傳參的方式指定使用的調度演算法: kernel elevator=deadline
或者,使用如下命令改變內核調度演算法:
echo SCHEDULER > /sys/block/DEVICE/queue/scheduler
二、塊設備結構介紹
2.1 內核自帶可參考的塊設備驅動原始碼
drivers\block\z2ram.c
drivers\block\xd.c
\drivers\mmc\host\sdhci-s3c.c
2.2 塊設備注冊與注銷函式
1. 注冊函式
| int register_blkdev(unsigned int major, const char *name) |
函式功能介紹: 注冊一個新的塊設備
函式引數介紹:
@major:塊設備的主設備號[1..255], 如果major = 0,表示嘗試分配未使用的主設備號,回傳值就表示分配成功的主設備號,
@name:新塊設備的名稱, 注意: 名稱必須保證在系統中是唯一的(不能與設備名稱重名),
注冊示例:
| int Tiny4412_block_major = register_blkdev(0, "Tiny4412_block"); |
2. 注銷函式
| void unregister_blkdev(unsigned int major, const char *name) |
函式工程介紹: 注銷已注冊的塊設備,
函式引數介紹:
@major: 主設備號
@name: 設備名稱
注銷示例:
| unregister_blkdev(Tiny4412_block_major, "Tiny4412_block"); |
2.3 動態分配請求佇列
| struct request_queue *blk_alloc_queue(gfp_t gfp_mask) |
函式功能介紹: 分配一個默認的請求佇列,用該函式生成的請求佇列沒有設定默認的IO調度器,如果撰寫的塊設備是記憶體模擬塊設備或者是SD卡、Flash等設備,就可以用此函式分配請求佇列,
函式引數介紹:
@ gfp_mask : 記憶體分配的方式, GFP_KERNEL和GFP_ATOMIC,
GFP_ATOMIC: 用來從中斷處理和行程背景關系之外的其他代碼中分配記憶體. 從不睡眠
GFP_KERNEL: 內核記憶體的正常分配. 可能睡眠
分配請求佇列示例:
| struct request_queue *queue= =blk_alloc_queue(GFP_KERNEL); |
卸載驅動時,可以通過kfree釋放空間,
如果需要訪問外部硬體,比如: 光碟、磁盤等外部物理設備時,要設定默認的調度器,可以呼叫blk_init_queue函式分配請求佇列,
| struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) |
blk_init_queue()必須與blk_cleanup_queue()呼叫配對,
函式引數介紹:
@ rfn 是一個函式指標,型別為 typedef void (request_fn_proc) (struct request_queue *q);
@ lock 自旋鎖
2.4 系結請求佇列
| void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn) |
函式功能介紹: 系結blk_alloc_queue函式到請求佇列,
上一步介紹的blk_alloc_queue函式分配的請求佇列,由于不會使用默認的IO調度器,其中的make_request_fn是沒有賦值的,因為上層代碼向請求佇列發生請求時都是通過make_request_fn這個函式來完成的,對于上層代碼發出的請求,可以直接用make_request_fn函式來完成請求并直接將結果回傳給上層的代碼,
函式引數介紹:
struct request_queue *q :請求佇列指標,
make_request_fn *mfn : make_request_fn函式指標,
函式指標的原型如下:
| typedef void (make_request_fn) (struct request_queue *q, struct bio *bio); |
該函式指標在Blkdev.h定義,

系結請求佇列示例:
| blk_queue_make_request(queue, Tiny4412_block_make_request); |
2.5 make_request_fn處理函式撰寫
| //直接提交請求,佇列處理 static void Tiny4412_block_make_request(struct request_queue *q, struct bio *bio) { int i; struct bio_vec *bvec; sector_t sector = bio->bi_sector; /*通過for回圈遍歷一個bio中所有的segment請求*/ bio_for_each_segment(bvec, bio, i) { char *buffer = __bio_kmap_atomic(bio, i, KM_USER0); /*映射記憶體空間(申請空間)*/ Tiny4412_block_dev_sector_read_write(sector, bio_cur_bytes(bio)>>9 ,buffer, bio_data_dir(bio) == WRITE); /* sector: 當前扇區位置 bio_cur_bytes(bio)>>9: 扇區讀寫數量 buffer :讀寫的緩沖區指標首地址 bio_data_dir(bio): 判斷是讀還是寫 */ sector += bio_cur_bytes(bio)>>9; /*偏移扇區*/ __bio_kunmap_atomic(bio, KM_USER0); /*取消映射(釋放空間)*/ } bio_endio(bio, 0); /*結束處理*/ return; } |
make_request_fn函式指標傳入的引數介紹:
struct bio *bio: 描述塊資料傳送時怎樣完成填充或讀取塊給driver
struct request_queue *q :傳入的請求佇列
2.6 扇區讀寫函式實作
代碼示例:
| unsigned long sector: 當前扇區位置 unsigned long nsect : 扇區讀寫數量 char *buffer : 讀寫的緩沖區指標 int write : 是讀還是寫 */ static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write) { /*塊設備最小單位是一個扇區,一個扇區的位元組數是512位元組*/ unsigned long offset = sector*512; unsigned long nbytes = nsect*512; if((offset + nbytes)>TINY4412_BLKDEV_BYTES) { printk(KERN_NOTICE "寫超出范圍,強制結束(%ld %ld)\n", offset, nbytes); return; } if(write) /*為真,表示是寫*/ memcpy(tiny4412_blkdev_data + offset, buffer, nbytes); else /*讀操作*/ memcpy(buffer,tiny4412_blkdev_data + offset, nbytes); } |
2.7 分配一個gendisk結構
| struct gendisk *alloc_disk(int minors) //動態分配gendisk void del_gendisk(struct gendisk *disk) //注銷gendisk |
函式功能介紹:每個塊設備都對應一個gendisk結構,函式alloc_disk用于分配一個gendisk結構,
函式引數介紹:
@minors: 數量
給分配的結構填充引數:
| /*動態分配次設備號結構*/ gd=alloc_disk(1);/*分配一個gendisk,1表示不能進行磁區,只能固定一個磁區, >1表示支持磁區的數量 磁區可以通過fdsik命令進行操作*/ gd->major=Tiny4412_block_major; /*主設備號*/ gd->first_minor=0; /*次設備號*/ gd->fops=&Tiny4412_block_ops; /*檔案操作集合*/ gd->queue=queue; /*將請求佇列關聯到gendisk結構*/ snprintf(gd->disk_name, 32, "Tiny4412_block_%c",'a'); //設定磁盤名稱,在/dev下可以查看該名稱 //塊設備基本都是使用檔案系統函式進行操作,該檔案操作集合可以不用自己實作 static struct block_device_operations Tiny4412_block_ops= { .owner = THIS_MODULE, }; |
驅動安裝之后,查看的節點資訊:

設定磁盤的容量
| /*注意: 塊設備的大小使用扇區作為單位設定,而扇區的大小默認是512位元組, cat /sys/block/xxxx/size 可以查看到設定的大小 把位元組為單位的大小轉換為以扇區為單位時,我們需要除以512,或者右移9位 */ set_capacity(gd,TINY4412_BLKDEV_BYTES>>9); |
2.8 添加磁盤磁區資訊到內核
| void add_disk(struct gendisk *disk) |
函式功能介紹: 將磁區資訊添加到內核,
函式引數: 填充好gendisk結構,
示例:
| add_disk(gd); |
12.9 初始化一個請求佇列
| struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) |
示例:
| tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock); |
該函式里呼叫了默認的IO調度器, 代碼可以參考內核檔案: drivers\block\z2ram.c
三、塊設備示例代碼

3.1 記憶體模擬塊設備(不使用IO調度器)
記憶體空間采用vmalloc函式進行分配,
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <linux/version.h>
/*
* insmod tiny4412_blkdev.ko
* # or insmod tiny4412_blkdev.ko size=numK/M/G/T
* fdisk /dev/tiny4412_blkdev # create 2 patitions
* mkfs.ext2 /dev/tiny4412_blkdev1
* mkfs.ext2 /dev/tiny4412_blkdev2
* mount /dev/tiny4412_blkdev1 /mnt/temp1/
* mount /dev/tiny4412_blkdev2 /mnt/temp2/
* # play in /mnt/temp1/ and /mnt/temp2/
* umount /mnt/temp1/
* umount /mnt/temp2/
* rmmod tiny4412_blkdev.ko
*
*/
static int Tiny4412_block_major=0;
static struct request_queue *tiny4412_blkdev_queue;
static struct gendisk *tiny4412_blkdev_disk;
static unsigned long long tiny4412_blkdev_bytes=1024*1024*10;//10M--空間容量
#define TINY4412_BLKDEV_BYTES_1 (1024*1024*10) /*設定塊設備的大小*/
static unsigned char tiny4412_blkdev_data_1[TINY4412_BLKDEV_BYTES_1]; /*用于測驗塊設備的陣列大小*/
/*
* Handle an I/O request.
* 實作扇區的讀寫
unsigned long sector: 當前扇區位置
unsigned long nsect : 扇區讀寫數量
char *buffer : 讀寫的緩沖區指標
int write : 是讀還是寫
*/
static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write)
{
/*塊設備最小單位是一個扇區,一個扇區的位元組數是512位元組*/
unsigned long offset = sector; /*寫入資料的位置*/
unsigned long nbytes = nsect; /*寫入的長度*/
if((offset + nbytes)>TINY4412_BLKDEV_BYTES_1)
{
printk("寫超出范圍,強制結束(%ld %ld)\n", offset, nbytes);
return;
}
if(write) /*為真,表示是寫*/
memcpy(tiny4412_blkdev_data_1 + offset, buffer, nbytes);
else /*讀操作*/
memcpy(buffer,tiny4412_blkdev_data_1 + offset, nbytes);
}
/*
處理請求
*/
static int tiny4412_blkdev_make_request(struct request_queue *q, struct bio *bio)
{
int dir;
unsigned long long dsk_offset;
struct bio_vec *bvec;
int i;
void *iovec_mem;
/*判斷讀寫方向*/
if(bio_data_dir(bio) == WRITE) dir = 1;
else dir = 0;
dsk_offset = bio->bi_sector << 9;
bio_for_each_segment(bvec, bio, i)
{
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
//起始位置,長度,源資料,方向
Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir);
kunmap(bvec->bv_page);
dsk_offset += bvec->bv_len;
}
bio_endio(bio, 0);
return 0;
}
static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512
* 存盤容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數
*/
geo->heads = 2; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/
geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/
geo->sectors = TINY4412_BLKDEV_BYTES_1/2/32/512; /*扇區,一般每個磁道上有12個扇區,這里需要根據前面柱面和磁頭進行計算,不能亂填*/
return 0;
}
struct block_device_operations tiny4412_blkdev_fops =
{
.owner= THIS_MODULE,
/*fdisk命令磁區時需要呼叫該函式,用于讀取磁頭、柱面、扇區等資訊*/
.getgeo = tiny4412_blockdev_getgeo,
};
static int __init tiny4412_blkdev_init(void)
{
/*動態分配請求佇列*/
tiny4412_blkdev_queue = blk_alloc_queue(GFP_KERNEL);
/*系結請求佇列*/
blk_queue_make_request(tiny4412_blkdev_queue,tiny4412_blkdev_make_request);
/*動態分配次設備號結構*/
/*每一個磁盤(磁區)都是使用一個gendisk結構保存*/
tiny4412_blkdev_disk = alloc_disk(64);
/*磁盤名稱賦值*/
strcpy(tiny4412_blkdev_disk->disk_name, "tiny4412_blkdev");
/*注冊一個塊設備,自動分配主設備號*/
Tiny4412_block_major = register_blkdev(0,"Tiny4412_block");
printk("Tiny4412_block_major=%d\n",Tiny4412_block_major);
tiny4412_blkdev_disk->major=Tiny4412_block_major; /*主設備號*/
tiny4412_blkdev_disk->first_minor = 0; /*次設備號*/
tiny4412_blkdev_disk->fops = &tiny4412_blkdev_fops; /*檔案操作結合*/
tiny4412_blkdev_disk->queue = tiny4412_blkdev_queue; /*處理資料請求的佇列*/
/*設定磁盤結構 capacity 的容量*/
/*注意: 塊設備的大小使用扇區作為單位設定,而扇區的大小默認是512位元組,
cat /sys/block/xxxx/size 可以查看到設定的大小
把位元組為單位的大小轉換為以扇區為單位時,我們需要除以512,或者右移9位
*/
set_capacity(tiny4412_blkdev_disk,tiny4412_blkdev_bytes>>9);
//添加磁盤資訊到內核
add_disk(tiny4412_blkdev_disk);
return 0;
}
static void __exit tiny4412_blkdev_exit(void)
{
//洗掉磁盤
del_gendisk(tiny4412_blkdev_disk);
put_disk(tiny4412_blkdev_disk);
//清除佇列
blk_cleanup_queue(tiny4412_blkdev_queue);
/*注銷塊設備*/
unregister_blkdev(Tiny4412_block_major, "Tiny4412_block");
}
module_init(tiny4412_blkdev_init);
module_exit(tiny4412_blkdev_exit);
MODULE_LICENSE("GPL");
塊設備操作程序:
| [root@wbyq code]#ls tiny4412_block_device.ko [root@wbyq code]#insmod tiny4412_block_device.ko [ 154.950000] Tiny4412_block_major=253 [ 154.955000] tiny4412_blkdev: unknown partition table (因為使用的記憶體設備模擬塊設備,陣列里沒有磁區表,所以第一次安裝設備時,讀取不到設備磁區表,這個提示是正常的) [root@wbyq code]#fdisk /dev/tiny4412_blkdev Device contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabel Building a new DOS disklabel. Changes will remain in memory only, until you decide to write them. After that the previous content won't be recoverable. Command (m for help): n (n表示新建磁區表) Command action e extended(表示擴展磁區) p primary partition (1-4)(表示主磁區) p (選擇p創建主磁區) Partition number (1-4): 1 (創建的主磁區編號為1) First cylinder (1-160, default 1): 1 (柱面起始位置設定為1, 1-160表示當前磁盤剩余的未磁區的柱面范圍為1-160) Last cylinder or +size or +sizeM or +sizeK (1-160, default 160): 100 (設定柱面的結束位置) Command (m for help): n (表示主磁區) Command action e extended p primary partition (1-4) p (選擇p創建主磁區) Partition number (1-4): 2 (創建的主磁區編號為2) First cylinder (101-160, default 101): 101 (柱面起始位置設定為101, 101-160表示當前磁盤剩余的未磁區的柱面范圍為101-160) Last cylinder or +size or +sizeM or +sizeK (101-160, default 160): 160 (設定柱面的結束位置) Command (m for help): p (列印當前的磁區情況) Disk /dev/tiny4412_blkdev: 10 MB, 10485760 bytes (磁盤的總容量: M單位,位元組單位) 2 heads, 64 sectors/track, 160 cylinders (一個共有2個磁頭,每個柱面有64個扇區,160個柱面) (提示: 存盤容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數) Units = cylinders of 128 * 512 = 65536 bytes
Device Boot Start End Blocks Id System /dev/tiny4412_blkdev1 1 100 6368 83 Linux (磁區1的資訊) Partition 1 has different physical/logical endings: phys=(355, 1, 0) logical=(99, 1, 64) Partition 1 does not end on cylinder boundary /dev/tiny4412_blkdev2 101 160 3840 83 Linux (磁區2的資訊) Partition 2 has different physical/logical endings: phys=(415, 1, 0) logical=(159, 1, 64) Partition 2 does not end on cylinder boundary Command (m for help): w (w表示保存磁區表,寫入磁盤,并退出fdisk命令列) The partition table has been altered. (提示,磁區表示已經更改) Calling ioctl() to re-read partition table (呼叫ioctl()重新讀取磁區表) [ 218.905000] tiny4412_blkdev: tiny4412_blkdev1 tiny4412_blkdev2 (提示磁區之后創建成功的設備節點) [root@wbyq code]#ls /dev/tiny4412_blkdev* -l (查看/dev下磁區成功的設備節點) brw-rw---- 1 root root 253, 0 Nov 24 2018 /dev/tiny4412_blkdev brw-rw---- 1 root root 253, 1 Nov 24 2018 /dev/tiny4412_blkdev1 brw-rw---- 1 root root 253, 2 Nov 24 2018 /dev/tiny4412_blkdev2 [root@wbyq code]#mkfs.ext2 /dev/tiny4412_blkdev1 (給第一個設備磁區進行格式化檔案系統) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 1592 inodes, 6368 blocks 318 blocks (5%) reserved for the super user First data block=1 Maximum filesystem blocks=262144 1 block groups 8192 blocks per group, 8192 fragments per group 1592 inodes per group [root@wbyq code]#mkfs.ext2 /dev/tiny4412_blkdev2(給第二個設備磁區進行格式化檔案系統) Filesystem label= OS type: Linux Block size=1024 (log=0) Fragment size=1024 (log=0) 960 inodes, 3840 blocks 192 blocks (5%) reserved for the super user First data block=1 Maximum filesystem blocks=262144 1 block groups 8192 blocks per group, 8192 fragments per group 960 inodes per group [root@wbyq code]#mount /dev/tiny4412_blkdev1 /mnt/ (將第一個磁區掛載到/mnt目錄下) [root@wbyq code]#mount /dev/tiny4412_blkdev2 /tmp/ (將第二個磁區掛載到/mnt目錄下) [root@wbyq code]#df -h (查看當前系統磁盤的資訊) Filesystem Size Used Available Use% Mounted on 192.168.10.11:/work/rootfs/ 48.1G 16.5G 29.1G 36% / /dev/tiny4412_blkdev1 6.0M 13.0K 5.7M 0% /mnt /dev/tiny4412_blkdev2 3.6M 13.0K 3.4M 0% /tmp /*下面就是分別進入到掛載的目錄下進行檔案拷貝操作,測驗塊設備是否正常,最后在取消掛載退出*/ [root@wbyq code]#cd /mnt/ [root@wbyq mnt]#ls lost+found [root@wbyq mnt]#cp /123.mp3 ./ [root@wbyq mnt]# [root@wbyq mnt]#cd /tmp/ [root@wbyq tmp]#cp /123.mp3 ./ [root@wbyq tmp]# [root@wbyq tmp]#ls 123.mp3 lost+found [root@wbyq tmp]#cd / [root@wbyq ]#umount /tmp/ [root@wbyq ]#umount /mnt/ |
3.2 使用SD卡撰寫塊設備(不使用IO調度器)
SD卡采用SPI協議通信,底層采用模擬的SPI系統,沒有使用SPI子系統,

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/timer.h>
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/hdreg.h> /* HDIO_GETGEO */
#include <linux/kdev_t.h>
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/ioctl.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/list.h>
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/spi/spi.h>
#include <linux/spi/spidev.h>
#include <asm/uaccess.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/mutex.h>
#include <linux/miscdevice.h> /*雜項字符設備頭檔案*/
/*--------------------------------SD相關操作代碼---------------------------------------------*/
/*定義指標,用于接收虛擬地址*/
volatile unsigned int *SD_GPBCON;
volatile unsigned int *SD_GPBDAT;
/*
函式功能:SD初始化
Tiny4412硬體連接:
DO--MISO :GPB_2
DI--MOSI :GPB_3
CLK-SCLK :GPB_0
CS--CS :GPB_1
*/
void SDCardSpiInit(void)
{
/*1. 初始化GPIO*/
/*映射物理地址*/
SD_GPBCON=ioremap(0x11400040,4);
SD_GPBDAT=ioremap(0x11400044,4);
*SD_GPBCON &= ~(0xf << 0 * 4);*SD_GPBCON |= (0x1 << 0 * 4);
*SD_GPBCON &= ~(0xf << 1 * 4);*SD_GPBCON |= (0x1 << 1 * 4);
*SD_GPBCON &= ~(0xf << 2 * 4);
*SD_GPBCON &= ~(0xf << 3 * 4);*SD_GPBCON |= (0x1 << 3 * 4);
/*2. 上拉GPIO口*/
//*SD_GPBDAT &= ~(1 << 4);//輸出0
*SD_GPBDAT |= (1 << 0); //輸出1
*SD_GPBDAT |= (1 << 1); //輸出1
*SD_GPBDAT |= (1 << 3); //輸出1
}
// SD卡型別定義
#define SDCard_TYPE_ERR 0X00 //卡型別錯誤
#define SDCard_TYPE_MMC 0X01 //MMC卡
#define SDCard_TYPE_V1 0X02
#define SDCard_TYPE_V2 0X04
#define SDCard_TYPE_V2HC 0X06
// SD卡指令表
#define SDCard_CMD0 0 //卡復位
#define SDCard_CMD1 1
#define SDCard_CMD8 8 //命令8 ,SEND_IF_COND
#define SDCard_CMD9 9 //命令9 ,讀CSD資料
#define SDCard_CMD10 10 //命令10,讀CID資料
#define SDCard_CMD12 12 //命令12,停止資料傳輸
#define SDCard_CMD13 16 //命令16,設定扇區大小 應回傳0x00
#define SDCard_CMD17 17 //命令17,讀扇區
#define SDCard_CMD18 18 //命令18,讀Multi 扇區
#define SDCard_CMD23 23 //命令23,設定多扇區寫入前預先擦除N個block
#define SDCard_CMD24 24 //命令24,寫扇區
#define SDCard_CMD25 25 //命令25,寫多個扇區
#define SDCard_CMD41 41 //命令41,應回傳0x00
#define SDCard_CMD55 55 //命令55,應回傳0x01
#define SDCard_CMD58 58 //命令58,讀OCR資訊
#define SDCard_CMD59 59 //命令59,使能/禁止CRC,應回傳0x00、
/*SD卡回應標記字*/
#define SDCard_RESPONSE_NO_ERROR 0x00 //正確回應
#define SDCard_SD_IN_IDLE_STATE 0x01 //閑置狀態
#define SDCard_SD_ERASE_RESET 0x02 //擦除復位
#define SDCard_RESPONSE_FAILURE 0xFF //回應失敗
//函式宣告
unsigned char SDCardReadWriteOneByte(unsigned char data); //底層介面,SPI讀寫位元組函式
unsigned char SDCardWaitBusy(void); //等待SD卡準備
unsigned char SDCardGetAck(unsigned char Response); //獲得應答
unsigned char SDCardDeviceInit(void); //初始化
unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt); //讀塊(扇區)
unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt); //寫塊(扇區)
unsigned int GetSDCardSectorCount(void); //讀扇區數
unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data); //讀SD卡CID
unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data); //讀SD卡CSD
static unsigned char SD_Type=0; //存放SD卡的型別
/*
函式功能:SD卡底層介面,通過SPI時序向SD卡讀寫一個位元組
函式引數:data是要寫入的資料
返 回 值:讀到的資料
說明:時序是第二個上升沿采集資料
*/
unsigned char SDCardReadWriteOneByte(unsigned char data_tx)
{
u8 data_rx=0;
u8 i;
for(i=0;i<8;i++)
{
*SD_GPBDAT &= ~(1 << 0);//輸出0
if(data_tx&0x80)*SD_GPBDAT |= (1 << 3); //輸出1
else *SD_GPBDAT &= ~(1 << 3);//輸出0
data_tx<<=1; //繼續發送下一個資料
*SD_GPBDAT |= (1 << 0); //輸出1
data_rx<<=1;
if((*SD_GPBDAT & (1 << 2)))data_rx|=0x01;
}
return data_rx;
}
/*
函式功能:取消選擇,釋放SPI總線
*/
void SDCardCancelCS(void)
{
*SD_GPBDAT |= (1 << 1);
SDCardReadWriteOneByte(0xff);//提供額外的8個時鐘
}
/*
函式 功 能:選擇sd卡,并且等待卡準備OK
函式回傳值:0,成功;1,失敗;
*/
unsigned char SDCardSelectCS(void)
{
*SD_GPBDAT &= ~(1 << 1);
if(SDCardWaitBusy()==0)return 0;//等待成功
SDCardCancelCS();
return 1;//等待失敗
}
/*
函式 功 能:等待卡準備好
函式回傳值:0,準備好了;其他,錯誤代碼
*/
unsigned char SDCardWaitBusy(void)
{
unsigned int t=0;
do
{
if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK
t++;
}while(t<0xFFFFFF);//等待
return 1;
}
/*
函式功能:等待SD卡回應
函式引數:
Response:要得到的回應值
返 回 值:
0,成功得到了該回應值
其他,得到回應值失敗
*/
unsigned char SDCardGetAck(unsigned char Response)
{
u16 Count=0xFFFF;//等待次數
while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到準確的回應
if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回應失敗
else return SDCard_RESPONSE_NO_ERROR;//正確回應
}
/*
函式功能:從sd卡讀取一個資料包的內容
函式引數:
buf:資料快取區
len:要讀取的資料長度.
回傳值:
0,成功;其他,失敗;
*/
unsigned char SDCardRecvData(unsigned char*buf,u16 len)
{
if(SDCardGetAck(0xFE))return 1;//等待SD卡發回資料起始令牌0xFE
while(len--)//開始接收資料
{
*buf=SDCardReadWriteOneByte(0xFF);
buf++;
}
//下面是2個偽CRC(dummy CRC)
SDCardReadWriteOneByte(0xFF);
SDCardReadWriteOneByte(0xFF);
return 0;//讀取成功
}
/*
函式功能:向sd卡寫入一個資料包的內容 512位元組
函式引數:
buf 資料快取區
cmd 指令
返 回 值:0表示成功;其他值表示失敗;
*/
unsigned char SDCardSendData(unsigned char*buf,unsigned char cmd)
{
u16 t;
if(SDCardWaitBusy())return 1; //等待準備失效
SDCardReadWriteOneByte(cmd);
if(cmd!=0XFD)//不是結束指令
{
for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,減少函式傳參時間
SDCardReadWriteOneByte(0xFF); //忽略crc
SDCardReadWriteOneByte(0xFF);
t=SDCardReadWriteOneByte(0xFF); //接收回應
if((t&0x1F)!=0x05)return 2; //回應錯誤
}
return 0;//寫入成功
}
/*
函式功能:向SD卡發送一個命令
函式引數:
unsigned char cmd 命令
unsigned int arg 命令引數
unsigned char crc crc校驗值
回傳值:SD卡回傳的回應
*/
unsigned char SendSDCardCmd(unsigned char cmd, unsigned int arg, unsigned char crc)
{
unsigned char r1;
unsigned char Retry=0;
SDCardCancelCS(); //取消上次片選
if(SDCardSelectCS())return 0XFF;//片選失效
//發送資料
SDCardReadWriteOneByte(cmd | 0x40);//分別寫入命令
SDCardReadWriteOneByte(arg >> 24);
SDCardReadWriteOneByte(arg >> 16);
SDCardReadWriteOneByte(arg >> 8);
SDCardReadWriteOneByte(arg);
SDCardReadWriteOneByte(crc);
if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
Retry=0X1F;
do
{
r1=SDCardReadWriteOneByte(0xFF);
}while((r1&0X80) && Retry--); //等待回應,或超時退出
return r1; //回傳狀態值
}
/*
函式功能:獲取SD卡的CID資訊,包括制造商資訊
函式引數:unsigned char *cid_data(存放CID的記憶體,至少16Byte)
返 回 值:
0:成功,1:錯誤
*/
unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data)
{
unsigned char r1;
//發SDCard_CMD10命令,讀CID
r1=SendSDCardCmd(SDCard_CMD10,0,0x01);
if(r1==0x00)
{
r1=SDCardRecvData(cid_data,16);//接收16個位元組的資料
}
SDCardCancelCS();//取消片選
if(r1)return 1;
else return 0;
}
/*
函式說明:
獲取SD卡的CSD資訊,包括容量和速度資訊
函式引數:
unsigned char *cid_data(存放CID的記憶體,至少16Byte)
返 回 值:
0:成功,1:錯誤
*/
unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data)
{
unsigned char r1;
r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //發SDCard_CMD9命令,讀CSD
if(r1==0)
{
r1=SDCardRecvData(csd_data, 16);//接收16個位元組的資料
}
SDCardCancelCS();//取消片選
if(r1)return 1;
else return 0;
}
/*
函式功能:獲取SD卡的總扇區數(扇區數)
返 回 值:
0表示容量檢測出錯,其他值表示SD卡的容量(扇區數/512位元組)
說 明:
每扇區的位元組數必為512位元組,如果不是512位元組,則初始化不能通過.
*/
unsigned int GetSDCardSectorCount(void)
{
unsigned char csd[16];
unsigned int Capacity;
unsigned char n;
u16 csize;
if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0; //取CSD資訊,如果期間出錯,回傳0
if((csd[0]&0xC0)==0x40) //V2.00的卡,如果為SDHC卡,按照下面方式計算
{
csize = csd[9] + ((u16)csd[8] << 8) + 1;
Capacity = (unsigned int)csize << 10;//得到扇區數
}
else//V1.XX的卡
{
n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
Capacity= (unsigned int)csize << (n - 9);//得到扇區數
}
return Capacity;
}
/*
函式功能: 初始化SD卡
返 回 值: 非0表示初始化失敗!
*/
unsigned char SDCardDeviceInit(void)
{
unsigned char r1; // 存放SD卡的回傳值
u16 retry; // 用來進行超時計數
unsigned char buf[4];
u16 i;
SDCardSpiInit(); //初始化底層IO口
for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //發送最少74個脈沖
retry=20;
do
{
r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//進入IDLE狀態 閑置
}while((r1!=0X01) && retry--);
SD_Type=0; //默認無卡
if(r1==0X01)
{
if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0
{
for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF); //Get trailing return value of R7 resp
if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支持2.7~3.6V
{
retry=0XFFFE;
do
{
SendSDCardCmd(SDCard_CMD55,0,0X01); //發送SDCard_CMD55
r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//發送SDCard_CMD41
}while(r1&&retry--);
if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鑒別SD2.0卡版本開始
{
for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值
if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //檢查CCS
else SD_Type=SDCard_TYPE_V2;
}
}
}
else//SD V1.x/ MMC V3
{
SendSDCardCmd(SDCard_CMD55,0,0X01); //發送SDCard_CMD55
r1=SendSDCardCmd(SDCard_CMD41,0,0X01); //發送SDCard_CMD41
if(r1<=1)
{
SD_Type=SDCard_TYPE_V1;
retry=0XFFFE;
do //等待退出IDLE模式
{
SendSDCardCmd(SDCard_CMD55,0,0X01); //發送SDCard_CMD55
r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//發送SDCard_CMD41
}while(r1&&retry--);
}
else//MMC卡不支持SDCard_CMD55+SDCard_CMD41識別
{
SD_Type=SDCard_TYPE_MMC;//MMC V3
retry=0XFFFE;
do //等待退出IDLE模式
{
r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//發送SDCard_CMD1
}while(r1&&retry--);
}
if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//錯誤的卡
}
}
SDCardCancelCS(); //取消片選
if(SD_Type)return 0; //初始化成功回傳0
else if(r1)return r1; //回傳值錯誤值
return 0xaa; //其他錯誤
}
/*
函式功能:讀SD卡
函式引數:
buf:資料快取區
sector:扇區
cnt:扇區數
回傳值:
0,ok;其他,失敗.
說 明:
SD卡一個扇區大小512位元組
*/
unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt)
{
unsigned char r1;
if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//轉換為位元組地址
if(cnt==1)
{
r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//讀命令
if(r1==0) //指令發送成功
{
r1=SDCardRecvData(buf,512); //接收512個位元組
}
}else
{
r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//連續讀命令
do
{
r1=SDCardRecvData(buf,512);//接收512個位元組
buf+=512;
}while(--cnt && r1==0);
SendSDCardCmd(SDCard_CMD12,0,0X01); //發送停止命令
}
SDCardCancelCS();//取消片選
return r1;//
}
/*
函式功能:向SD卡寫資料
函式引數:
buf:資料快取區
sector:起始扇區
cnt:扇區數
回傳值:
0,ok;其他,失敗.
說 明:
SD卡一個扇區大小512位元組
*/
unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt)
{
unsigned char r1;
if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//轉換為位元組地址
if(cnt==1)
{
r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//讀命令
if(r1==0)//指令發送成功
{
r1=SDCardSendData(buf,0xFE);//寫512個位元組
}
}
else
{
if(SD_Type!=SDCard_TYPE_MMC)
{
SendSDCardCmd(SDCard_CMD55,0,0X01);
SendSDCardCmd(SDCard_CMD23,cnt,0X01);//發送指令
}
r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//連續讀命令
if(r1==0)
{
do
{
r1=SDCardSendData(buf,0xFC);//接收512個位元組
buf+=512;
}while(--cnt && r1==0);
r1=SDCardSendData(0,0xFD);//接收512個位元組
}
}
SDCardCancelCS();//取消片選
return r1;//
}
/*
功能說明:
1. 支持檔案系統格式化: #mkfs.ext2 /dev/tiny4412_block_a
2. 支持mount掛載: #mount /dev/tiny4412_block_a /mnt/
3. 支持磁盤大小查看: #cat /sys/block/Tiny4412_block_a/size
#df -h
*/
static struct request_queue *queue=NULL; /* 設備請求佇列 */
static struct gendisk *gd; /* gendisk結構 */
static unsigned int sd_size=0; //存放SD卡回傳的容量扇區數量單位(512位元組)
static int Tiny4412_block_major = 0; /*存放主設備號*/
static DEFINE_MUTEX(sd_mutex); /*靜態定義互斥鎖*/
/*
* Handle an I/O request.
* 實作扇區的讀寫
*/
static void Tiny4412_block_dev_sector_read_write(unsigned long sector,unsigned long nsect, char *buffer, int write)
{
/*互斥鎖,上鎖*/
mutex_lock(&sd_mutex);
sector>>=9;
nsect>>=9;
/*塊設備最小單位是一個扇區,一個扇區的位元組數是512位元組*/
if(write)
{
if(SDCardWriteData(buffer,sector,nsect))
{
printk(KERN_ERR"write error!\r\n");
printk("write ---> nsect=%ld,sector=%ld\r\n",nsect,sector);
}
}
else
{
if(SDCardReadData(buffer,sector,nsect))
{
printk(KERN_ERR"read error!\r\n");
printk("read ---> nsect=%ld,sector=%ld\r\n",nsect,sector);
}
}
/*互斥鎖解鎖*/
mutex_unlock(&sd_mutex);
}
/*
處理請求
*/
static void Tiny4412_block_make_request(struct request_queue *q, struct bio *bio)
{
int dir;
unsigned long long dsk_offset;
struct bio_vec *bvec;
int i;
void *iovec_mem;
/*判斷讀寫方向*/
if(bio_data_dir(bio) == WRITE) dir = 1;
else dir = 0;
dsk_offset = bio->bi_sector << 9;
bio_for_each_segment(bvec, bio, i)
{
iovec_mem = kmap(bvec->bv_page) + bvec->bv_offset;
//起始位置,長度,源資料,方向
Tiny4412_block_dev_sector_read_write(dsk_offset,bvec->bv_len,iovec_mem,dir);
kunmap(bvec->bv_page);
dsk_offset += bvec->bv_len;
}
bio_endio(bio, 0);
}
static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512
* 存盤容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數
*/
geo->heads = 2; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/
geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/
geo->sectors = sd_size/2/32; /*扇區,一般每個磁道上有12個扇區,這里需要根據前面柱面和磁頭進行計算,不能亂填*/
/*geo->sectors =存盤容量/磁頭數/柱面/每扇區位元組數*/
return 0;
}
/*
* 塊設備檔案操作集合介面
*/
static struct block_device_operations Tiny4412_block_ops=
{
.owner = THIS_MODULE,
/*fdisk命令磁區時需要呼叫該函式,用于讀取磁頭、柱面、扇區等資訊*/
.getgeo = tiny4412_blockdev_getgeo,
};
/*
驅動入口
*/
static int __init Tiny4412_block_init(void)
{
/*1. 初始化SD口*/
if(SDCardDeviceInit())
{
printk("SD卡初始化失敗!\r\n");
return -1;
}
/*2. 檢測SD卡大小*/
sd_size=GetSDCardSectorCount(); //檢測SD卡大小,回傳值右移11位得到以M為單位的容量
printk("SD卡Sizeof:%dM secnt=%d\r\n",sd_size>>11,sd_size);
/*注冊一個塊設備,自動分配主設備號*/
Tiny4412_block_major = register_blkdev(0, "Tiny4412_SDdrv");
/*動態分配請求佇列*/
queue=blk_alloc_queue(GFP_KERNEL);
/*系結請求佇列*/
blk_queue_make_request(queue, Tiny4412_block_make_request);
/*動態分配次設備號結構*/
gd=alloc_disk(16);/*分配一個gendisk,磁區是一個*/
gd->major=Tiny4412_block_major; /*主設備號*/
gd->first_minor=0; /*次設備號*/
gd->fops=&Tiny4412_block_ops;
gd->queue=queue; /*將請求佇列關聯到gendisk結構*/
snprintf(gd->disk_name, 32, "tiny4412_sd%c",'a');
/*設定磁盤結構 capacity 的容量*/
/*注意: 塊設備的大小使用扇區作為單位設定,而扇區的大小默認是512位元組,
cat /sys/block/xxxx/size 可以查看到設定的大小
把位元組為單位的大小轉換為以扇區為單位時,我們需要除以512,或者右移9位
*/
set_capacity(gd,sd_size);
/*注冊磁盤設備*/
add_disk(gd);
printk("塊設備注冊成功!\r\n");
return 0;
}
/*驅動出口*/
static void Tiny4412_block_exit(void)
{
/*釋放虛擬地址*/
iounmap(SD_GPBCON);
iounmap(SD_GPBDAT);
/*注銷磁盤設備*/
if(gd)del_gendisk(gd);
/*注銷塊設備*/
if(Tiny4412_block_major!=0)unregister_blkdev(Tiny4412_block_major, "Tiny4412_SDdrv");
printk("塊設備注消成功!\r\n");
}
module_init(Tiny4412_block_init);
module_exit(Tiny4412_block_exit);
MODULE_LICENSE("GPL");
3.3 記憶體模擬塊設備(使用默認的IO調度器)
示例代碼:
/* 參考: 搜索注冊函式即可
* drivers\block\xd.c
* drivers\block\z2ram.c
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/vmalloc.h>
#include <linux/hdreg.h>
#include <linux/version.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
static struct gendisk *tiny4412_blockdev_disk;
static struct request_queue *tiny4412_blockdev_queue;
static int major;
static DEFINE_SPINLOCK(tiny4412_blockdev_lock);
#define RAMBLOCK_SIZE (1024*1024*10) /*10M空間*/
static unsigned char *tiny4412_blockdev_buf=NULL;
static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512
* 存盤容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數
*/
geo->heads = 2; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/
geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/
geo->sectors = RAMBLOCK_SIZE/2/32/512; /*扇區,一般每個磁道上有12個扇區,這里需要根據前面柱面和磁頭進行計算,不能亂填*/
return 0;
}
static struct block_device_operations tiny4412_blockdev_fops = {
.owner = THIS_MODULE,
.getgeo = tiny4412_blockdev_getgeo,
};
static void do_tiny4412_blockdev_request(struct request_queue * q)
{
struct request *req;
req = blk_fetch_request(q);
while (req)
{
unsigned long start = blk_rq_pos(req) *512; /*轉為位元組單位(起始位置)*/
unsigned long len = blk_rq_cur_bytes(req); /*當前操作的位元組數量*/
int err = 0;
if (rq_data_dir(req) == READ) /*如果是讀*/
memcpy(req->buffer, tiny4412_blockdev_buf+start, len);
else
memcpy(tiny4412_blockdev_buf+start, req->buffer,len);
if (!__blk_end_request_cur(req, err)) /*判斷是否處理完畢請求*/
req = blk_fetch_request(q); /*繼續處理下一個請求*/
}
}
static int tiny4412_blockdev_init(void)
{
/* 1. 分配一個gendisk結構體 */
tiny4412_blockdev_disk = alloc_disk(16); /* 次設備號個數: 磁區個數+1 */
/* 2. 設定 */
/* 2.1 分配/設定佇列: 提供讀寫能力 */
tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock);
tiny4412_blockdev_disk->queue = tiny4412_blockdev_queue;
/* 2.2 設定其他屬性: 比如容量 */
major = register_blkdev(0, "tiny4412_blockdev"); /* cat /proc/devices */
tiny4412_blockdev_disk->major = major;
tiny4412_blockdev_disk->first_minor = 0;
sprintf(tiny4412_blockdev_disk->disk_name, "tiny4412_blockdev");
tiny4412_blockdev_disk->fops = &tiny4412_blockdev_fops;
set_capacity(tiny4412_blockdev_disk, RAMBLOCK_SIZE / 512);
/* 3. 硬體相關操作 */
tiny4412_blockdev_buf = vmalloc(RAMBLOCK_SIZE);
if(tiny4412_blockdev_buf==NULL)
{
printk("空間申請失敗!\n");
return -1;
}
/* 4. 注冊 */
add_disk(tiny4412_blockdev_disk);
return 0;
}
static void tiny4412_blockdev_exit(void)
{
unregister_blkdev(major, "tiny4412_blockdev");
del_gendisk(tiny4412_blockdev_disk);
put_disk(tiny4412_blockdev_disk);
blk_cleanup_queue(tiny4412_blockdev_queue);
vfree(tiny4412_blockdev_buf);
}
module_init(tiny4412_blockdev_init);
module_exit(tiny4412_blockdev_exit);
MODULE_LICENSE("GPL");
3.4 使用SD卡撰寫塊設備(使用默認的IO調度器)
/* 參考:
* drivers\block\xd.c
* drivers\block\z2ram.c
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/vmalloc.h>
#include <linux/hdreg.h>
#include <linux/version.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>
#include <linux/gpio.h>
#include <mach/gpio.h>
#include <plat/gpio-cfg.h>
#include <linux/delay.h>
#include <linux/io.h>
/*--------------------------------SD相關操作代碼---------------------------------------------*/
/*定義指標,用于接收虛擬地址*/
volatile unsigned int *SD_GPBCON;
volatile unsigned int *SD_GPBDAT;
/*
函式功能:SD初始化
Tiny4412硬體連接:
DO--MISO :GPB_2
DI--MOSI :GPB_3
CLK-SCLK :GPB_0
CS--CS :GPB_1
*/
void SDCardSpiInit(void)
{
/*1. 初始化GPIO*/
/*映射物理地址*/
SD_GPBCON=ioremap(0x11400040,4);
SD_GPBDAT=ioremap(0x11400044,4);
*SD_GPBCON &= ~(0xf << 0 * 4);*SD_GPBCON |= (0x1 << 0 * 4);
*SD_GPBCON &= ~(0xf << 1 * 4);*SD_GPBCON |= (0x1 << 1 * 4);
*SD_GPBCON &= ~(0xf << 2 * 4);
*SD_GPBCON &= ~(0xf << 3 * 4);*SD_GPBCON |= (0x1 << 3 * 4);
/*2. 上拉GPIO口*/
//*SD_GPBDAT &= ~(1 << 4);//輸出0
*SD_GPBDAT |= (1 << 0); //輸出1
*SD_GPBDAT |= (1 << 1); //輸出1
*SD_GPBDAT |= (1 << 3); //輸出1
}
// SD卡型別定義
#define SDCard_TYPE_ERR 0X00 //卡型別錯誤
#define SDCard_TYPE_MMC 0X01 //MMC卡
#define SDCard_TYPE_V1 0X02
#define SDCard_TYPE_V2 0X04
#define SDCard_TYPE_V2HC 0X06
// SD卡指令表
#define SDCard_CMD0 0 //卡復位
#define SDCard_CMD1 1
#define SDCard_CMD8 8 //命令8 ,SEND_IF_COND
#define SDCard_CMD9 9 //命令9 ,讀CSD資料
#define SDCard_CMD10 10 //命令10,讀CID資料
#define SDCard_CMD12 12 //命令12,停止資料傳輸
#define SDCard_CMD13 16 //命令16,設定扇區大小 應回傳0x00
#define SDCard_CMD17 17 //命令17,讀扇區
#define SDCard_CMD18 18 //命令18,讀Multi 扇區
#define SDCard_CMD23 23 //命令23,設定多扇區寫入前預先擦除N個block
#define SDCard_CMD24 24 //命令24,寫扇區
#define SDCard_CMD25 25 //命令25,寫多個扇區
#define SDCard_CMD41 41 //命令41,應回傳0x00
#define SDCard_CMD55 55 //命令55,應回傳0x01
#define SDCard_CMD58 58 //命令58,讀OCR資訊
#define SDCard_CMD59 59 //命令59,使能/禁止CRC,應回傳0x00、
/*SD卡回應標記字*/
#define SDCard_RESPONSE_NO_ERROR 0x00 //正確回應
#define SDCard_SD_IN_IDLE_STATE 0x01 //閑置狀態
#define SDCard_SD_ERASE_RESET 0x02 //擦除復位
#define SDCard_RESPONSE_FAILURE 0xFF //回應失敗
//函式宣告
unsigned char SDCardReadWriteOneByte(unsigned char data); //底層介面,SPI讀寫位元組函式
unsigned char SDCardWaitBusy(void); //等待SD卡準備
unsigned char SDCardGetAck(unsigned char Response); //獲得應答
unsigned char SDCardDeviceInit(void); //初始化
unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt); //讀塊(扇區)
unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt); //寫塊(扇區)
unsigned int GetSDCardSectorCount(void); //讀扇區數
unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data); //讀SD卡CID
unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data); //讀SD卡CSD
static unsigned char SD_Type=0; //存放SD卡的型別
/*
函式功能:SD卡底層介面,通過SPI時序向SD卡讀寫一個位元組
函式引數:data是要寫入的資料
返 回 值:讀到的資料
說明:時序是第二個上升沿采集資料
*/
unsigned char SDCardReadWriteOneByte(unsigned char data_tx)
{
u8 data_rx=0;
u8 i;
for(i=0;i<8;i++)
{
*SD_GPBDAT &= ~(1 << 0);//輸出0
if(data_tx&0x80)*SD_GPBDAT |= (1 << 3); //輸出1
else *SD_GPBDAT &= ~(1 << 3);//輸出0
data_tx<<=1; //繼續發送下一個資料
*SD_GPBDAT |= (1 << 0); //輸出1
data_rx<<=1;
if((*SD_GPBDAT & (1 << 2)))data_rx|=0x01;
}
return data_rx;
}
/*
函式功能:取消選擇,釋放SPI總線
*/
void SDCardCancelCS(void)
{
*SD_GPBDAT |= (1 << 1);
SDCardReadWriteOneByte(0xff);//提供額外的8個時鐘
}
/*
函式 功 能:選擇sd卡,并且等待卡準備OK
函式回傳值:0,成功;1,失敗;
*/
unsigned char SDCardSelectCS(void)
{
*SD_GPBDAT &= ~(1 << 1);
if(SDCardWaitBusy()==0)return 0;//等待成功
SDCardCancelCS();
return 1;//等待失敗
}
/*
函式 功 能:等待卡準備好
函式回傳值:0,準備好了;其他,錯誤代碼
*/
unsigned char SDCardWaitBusy(void)
{
unsigned int t=0;
do
{
if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK
t++;
}while(t<0xFFFFFF);//等待
return 1;
}
/*
函式功能:等待SD卡回應
函式引數:
Response:要得到的回應值
返 回 值:
0,成功得到了該回應值
其他,得到回應值失敗
*/
unsigned char SDCardGetAck(unsigned char Response)
{
u16 Count=0xFFFF;//等待次數
while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到準確的回應
if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回應失敗
else return SDCard_RESPONSE_NO_ERROR;//正確回應
}
/*
函式功能:從sd卡讀取一個資料包的內容
函式引數:
buf:資料快取區
len:要讀取的資料長度.
回傳值:
0,成功;其他,失敗;
*/
unsigned char SDCardRecvData(unsigned char*buf,u16 len)
{
if(SDCardGetAck(0xFE))return 1;//等待SD卡發回資料起始令牌0xFE
while(len--)//開始接收資料
{
*buf=SDCardReadWriteOneByte(0xFF);
buf++;
}
//下面是2個偽CRC(dummy CRC)
SDCardReadWriteOneByte(0xFF);
SDCardReadWriteOneByte(0xFF);
return 0;//讀取成功
}
/*
函式功能:向sd卡寫入一個資料包的內容 512位元組
函式引數:
buf 資料快取區
cmd 指令
返 回 值:0表示成功;其他值表示失敗;
*/
unsigned char SDCardSendData(unsigned char*buf,unsigned char cmd)
{
u16 t;
if(SDCardWaitBusy())return 1; //等待準備失效
SDCardReadWriteOneByte(cmd);
if(cmd!=0XFD)//不是結束指令
{
for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,減少函式傳參時間
SDCardReadWriteOneByte(0xFF); //忽略crc
SDCardReadWriteOneByte(0xFF);
t=SDCardReadWriteOneByte(0xFF); //接收回應
if((t&0x1F)!=0x05)return 2; //回應錯誤
}
return 0;//寫入成功
}
/*
函式功能:向SD卡發送一個命令
函式引數:
unsigned char cmd 命令
unsigned int arg 命令引數
unsigned char crc crc校驗值
回傳值:SD卡回傳的回應
*/
unsigned char SendSDCardCmd(unsigned char cmd, unsigned int arg, unsigned char crc)
{
unsigned char r1;
unsigned char Retry=0;
SDCardCancelCS(); //取消上次片選
if(SDCardSelectCS())return 0XFF;//片選失效
//發送資料
SDCardReadWriteOneByte(cmd | 0x40);//分別寫入命令
SDCardReadWriteOneByte(arg >> 24);
SDCardReadWriteOneByte(arg >> 16);
SDCardReadWriteOneByte(arg >> 8);
SDCardReadWriteOneByte(arg);
SDCardReadWriteOneByte(crc);
if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading
Retry=0X1F;
do
{
r1=SDCardReadWriteOneByte(0xFF);
}while((r1&0X80) && Retry--); //等待回應,或超時退出
return r1; //回傳狀態值
}
/*
函式功能:獲取SD卡的CID資訊,包括制造商資訊
函式引數:unsigned char *cid_data(存放CID的記憶體,至少16Byte)
返 回 值:
0:成功,1:錯誤
*/
unsigned char GetSDCardCISDCardOutnfo(unsigned char *cid_data)
{
unsigned char r1;
//發SDCard_CMD10命令,讀CID
r1=SendSDCardCmd(SDCard_CMD10,0,0x01);
if(r1==0x00)
{
r1=SDCardRecvData(cid_data,16);//接收16個位元組的資料
}
SDCardCancelCS();//取消片選
if(r1)return 1;
else return 0;
}
/*
函式說明:
獲取SD卡的CSD資訊,包括容量和速度資訊
函式引數:
unsigned char *cid_data(存放CID的記憶體,至少16Byte)
返 回 值:
0:成功,1:錯誤
*/
unsigned char GetSDCardCSSDCardOutnfo(unsigned char *csd_data)
{
unsigned char r1;
r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //發SDCard_CMD9命令,讀CSD
if(r1==0)
{
r1=SDCardRecvData(csd_data, 16);//接收16個位元組的資料
}
SDCardCancelCS();//取消片選
if(r1)return 1;
else return 0;
}
/*
函式功能:獲取SD卡的總扇區數(扇區數)
返 回 值:
0表示容量檢測出錯,其他值表示SD卡的容量(扇區數/512位元組)
說 明:
每扇區的位元組數必為512位元組,如果不是512位元組,則初始化不能通過.
*/
unsigned int GetSDCardSectorCount(void)
{
unsigned char csd[16];
unsigned int Capacity;
unsigned char n;
u16 csize;
if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0; //取CSD資訊,如果期間出錯,回傳0
if((csd[0]&0xC0)==0x40) //V2.00的卡,如果為SDHC卡,按照下面方式計算
{
csize = csd[9] + ((u16)csd[8] << 8) + 1;
Capacity = (unsigned int)csize << 10;//得到扇區數
}
else//V1.XX的卡
{
n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1;
Capacity= (unsigned int)csize << (n - 9);//得到扇區數
}
return Capacity;
}
/*
函式功能: 初始化SD卡
返 回 值: 非0表示初始化失敗!
*/
unsigned char SDCardDeviceInit(void)
{
unsigned char r1; // 存放SD卡的回傳值
u16 retry; // 用來進行超時計數
unsigned char buf[4];
u16 i;
SDCardSpiInit(); //初始化底層IO口
for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //發送最少74個脈沖
retry=20;
do
{
r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//進入IDLE狀態 閑置
}while((r1!=0X01) && retry--);
SD_Type=0; //默認無卡
if(r1==0X01)
{
if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0
{
for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF); //Get trailing return value of R7 resp
if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支持2.7~3.6V
{
retry=0XFFFE;
do
{
SendSDCardCmd(SDCard_CMD55,0,0X01); //發送SDCard_CMD55
r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//發送SDCard_CMD41
}while(r1&&retry--);
if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鑒別SD2.0卡版本開始
{
for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值
if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //檢查CCS
else SD_Type=SDCard_TYPE_V2;
}
}
}
else//SD V1.x/ MMC V3
{
SendSDCardCmd(SDCard_CMD55,0,0X01); //發送SDCard_CMD55
r1=SendSDCardCmd(SDCard_CMD41,0,0X01); //發送SDCard_CMD41
if(r1<=1)
{
SD_Type=SDCard_TYPE_V1;
retry=0XFFFE;
do //等待退出IDLE模式
{
SendSDCardCmd(SDCard_CMD55,0,0X01); //發送SDCard_CMD55
r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//發送SDCard_CMD41
}while(r1&&retry--);
}
else//MMC卡不支持SDCard_CMD55+SDCard_CMD41識別
{
SD_Type=SDCard_TYPE_MMC;//MMC V3
retry=0XFFFE;
do //等待退出IDLE模式
{
r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//發送SDCard_CMD1
}while(r1&&retry--);
}
if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//錯誤的卡
}
}
SDCardCancelCS(); //取消片選
if(SD_Type)return 0; //初始化成功回傳0
else if(r1)return r1; //回傳值錯誤值
return 0xaa; //其他錯誤
}
/*
函式功能:讀SD卡
函式引數:
buf:資料快取區
sector:扇區
cnt:扇區數
回傳值:
0,ok;其他,失敗.
說 明:
SD卡一個扇區大小512位元組
*/
unsigned char SDCardReadData(unsigned char*buf,unsigned int sector,unsigned int cnt)
{
unsigned char r1;
if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//轉換為位元組地址
if(cnt==1)
{
r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//讀命令
if(r1==0) //指令發送成功
{
r1=SDCardRecvData(buf,512); //接收512個位元組
}
}else
{
r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//連續讀命令
do
{
r1=SDCardRecvData(buf,512);//接收512個位元組
buf+=512;
}while(--cnt && r1==0);
SendSDCardCmd(SDCard_CMD12,0,0X01); //發送停止命令
}
SDCardCancelCS();//取消片選
return r1;//
}
/*
函式功能:向SD卡寫資料
函式引數:
buf:資料快取區
sector:起始扇區
cnt:扇區數
回傳值:
0,ok;其他,失敗.
說 明:
SD卡一個扇區大小512位元組
*/
unsigned char SDCardWriteData(unsigned char*buf,unsigned int sector,unsigned int cnt)
{
unsigned char r1;
if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//轉換為位元組地址
if(cnt==1)
{
r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//讀命令
if(r1==0)//指令發送成功
{
r1=SDCardSendData(buf,0xFE);//寫512個位元組
}
}
else
{
if(SD_Type!=SDCard_TYPE_MMC)
{
SendSDCardCmd(SDCard_CMD55,0,0X01);
SendSDCardCmd(SDCard_CMD23,cnt,0X01);//發送指令
}
r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//連續讀命令
if(r1==0)
{
do
{
r1=SDCardSendData(buf,0xFC);//接收512個位元組
buf+=512;
}while(--cnt && r1==0);
r1=SDCardSendData(0,0xFD);//接收512個位元組
}
}
SDCardCancelCS();//取消片選
return r1;//
}
static struct gendisk *tiny4412_blockdev_disk;
static struct request_queue *tiny4412_blockdev_queue;
static int major; //主設備號
static DEFINE_SPINLOCK(tiny4412_blockdev_lock); //自旋鎖
static unsigned int sd_size=0; //存放SD卡回傳的容量扇區數量單位(512位元組)
static int tiny4412_blockdev_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
/* 容量=heads*cylinders*sectors*512
* 存盤容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數
*/
geo->heads = 200; /*磁頭(一般一個盤面有兩個磁頭,正面一個/反面一個)*/
geo->cylinders = 32; /*柱面(一般一個盤面上有32個柱面)每個盤片32個磁道)*/
geo->sectors = sd_size/200/32; /*扇區,一般每個磁道上有12個扇區,這里需要根據前面柱面和磁頭進行計算,不能亂填*/
return 0;
}
static struct block_device_operations tiny4412_blockdev_fops = {
.owner = THIS_MODULE,
.getgeo = tiny4412_blockdev_getgeo,
};
static void do_tiny4412_blockdev_request(struct request_queue * q)
{
struct request *req;
req = blk_fetch_request(q);
while (req)
{
unsigned long start = blk_rq_pos(req); /*起始扇區位置*/
unsigned long len = (blk_rq_cur_bytes(req)>>9); /*當前操作的扇區數量*/
int err = 0;
if(rq_data_dir(req) == READ) /*如果是讀*/
{
if(SDCardReadData(req->buffer,start,len))
{
printk(KERN_ERR"read error!\r\n");
printk("read ---> nsect=%ld,sector=%ld\r\n",len,start);
}
}
else
{
if(SDCardWriteData(req->buffer,start,len))
{
printk(KERN_ERR"write error!\r\n");
printk("write ---> nsect=%ld,sector=%ld\r\n",len,start);
}
}
if (!__blk_end_request_cur(req, err)) /*判斷是否處理完畢請求*/
req = blk_fetch_request(q); /*繼續處理下一個請求*/
}
}
static int tiny4412_blockdev_init(void)
{
/*初始化SD卡*/
if(SDCardDeviceInit())
{
printk("SD卡初始化失敗!\r\n");
return -1;
}
/*檢測SD卡大小*/
sd_size=GetSDCardSectorCount(); //檢測SD卡大小,回傳值右移11位得到以M為單位的容量
printk("SD卡Sizeof:%dM secnt=%d\r\n",sd_size>>11,sd_size);
/* 1. 分配一個gendisk結構體 */
tiny4412_blockdev_disk = alloc_disk(16); /* 次設備號個數: 磁區個數+1 */
/* 2. 設定 */
/* 2.1 分配/設定佇列: 提供讀寫能力 */
tiny4412_blockdev_queue = blk_init_queue(do_tiny4412_blockdev_request, &tiny4412_blockdev_lock);
tiny4412_blockdev_disk->queue = tiny4412_blockdev_queue;
/* 2.2 設定其他屬性: 比如容量 */
major = register_blkdev(0,"blockdev"); /* cat /proc/devices */
printk("注冊major=%d\n",major);
tiny4412_blockdev_disk->major = major;
tiny4412_blockdev_disk->first_minor = 0;
sprintf(tiny4412_blockdev_disk->disk_name, "tiny4412_blockdev");
tiny4412_blockdev_disk->fops = &tiny4412_blockdev_fops;
set_capacity(tiny4412_blockdev_disk, sd_size); //設定磁盤容量
/* 3. 硬體相關操作 */
/* 4. 注冊 */
add_disk(tiny4412_blockdev_disk);
return 0;
}
static void tiny4412_blockdev_exit(void)
{
printk("注銷major=%d\n",major);
unregister_blkdev(major,"blockdev");
del_gendisk(tiny4412_blockdev_disk);
put_disk(tiny4412_blockdev_disk);
blk_cleanup_queue(tiny4412_blockdev_queue);
/*釋放虛擬地址*/
iounmap(SD_GPBCON);
iounmap(SD_GPBDAT);
}
module_init(tiny4412_blockdev_init);
module_exit(tiny4412_blockdev_exit);
MODULE_LICENSE("GPL");
四、 磁盤的構造分析
硬碟是電腦主要的存盤媒介之一,由一個或者多個鋁制或者玻璃制的碟片組成,碟片外覆寫有鐵磁性材料,
硬碟有固態硬碟(SSD 盤,新式硬碟)、機械硬碟(HDD 傳統硬碟)、混合硬碟(HHD 一塊基于傳統機械硬碟誕生出來的新硬碟),SSD采用閃存顆粒來存盤,HDD采用磁性碟片來存盤,混合硬碟(HHD: Hybrid Hard Disk)是把磁性硬碟和閃存集成到一起的一種硬碟,絕大多數硬碟都是固定硬碟,被永久性地密封固定在硬碟驅動器中,

硬碟是集精密機械、微電子電路、電磁轉換為一體的電腦存盤設備,它存盤著電腦系統資源和重要的資訊及資料,這些因素使硬碟在PC機中成為最為重要的一個硬體設備
最精密的部分--磁頭:
由于磁頭作業的性質,對磁感應的要求非常高,磁頭是在高速旋轉的盤片上懸浮的,懸浮力來自盤片旋轉帶動的氣流,磁頭必須懸浮而不是接觸盤面,避免盤面和磁頭發生相互接觸的磨損,
硬碟存盤的介質--盤片:
盤片是以堅固耐用的材料為盤基,將磁粉附著在平滑的鋁合金或玻璃圓盤基上,這些磁粉被劃分成稱為磁道的若干個同心圓,每個同心圓就好像有無數的小磁鐵,它們分別代表著0和1狀態,當小磁鐵受到來自磁頭的磁力影響時,其排列方向會隨之改變,
下面主要講解機械硬碟的構造,機械硬碟是由一個個盤片組成的,我們先從個盤片結構講起,下圖圖中的一圈圈灰色同心圓為一條條磁道,從圓心向外畫直線,可以將磁道劃分為若干個弧段,每個磁道上一個弧段被稱之為一個扇區(圖踐綠色部分),扇區是磁盤的最小組成單元,通常是512位元組,


磁盤的常見引數如下:
- 磁頭(head)
- 磁道(track)
- 柱面(cylinder)
- 扇區(sector)
- 圓盤(platter)
上圖2中磁盤是一個有 3個盤面6個磁頭(一個盤面有正反面兩個磁頭,兩面都可以獨立讀寫),7個柱面(每個盤片7個磁道) 的磁盤,每條磁道有12個扇區,所以此磁盤的容量為6*7*12*512位元組,
計算方法: 存盤容量 = 磁頭數 × 磁道(柱面)數 × 每道扇區數 × 每扇區位元組數,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/296522.html
標籤:其他
