1. 程式人生 > >linux 驅動入門程式,寫一個塊裝置驅動 (1)

linux 驅動入門程式,寫一個塊裝置驅動 (1)

+---------------------------------------------------+
|                 寫一個塊裝置驅動                  |
+---------------------------------------------------+
| 作者:趙磊                                        |
| 網名:OstrichFly、飛翔的鴕鳥                      |
| email: [email protected]                      |
+---------------------------------------------------+
| 文章版權歸原作者所有。                            |
| 大家可以自由轉載這篇文章,但原版權資訊必須保留。  |
| 如需用於商業用途,請務必與原作者聯絡,若因未取得  |
| 授權而收起的版權爭議,由侵權者自行負責。          |
+---------------------------------------------------+

同樣是讀書,讀小說可以行雲流水,讀完後心情舒暢,意猶未盡;讀電腦書卻舉步艱難,讀完後目光呆滯,也是意猶未盡,只不過未盡的是痛苦的回憶。
研究證明,痛苦的記憶比快樂的更難忘記,因此電腦書中的內容比小說記得持久。
而這套教程的目的是要打破這種狀況,以至於讀者在忘記小說內容忘記本文。

在這套教程中,我們通過寫一個建立在記憶體中的塊裝置驅動,來學習linux核心和相關裝置驅動知識。
選擇寫塊裝置驅動的原因是:
1:容易上手
2:可以牽連出更多的核心知識
3:像本文這樣的塊裝置驅動教程不多,所以需要一個

好吧,扯淡到此結束,我們開始寫了。

本章的目的用盡可能最簡單的方法寫出一個能用的塊裝置驅動。
所謂的能用,是指我們可以對這個驅動生成的塊裝置進行mkfs,mount和讀寫檔案。
為了儘可能簡單,這個驅動的規模不是1000行,也不是500行,而是100行以內。

這裡插一句,我們不打算在這裡介紹如何寫模組,理由是介紹的文章已經滿天飛舞了。
如果你能看得懂、並且成功地編譯、運行了這段程式碼,我們認為你已經達到了本教程的入學資格,
當然,如果你不幸的卡在這段程式碼中,那麼請等到搞定它以後再往下看:
mod.c:
#include <linux/module.h>

static int __init init_base(void)
{
        printk("----Hello. World----\n");
        return 0;
}

static void __exit exit_base(void)
{
        printk("----Bye----\n");
}

module_init(init_base);
module_exit(exit_base);

MODULE_LICENSE ("GPL");
MODULE_AUTHOR("Zhao Lei");
MODULE_DESCRIPTION("For test");

Makefile:
obj-m := mod.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)

default:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
        $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) clean
        rm -rf Module.markers modules.order Module.symvers


好了,這裡我們假定你已經搞定上面的最簡單的模組了,懂得什麼是看模組,以及簡單模組的編寫、編譯、載入和解除安裝。
還有就是,什麼是塊裝置,什麼是塊裝置驅動,這個也請自行google吧,因為我們已經迫不及待要寫完程式下課。

為了建立一個可用的塊裝置,我們需要做......1件事情:
1:用add_disk()函式向系統中新增這個塊裝置
   新增一個全域性的
   static struct gendisk *simp_blkdev_disk;
   然後申明模組的入口和出口:
   module_init(simp_blkdev_init);
   module_exit(simp_blkdev_exit);
   然後在入口處新增這個裝置、出口處私房這個裝置:
   static int __init simp_blkdev_init(void)
   {
           add_disk(simp_blkdev_disk);
        return 0;
   }
   static void __exit simp_blkdev_exit(void)
   {
           del_gendisk(simp_blkdev_disk);
   }

當然,在新增裝置之前我們需要申請這個裝置的資源,這用到了alloc_disk()函式,因此模組入口函式simp_blkdev_init(void)應該是:
   static int __init simp_blkdev_init(void)
   {
        simp_blkdev_disk = alloc_disk(1);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }

           add_disk(simp_blkdev_disk);

        return 0;

   err_alloc_disk:
        return ret;
   }
