1. 程式人生 > >Linux驅動開發04:塊裝置驅動和網路裝置驅動

Linux驅動開發04:塊裝置驅動和網路裝置驅動

介紹

因為塊裝置驅動和網路裝置驅動實際中用得較少,所以只給出驅動模板,我也沒有具體測試,等到實際用到是再研究吧,溜了溜了。

塊裝置驅動模板

struct xxx_dev {
    int size; 
    struct request_queue *queue;    /* The device request queue */
    struct gendisk *gd;             /* The gendisk structure */
    spinlock_t lock;                // 如果使用請求佇列需要自旋鎖
}

static int
xxx_major; module_param(xxx_major, int, 0);// 主裝置號為0, 動態獲取 #define HARDSECT_SIZE xxx // HARDSECT_SIZE為硬碟的塊大小, 一般為512位元組 #define NSECTORS xxx // 硬碟總的扇區數 #define xxx_MINORS // 次裝置號最大數目, 一個分割槽對應一個次裝置號 /* * 這裡是具體的硬體操作 * @sector: 要寫/讀硬碟的那個扇區 * @nsect: 要寫/讀的扇區數目 */ static void xxx_disk_transfer(struct
vmem_disk_dev *dev, unsigned long sector, unsigned long nsect, char *buffer, int write) { unsigned long offset = sector*KERNEL_SECTOR_SIZE; unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE; if ((offset + nbytes) > dev->size) { printk (KERN_NOTICE "Beyond-end write (%ld %ld)\n"
, offset, nbytes); return; } if (write) ... else ... } // 這個是通用的 static int gen_xfer_bio(struct xxx_dev *dev, struct bio *bio) { struct bio_vec bvec; struct bvec_iter iter; sector_t sector = bio->bi_iter.bi_sector; // 要寫的扇區號 // 遍歷一個bio的所有bio_vec, __bio_kmap_atomic()將一段記憶體對映到 // 要操作的記憶體頁, 然後返回首地址. bio_cur_bytes(bio)獲取當前bio_vec.bv_len bio_for_each_segment(bvec, bio, iter) { char *buffer = __bio_kmap_atomic(bio, iter); xxx_disk_transfer(dev, sector, bio_cur_bytes(bio) >> 9, buffer, bio_data_dir(bio) == WRITE); sector += bio_cur_bytes(bio) >> 9; __bio_kunmap_atomic(buffer); } return 0; } // 不使用請求佇列繫結該函式 static void xxx_make_request(struct request_queue *q, struct bio *bio) { struct xxx_dev *dev = q->queuedata; int status; status = gen_xfer_bio(dev, bio); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4,3,0) bio->bi_error = status; bio_endio(bio); #else bio_endio(bio, status); #endif } // 使用請求佇列繫結該函式 static void vmem_disk_request(struct request_queue *q) { struct request *req; struct bio *bio; // 1. 從請求佇列中拿出一個請求 while ((req = blk_peek_request(q)) != NULL) { struct xxx_dev *dev = req->rq_disk->private_data; if (req->cmd_type != REQ_TYPE_FS) { printk (KERN_NOTICE "Skip non-fs request\n"); blk_start_request(req); __blk_end_request_all(req, -EIO); continue; } blk_start_request(req); // 2. 遍歷請求中每一個bio __rq_for_each_bio(bio, req) gen_xfer_bio(dev, bio); __blk_end_request_all(req, 0); } } // 1. 申請主裝置號 xxx_major = register_blkdev(xxx_major, "vmem_disk"); // 2. 記錄硬碟大小 struct xxx_dev *dev = kzalloc(NSECTORS*sizeof(struct xxx_dev), GFP_KERNEL); dev->size = NSECTORS*HARDSECT_SIZE; // 3.1 不使用請求佇列, 繫結xxx_make_request()函式 dev->queue = blk_alloc_queue(GFP_KERNEL); blk_queue_make_request(dev->queue, xxx_make_request); // 3.2 使用請求佇列, 繫結xxx_request()函式 dev->queue = blk_init_queue(xxx_request, &dev->lock); // 4. 設定請求佇列的邏輯塊大小, 將私有資料xxx繫結到佇列中 blk_queue_logical_block_size(dev->queue, HARDSECT_SIZE); dev->queue->queuedata = dev; // 5. 申請gendisk(相當於cdev)並賦值, 然後新增到系統 dev->gd = alloc_disk(xxx_MINORS); // 6. 給gendisk賦值, 然後新增到系統 dev->gd->major = xxx_major; dev->gd->first_minor = 0; dev->gd->fops = &xxx_ops; // 繫結block_device_operations dev->gd->queue = dev->queue; dev->gd->private_data = dev; // xxx繫結到了gendisk中 set_capacity(dev->gd, NSECTORS*(HARDSECT_SIZE/KERNEL_SECTOR_SIZE)); add_disk(dev->gd);