還有別忘了在解除安裝模組的程式碼中也加一個行清理函式:
  put_disk(simp_blkdev_disk);

還有就是,裝置有關的屬性也是需要設定的,因此在alloc_disk()和add_disk()之間我們需要:
        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major = ?1;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops = ?2;
        simp_blkdev_disk->queue = ?3;
        set_capacity(simp_blkdev_disk, ?4);

SIMP_BLKDEV_DISKNAME其實是這個塊裝置的名稱,為了紳士一些,我們把它定義成巨集了:
#define SIMP_BLKDEV_DISKNAME        "simp_blkdev"

這裡又引出了4個問號。(天哪,是不是有種受騙的感覺,像是陪老婆去做頭髮)
第1個問號:
  每個裝置需要對應的主、從驅動號。
  我們的裝置當然也需要,但很明顯我不是腦科醫生,因此跟寫linux的那幫瘋子不熟,得不到預先為我保留的裝置號。
  還有一種方法是使用動態分配的裝置號,但在這一章中我們希望儘可能做得簡單,因此也不採用這種方法。
  那麼我們採用的是:搶別人的裝置號。
  我們手頭沒有AK47,因此不敢幹的太轟轟烈烈,而偷偷摸摸的事情倒是可以考慮的。
  柿子要撿軟的捏,而我們試圖找出一個不怎麼用得上的裝置,然後搶他的ID。
  開啟linux/include/linux/major.h,把所有的裝置一個個看下來,我們覺得最勝任被搶裝置號的傢伙非COMPAQ_SMART2_XXX莫屬。
  第一因為它不強勢,基本不會被用到,因此也不會造成衝突;第二因為它有錢,從COMPAQ_SMART2_MAJOR到COMPAQ_SMART2_MAJOR7有那8個之多的裝置號可以被搶,不過癮的話還有它妹妹:COMPAQ_CISS_MAJOR~COMPAQ_CISS_MAJOR7。
  為了讓搶劫顯得紳士一些,我們在外面又定義一個巨集:
  #define SIMP_BLKDEV_DEVICEMAJOR        COMPAQ_SMART2_MAJOR
  然後在?1的位置填上SIMP_BLKDEV_DEVICEMAJOR。
第2個問號:
  gendisk結構需要設定fops指標,雖然我們用不到,但該設還是要設的。
  好吧,就設個空得給它:
  在全域性部分新增:
  struct block_device_operations simp_blkdev_fops = {
          .owner                = THIS_MODULE,
  };
  然後把?2的位置填上&simp_blkdev_fops。
第3個問號:
  這個比較麻煩一些。
  首先介紹請求佇列的概念。對大多數塊裝置來說,系統會把對塊裝置的訪問需求用bio和bio_vec表示,然後提交給通用塊層。
  通用塊層為了減少塊裝置在尋道時損失的時間,使用I/O排程器對這些訪問需求進行排序,以儘可能提高塊裝置效率。
  關於I/O排程器在本章中不打算進行深入的講解,但我們必須知道的是:
  1:I/O排程器把排序後的訪問需求通過request_queue結構傳遞給塊裝置驅動程式處理
  2:我們的驅動程式需要設定一個request_queue結構
  申請request_queue結構的函式是blk_init_queue(),而呼叫blk_init_queue()函式時需要傳入一個函式的地址,這個函式擔負著處理對塊裝置資料的請求。
  因此我們需要做的就是:
  1:實現一個static void simp_blkdev_do_request(struct request_queue *q)函式。
  2:加入一個全域性變數,指向塊裝置需要的請求佇列:
     static struct request_queue *simp_blkdev_queue;
  3:在載入模組時用simp_blkdev_do_request()函式的地址作引數呼叫blk_init_queue()初始化一個請求佇列:
     simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
     if (!simp_blkdev_queue) {
             ret = -ENOMEM;
             goto err_init_queue;
     }
  4:解除安裝模組時把simp_blkdev_queue還回去:
     blk_cleanup_queue(simp_blkdev_queue);
  5:在?3的位置填上simp_blkdev_queue。
第4個問號:
  這個還好,比前面的簡單多了,這裡需要設定塊裝置的大小。
  塊裝置的大小使用扇區作為單位設定,而扇區的大小預設是512位元組。
  當然,在把位元組為單位的大小轉換為以扇區為單位時,我們需要除以512,或者右移9位可能更快一些。
  同樣,我們試圖把這一步也做得紳士一些,因此使用巨集定義了塊裝置的大小,目前我們定為16M:
  #define SIMP_BLKDEV_BYTES        (16*1024*1024)
  然後在?4的位置填上SIMP_BLKDEV_BYTES>>9。

看到這裡,是不是有種身陷茫茫大海的無助感?並且一波未平,一波又起,在搞定這4個問號的同時,居然又引入了simp_blkdev_do_request函式!
當然,如果在身陷茫茫波濤中時你認為到處都是海,因此絕望,那麼恭喜你可以不必捱到65歲再退休;
反之,如果你認為到處都是沒有三聚氰胺鮮魚,並且隨便哪個方向都是岸時,那麼也恭喜你,你可以活著回來繼續享受身為納稅人的榮譽。

為了理清思路,我們把目前為止涉及到的程式碼整理出來:
#define SIMP_BLKDEV_DEVICEMAJOR        COMPAQ_SMART2_MAJOR
#define SIMP_BLKDEV_DISKNAME        "simp_blkdev"
#define SIMP_BLKDEV_BYTES        (16*1024*1024)

static struct request_queue *simp_blkdev_queue;
static struct gendisk *simp_blkdev_disk;

static void simp_blkdev_do_request(struct request_queue *q);

struct block_device_operations simp_blkdev_fops = {
        .owner                = THIS_MODULE,
};

static int __init simp_blkdev_init(void)
{
        int ret;

        simp_blkdev_queue = blk_init_queue(simp_blkdev_do_request, NULL);
        if (!simp_blkdev_queue) {
                ret = -ENOMEM;
                goto err_init_queue;
        }

        simp_blkdev_disk = alloc_disk(1);
        if (!simp_blkdev_disk) {
                ret = -ENOMEM;
                goto err_alloc_disk;
        }

        strcpy(simp_blkdev_disk->disk_name, SIMP_BLKDEV_DISKNAME);
        simp_blkdev_disk->major = SIMP_BLKDEV_DEVICEMAJOR;
        simp_blkdev_disk->first_minor = 0;
        simp_blkdev_disk->fops = &simp_blkdev_fops;
        simp_blkdev_disk->queue = simp_blkdev_queue;
        set_capacity(simp_blkdev_disk, SIMP_BLKDEV_BYTES>>9);
        add_disk(simp_blkdev_disk);

        return 0;

err_alloc_disk:
        blk_cleanup_queue(simp_blkdev_queue);
err_init_queue:
        return ret;
}

static void __exit simp_blkdev_exit(void)
{
        del_gendisk(simp_blkdev_disk);
        put_disk(simp_blkdev_disk);
        blk_cleanup_queue(simp_blkdev_queue);
}

module_init(simp_blkdev_init);
module_exit(simp_blkdev_exit);

剩下部分的不多了,真的不多了。請相信我,因為我不在質監局上班。
我寫的文章誠實可靠,並且不拿你納稅的錢。

我們還有一個最重要的函式需要實現,就是負責處理塊裝置請求的simp_blkdev_do_request()。

首先我們看看究竟把塊裝置的資料以什麼方式放在記憶體中。
畢竟這是在第1章,因此我們將使用最simple的方式實現,也就是,陣列。
我們在全域性程式碼中定義:
unsigned char simp_blkdev_data[SIMP_BLKDEV_BYTES];
對驅動程式來說,這個陣列看起來大了一些,如果不幸被懂行的人看到,將100%遭到最無情、最嚴重的鄙視。
而我們卻從極少數公僕那裡學到了最有效的應對之策,那就是:無視他,然後把他定為成“不明真相的群眾”。