使用者每進行一次對硬碟的操作, 都會被作業系統處理成一個請求, 然後放入相應的請求佇列中(該請求佇列由驅動定義), 一個請求包含若干個bio, 一個bio又包含若干個bio_vec

bio_vec指向使用者需要寫入硬碟的資料, 它由如下三個引數組成:

 struct bio_vec {
     struct page *bv_page;      // 資料所在頁面的首地址
     unsigned int bv_len;       // 資料長度
     unsigned int bv_offset;    // 頁面偏移量
 }

一個bio還包含一個bvec_iter, 它由如下4個引數組成:

 struct bvec_iter {
    sector_t        bi_sector;      // 要操作的扇區號
    unsigned int    bi_size;        // 剩餘的bio_vec數目
    unsigned int    bi_idx;         // 當前的bio_vec的索引號
    unsigned int    bi_bvec_done;   // 當前bio_vec中已完成的位元組數
};

通過bio, 再結合其中的bio_iter就可以找到當前的bio_vec.

使用者可能發出若干對硬碟的操作, 也就對應著若干個bio, 作業系統能夠按照一定的演算法將這些操作重新組合成一個請求, 硬碟執行這個請求就能夠以最高的效率將資料讀取/寫入.

以上操作僅適用於機械硬碟, 因為機械硬碟按照扇區順序讀寫能夠達到最高效率. 對於RAMDISK, ZRAM等可以隨機訪問的裝置, 請求佇列是沒有必要的, 因此不需要請求佇列.

網路裝置驅動模板

static void xxx_rx (struct net_device *dev)
{
    struct xxx_priv *priv = netdev_priv(dev);
    struct sk_buff *skb;
    int length;

    length = get_rev_len(...); // 獲取要接收資料的長度
    skb = dev_alloc_skb(length + 2);

    // 對齊
    skb_reserve(skb, 2);
    skb->dev = dev;

    // 硬體讀取資料到skb
    ...

    // 獲取上層協議型別
    skb->protocol = eth_type_trans(skb, dev);

    // 把資料交給上層
    netif_rx(skb);

    // 記錄接收時間
    dev->last_rx = jiffies;
    ...
}

// 中斷接收
static void xxx_interrupt(int irq, void *dev_id)
{
    struct net_device *dev = dev_id;
    status = ior(...); // 從硬體暫存器獲取中斷狀態
    switch (status) {
    case IRQ_RECEIVER_ENENT:    // 接收中斷
        xxx_rx(dev);
        break;
    ...
    }
}

static void xxx_timeout (struct net_device *dev)
{
    netif_stop_queue(dev);
    ...
    netif_wake_queue(dev);
}
// 資料傳送
static xxx_start_xmit (struct sk_buf *skb, struct net_device *dev)
{
    int len;
    char *data, shortpkt[ETH_ZLEN];
    // 傳送佇列未滿, 可以傳送
    if (xxx_send_available()) {
        data = skb->data;
        len = skb->len;
        // 幀長度小於最小長度, 後面補0
        if (len < ETH_ZLEN) {
            memset(shortpkt, 0, ETH_ZLEN);
            memcpy(shortpkt, skb->data, skb->len);
            len = ETH_ZLEN;
            data = shortpkt;
        }
    }
    // 記錄時間戳
    dev->trans_start = jiffies;

    if (...) {
        // 滿足一定新增使用硬體傳送資料
        xxx_hw_tx(data, len, dev);
    } else {
        // 否則停止佇列
        netif_stop_queue(dev);
        ...
    }
}

static int xxx_open(struct net_device *dev)
{
    ...
    // 申請埠, IRQ等
    ret = request_irq(dev->irq, &xxx_interrupt, 0, dev->name, dev);
    ...
    // 打開發送佇列
    netif_start_queue(dev);
    ...
}

static const struct net_device_ops xxx_netdev_ops = {
    .ndo_open = xxx_open,
    .ndo_stop = xxx_stop,
    .ndo_start_xmit = xxx_start_xmit,
    .ndo_tx_timeout = xxx_timeout,
    .ndo_do_ioctl = xxx_ioctl,
    ...
}

// 1. 給net_device結構體分配記憶體, xxx_priv為私有資料
// 私有資料是和net_device繫結到一起的
struct net_device *ndev;
struct xxx_priv *priv;
ndev = alloc_etherdev(sizeof(struct xxx_priv));

// 2. 硬體初始化, 並將net_device_ops和ethtool_ops與ndev繫結
xxx_hw_init();
ndev->netdev_ops = &xxx_netdev_ops;
ndev->ethtool_ops = &xxx_ethtool_ops;
ndev->watchdog_timeo = timeout; 

// 3. 獲取私有資料地址, 為私有資料賦值
priv = netdev_priv(ndev);
...

// 4. 註冊ndev
register_netdev(ndev);

// 5. 登出ndev
unregister_netdev(ndev);
free_netdev(ndev);