然後我們著手實現simp_blkdev_do_request。
這裡介紹elv_next_request()函式,原型是:
struct request *elv_next_request(struct request_queue *q);
用來從一個請求佇列中拿出一條請求(其實嚴格來說,拿出的可能是請求中的一段)。
隨後的處理請求本質上是根據rq_data_dir(req)返回的該請求的方向(讀/寫),把塊裝置中的資料裝入req->buffer、或是把req->buffer中的資料寫入塊裝置。
剛才已經提及了與request結構相關的rq_data_dir()巨集和.buffer成員,其他幾個相關的結構成員和函式是:
request.sector:請求的開始磁軌
request.current_nr_sectors:請求磁軌數
end_request():結束一個請求,第2個引數表示請求處理結果,成功時設定為1,失敗時設定為0或者錯誤號。
因此我們的simp_blkdev_do_request()函式為:
static void simp_blkdev_do_request(struct request_queue *q)
{
        struct request *req;
        while ((req = elv_next_request(q)) != NULL) {
                if ((req->sector + req->current_nr_sectors) << 9
                        > SIMP_BLKDEV_BYTES) {
                        printk(KERN_ERR SIMP_BLKDEV_DISKNAME
                                ": bad request: block=%llu, count=%u\n",
                                (unsigned long long)req->sector,
                                req->current_nr_sectors);
                        end_request(req, 0);
                        continue;
                }

                switch (rq_data_dir(req)) {
                case READ:
                        memcpy(req->buffer,
                                simp_blkdev_data + (req->sector << 9),
                                req->current_nr_sectors << 9);
                        end_request(req, 1);
                        break;
                case WRITE:
                        memcpy(simp_blkdev_data + (req->sector << 9),
                                req->buffer, req->current_nr_sectors << 9);
                        end_request(req, 1);
                        break;
                default:
                        /* No default because rq_data_dir(req) is 1 bit */
                        break;
                }
        }
}
函式使用elv_next_request()遍歷struct request_queue *q中使用struct request *req表示的每一段,首先判斷這個請求是否超過了我們的塊裝置的最大容量,
然後根據請求的方向rq_data_dir(req)進行相應的請求處理。由於我們使用的是指簡單的陣列,因此請求處理僅僅是2條memcpy。
memcpy中也牽涉到了扇區號到線性地址的轉換操作,我想對堅持到這裡的讀者來說,這個操作應該不需要進一步解釋了。

編碼到此結束,然後我們試試這個程式:
首先編譯:
# make
make -C /lib/modules/2.6.18-53.el5/build SUBDIRS=/root/test/simp_blkdev/simp_blkdev_step1 modules
make[1]: Entering directory `/usr/src/kernels/2.6.18-53.el5-i686'
  CC [M]  /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.o
  Building modules, stage 2.
  MODPOST
  CC      /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.mod.o
  LD [M]  /root/test/simp_blkdev/simp_blkdev_step1/simp_blkdev.ko
make[1]: Leaving directory `/usr/src/kernels/2.6.18-53.el5-i686'
#
載入模組
# insmod simp_blkdev.ko
#
用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/simp_blkdev
brw-r----- 1 root disk 72, 0 11-10 18:13 /dev/simp_blkdev
#
在塊裝置中建立檔案系統,這裡我們建立常用的ext3。
當然,作為通用的塊裝置,建立其他型別的檔案系統也沒問題。
# mkfs.ext3 /dev/simp_blkdev
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/simp_blkdev /mnt/temp1
#
看看現在的mount表
# mount
...
/dev/simp_blkdev on /mnt/temp1 type ext3 (rw)
#
看看現在的模組引用計數,從剛才的0變成1了,
原因是我們mount了。
# lsmod
Module                  Size  Used by
simp_blkdev         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/simp_blkdev         15863      1440     13604  10% /mnt/temp1
#
再全刪了玩玩
# rm -rf /mnt/temp1/*
#
看看刪完了沒有
# ls /mnt/temp1
#
好了,大概玩夠了,我們把檔案系統umount掉
# umount /mnt/temp1
#
模組的引用計數應該還原成0了吧
# lsmod
Module                  Size  Used by
simp_blkdev         16784008  0
...
#
最後一步,移除模組
# rmmod simp_blkdev
#

這是這部教程的第1章,不好意思的是,內容比預期還是難了一些。
當初還有一種考慮是在本章中僅僅實現一個寫了就丟的塊裝置驅動,也就是說,對這個塊裝置的操作只能到mkfs這一部,而不能繼續mount,因為剛才寫的資料全被扔了。
或者更簡單些,僅僅寫一個hello world的模組。
但最後還是寫成了現在這樣沒,因為我覺得拿出一個真正可用的塊裝置驅動程式對讀者來說更有成就感。

無論如何,本章是一個開始,而你,已經跨入了學習塊裝置驅動教室的大門,或者通俗來說,上了賊船。
而在後續的章節中,我們將陸續完善對這個程式,通過追加或者強化這個程式,來學習與塊裝置有關、或與塊裝置無關但與linux有關的方方面面。
總之,我希望通過這部教程,起碼讓讀者學到有用的知識,或者更進一步,引導讀者對linux的興趣,甚至領悟學習一切科學所需要的鑽研精神。

作為第一章的結尾,引用我在另一篇文章中的序言:
謹以此文向讀者示範什麼叫做嚴謹的研究。
呼喚踏實的治學態度,反對浮躁的論壇風氣。
--OstrichFly

<未完,待續>

相關推薦

linux 驅動入門程式一個裝置驅動 (1)

+---------------------------------------------------+ |                 寫一個塊裝置驅動                  | +---------------------------------------------------+ |

轉:一個裝置驅動

----------------------- Page 1----------------------- 第 1章 +---------------------------------------------------+ |                 寫一

Linux i2c子系統(一) _動手一個i2c裝置驅動

i2c匯流排是一種十分常見的板級匯流排,本文以linux3.14.0為參考, 討論Linux中的i2c驅動模型並利用這個模型寫一個mpu6050的驅動, 最後在應用層將mpu6050中的原始資料讀取出來 i2c子系統框架 下圖就是我理解的i2c驅動框架示意圖, 類似中斷子系

Linux Device Drivers》第十六章 裝置驅動程式——note

簡介 一個塊裝置驅動程式主要通過傳輸固定大小的隨機資料來訪問裝置 Linux核心視塊裝置為與字元裝置相異的基本裝置型別 Linux塊裝置驅動程式介面使得塊裝置可以發揮其最大的功效,但是其複雜程式又是程式設計者必須面對的一個問題 一個數據塊指的是固

Java:假設車庫有3個車位(可以通過boolean[]陣列來表示車庫)可以停車一個程式模擬多個使用者開車離開停車入庫的效果。注意:車位有車時不能停車。

假設車庫有3個車位(可以通過boolean[]陣列來表示車庫)可以停車,寫一個程式模擬多個使用者開車離開,停車入庫的效果。注意:車位有車時不能停車。 1)使用阻塞佇列來實現(BlockingQueue<T>) Producer類 package com.多執行緒停車問

一個程式接受一個字串然後輸出該字串反轉後的字串

輸入描述: 輸入N個字元     輸出描述:輸出該字串反轉後的字串 #include <iostream>  #include <string> using namespace std; int main(){     string s ;    

一個程式接受一個由字母和數字組成的字串一個字元然後輸出輸入字串中含有該字元的個數。不區分大小寫。

輸入描述:輸入一個有字母和數字以及空格組成的字串,和一個字元。     輸出描述:輸出輸入字串中含有該字元的個數。 #include <string> #include <iostream> using namespace std; int main

日本某地發生了一件謀殺案警察通過排查確定殺人凶手必為4個 嫌疑犯的一個。現在請根據這些資訊一個程式來確定到底誰是凶手。

題目: 日本某地發生了一件謀殺案,警察通過排查確定殺人凶手必為4個  嫌疑犯的一個。以下為4個嫌疑犯的供詞。  A說:不是我。  B說:是C。  C說:是D。  D說:C在胡說  已知3個人說了真話,

某公司的僱員分為以下若干類。一個程式把若干各種型別的員工放在一個Employee 數組裡一個函式打印出某月每個員工的工資數額

某公司的僱員分為以下若干類:Employee:這是所有員工總的父類,屬性:員工的生日月份。方法:getSalary(int month) 根據引數月份來確定工資,如果該月員工過生日,則公司會額外獎勵100 元。SalariedEmployee:Employee 的子類,拿固定工資的員工。屬性:月薪Hourly

linux驅動由淺入深系列:裝置驅動之三(裝置驅動結構分析以mmc為例)

linux驅動由淺入深系列:塊裝置驅動之一(高通eMMC分割槽例項)前一篇文章介紹了塊裝置驅動在linux框架張的位置關係,本文來分析一下驅動本身。塊裝置驅動的模型還是基本基於字元裝置驅動的,可以簡單理解為塊裝置僅僅增加了操作緩衝區,對使用者操作請求進行佇列重排。因此只在有了

華為計算字元個數一個程式接受一個有字母和數字以及空格組成的字串一個字元然後輸出輸入字串中含有該字元的個數。不區分大小寫。

寫出一個程式,接受一個有字母和數字以及空格組成的字串,和一個字元,然後輸出輸入字串中含有該字元的個數。不區分大小寫。 自己編寫的一個程式,聽牛客網的左神說程式設計也跟高考一樣要不斷地刷題才會有感覺,自

linux裝置驅動程式示例(適用於高版本核心3.16.0

1. 字元裝置與塊裝置的 I/O 操作主要有如下不同點:    (1)塊裝置只能以塊為單位接受輸入和返回輸出,而字元裝置則以位元組為單位。大多數裝置是字元裝置,因為它們不需要緩衝而且不以固定塊大小進行操作。    (2)塊裝置對於 I/O 請求有對應的緩衝區,因此它們可以選擇

Linux裝置驅動程式分析

struct sbull_dev {         int size;                       /* Device size in sectors */         u8 *data;                       /* The data array */       

Linux裝置驅動程式》——裝置驅動程式

一、概論  1、一個塊裝置驅動程式主要同通過傳輸固定大小的隨機資料來訪問裝置。Linux核心視塊裝置為與字元裝置相異的基本裝置型別,因此塊裝置驅動程式有自己完成特定任務的       接 口。   2、高效的塊裝置驅動程式在功能上是嚴格要求的,並不僅僅體現在使用者應用程式的

淺談python自動化測試資料驅動一個真正通用的驅動

現如今python越來越流行,這種指令碼語言讓自動化測試變的簡潔高效;當然不論是用java還是python或者其他框架時,都有一個不能迴避的問題-----那就是資料問題 資料的靈活性不僅可以讓case覆蓋度更大,還可以避免出現因為需求變更導致的測試指令碼“傷筋動骨”式的改造

嵌入式linux裝置驅動程式概念,框架

不能像字元裝置驅動那樣,直接提供簡單的讀寫函式,效率過於低下 要將讀寫函式放入佇列,優化後,再去執行 框架: “` 框架: app: open,read,write “1.txt” ——————————————— 檔案的讀寫 檔

公共汽車一個程式告訴司機怎麼走能接到最多的乘客。

【問題描述】 一個城市的道路成了像棋盤那樣的網狀,南北向的路有n條,並由西向東從1標記到n,東西向的路有m條,並從南向北從1標記到m,每一個交叉點代表一個路口,有的路口有正在等車的乘客。一輛公共汽車將從(1,1)點駛到(n,m)點,車只能向東或者向北開. 寫一個程式,告訴司

牛客網程式設計練習——一個程式接受一個由字母和數字組成的字串一個字元然後輸出輸入字串中含有該字元的個數。不區分大小寫。

題目描述 寫出一個程式,接受一個由字母和數字組成的字串,和一個字元,然後輸出輸入字串中含有該字元的個數。不區分大小寫。 輸入描述: 輸入一個有字母和數字以及空格組成的字串,和一個字元。 輸出描述: 輸出輸入字串中含有該字元的個數。 示例1 輸入 複