linux驅(qū)動:[2]字符設(shè)備驅(qū)動memdev(cdev結(jié)構(gòu)解析)

linux驅(qū)動:[2]字符設(shè)備驅(qū)動memdev

Linux 內(nèi)存模擬字符設(shè)備 驅(qū)動程序

測試平臺: Xunlong Orange Pi Zero

代碼一覽(解析見下方)

驅(qū)動程序以及Makefile如下:

  • memdev.c:
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

#ifndef MEMDEV_MAJOR
#define MEMDEV_MAJOR 254
#endif

#ifndef MEMDEV_NR_DEVS
#define MEMDEV_NR_DEVS 2
#endif

#ifndef MEMDEV_SIZE
#define MEMDEV_SIZE 4096
#endif

struct mem_dev {
    char *data;
    unsigned long size;
};

static int mem_major = MEMDEV_MAJOR;

module_param(mem_major, int, S_IRUGO);

struct mem_dev *mem_devp;

struct cdev cdev;

int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_dev *dev;

    int num = MINOR(inode->i_rdev);

    if (num >= MEMDEV_NR_DEVS)
        return -ENODEV;
    dev = &mem_devp[num];

    filp->private_data = dev;

    return 0;
}

int mem_release(struct inode *inode, struct file *filp)
{
    return 0;
}

static ssize_t mem_read(struct file *filp, char __user *buf, size_t size, loff_t *poss)
{
    unsigned long p = *poss;
    unsigned int count = size;
    int ret = 0;
    struct mem_dev *dev = filp->private_data;

    if (p >= MEMDEV_SIZE)
        return 0;
    if (count > MEMDEV_SIZE-p)
        count = MEMDEV_SIZE-p;

    if(copy_to_user(buf, (void*)(dev->data + p), count)) {
        ret = -EFAULT;
    } else {
        *poss += count;
        ret = count;
        printk(KERN_INFO "read %d bytes from %lu\n", count, p);
    }

    return ret;
}

static ssize_t mem_write(struct file *filp, const char __user *buf, size_t size, loff_t *poss)
{
    unsigned long p = *poss;
    unsigned int count = size;
    int ret = 0;
    struct mem_dev *dev = filp->private_data;

    if (p >= MEMDEV_SIZE)
        return 0;
    if (count > MEMDEV_SIZE-p)
        count = MEMDEV_SIZE - p;

    if (copy_from_user(dev->data + p, buf, count)) {
        ret = -EFAULT;
    } else {
        *poss += count;
        ret = count;
        printk(KERN_INFO "write %d bytes from %lu\n", count, p);
    }

    return ret;
}

static loff_t mem_llseek(struct file *filp, loff_t offset, int whence)
{
    loff_t newpos;

    switch (whence) {
    case 0:
        newpos = offset;
        break;
    case 1:
        newpos = filp->f_pos + offset;
        break;
    case 2:
        newpos = MEMDEV_SIZE - 1 + offset;
        break;
    default:
        return -EINVAL;
    }
    if ((newpos < 0) || (newpos > MEMDEV_SIZE))
        return -EINVAL;

    filp->f_pos = newpos;
    return newpos;
}

static const struct file_operations mem_fops =
{
    .owner = THIS_MODULE,
    .llseek = mem_llseek,
    .read = mem_read,
    .write = mem_write,
    .open = mem_open,
    .release = mem_release,
};

static int memdev_init(void)
{
    int result;
    int i;
    dev_t devno = MKDEV(mem_major, 0);

    if (mem_major)
        result = register_chrdev_region(devno, 2, "memdev");
    else {
        result = alloc_chrdev_region(&devno, 0, 2, "memdev");
        mem_major = MAJOR(devno);
    }

    if (result < 0)
        return result;

    cdev_init(&cdev, &mem_fops);
    cdev.owner = THIS_MODULE;
    cdev.ops = &mem_fops;

    cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);

    mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
    if (!mem_devp) {
        result = -ENOMEM;
        goto fail_malloc;
    }

    memset(mem_devp, 0, MEMDEV_NR_DEVS * sizeof(struct mem_dev));

    for (i = 0; i < MEMDEV_NR_DEVS; i++) {
        mem_devp[i].size = MEMDEV_SIZE;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
    }

    printk("memdev init success\n");
    return 0;

fail_malloc:
    unregister_chrdev_region(devno, 2);
    return result;
}

static void memdev_exit(void)
{
    cdev_del(&cdev);
    kfree(mem_devp);
    unregister_chrdev_region(MKDEV(mem_major, 0), 2);
    printk("memdev exit success\n");
}

MODULE_AUTHOR("Ziping Chen <techping.chan@gmail.com>");
MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);
  • Makefile:
obj-m := memdev.o #編譯進模塊
KERNELDIR := /lib/modules/4.11.0-rc4-00064-g89970a0-dirty/build #此處為linux內(nèi)核庫目錄
PWD := $(shell pwd) #獲取當(dāng)前目錄
OUTPUT := $(obj-m) $(obj-m:.o=.ko) $(obj-m:.o=.mod.o) $(obj-m:.o=.mod.c) modules.order Module.symvers
 
modules:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
    rm -rf $(OUTPUT)

在shell中使用以下命令裝載驅(qū)動程序:
<font color="red">(這里以主設(shè)備號為181進行測試)</font>

$ make
$ insmod memdev.ko mem_major=181
$ mknod /dev/memdev0 c 181 0

使用linux c進行測試:

  • memapp.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

int main()
{
    int fd;
    char buf[4096];

    strcpy(buf,"memory simulate char device test...\n");
    printf("original buf:%s\n",buf); 

    fd = open("/dev/memdev0",O_RDWR);
    if (fd == -1) {
        printf("open memdev failed!\n");
        return -1;
    }
    write(fd, buf, sizeof(buf));
    lseek(fd, 0, SEEK_SET);
    strcpy(buf, "nothing here");
    read(fd, buf, sizeof(buf));
    printf("read buf:%s\n", buf);

    return 0;
}

進行編譯坐儿、測試:

$ gcc -o memapp memapp.c

實驗成功!


代碼解析:

一、分配設(shè)備號

if (mem_major)
        result = register_chrdev_region(devno, 2, "memdev");
else {
        result = alloc_chrdev_region(&devno, 0, 2, "memdev");
        mem_major = MAJOR(devno);
}

如果定義的參數(shù)mem_major不為0(上面測試用了181),則進行靜態(tài)分配

register_chrdev_region(devno, 2, "memdev");//靜態(tài)分配設(shè)備號為devno的設(shè)備

如果mem_major為0則進行動態(tài)分配

alloc_chrdev_region(&devno, 0, 2, "memdev");//動態(tài)分配主設(shè)備號為devno絮短,次設(shè)備號為0的設(shè)備

二萎庭、初始化cdev結(jié)構(gòu)

/linux/include/linux/cdev.h:

struct cdev {
    struct kobject kobj;//每個 cdev 都是一個 kobject
    struct module *owner;//指向?qū)崿F(xiàn)驅(qū)動的模塊
    const struct file_operations *ops;//操縱這個字符設(shè)備文件的方法
    struct list_head list;//與 cdev 對應(yīng)的字符設(shè)備文件的 inode->i_devices 的鏈表頭
    dev_t dev;//起始設(shè)備編號
    unsigned int count;//設(shè)備范圍號大小
};

一個 cdev 一般它有兩種定義初始化方式:靜態(tài)的和動態(tài)的刘离。

靜態(tài)內(nèi)存定義初始化:

struct cdev my_cdev;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;

動態(tài)內(nèi)存定義初始化:

struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &fops;
my_cdev->owner = THIS_MODULE;

兩種使用方式的功能是一樣的垢揩,只是使用的內(nèi)存區(qū)不一樣因谎,一般視實際的數(shù)據(jù)結(jié)構(gòu)需求而定闹获。

源碼分析:

struct cdev *cdev_alloc(void)
{
   struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
   if (p) {
       INIT_LIST_HEAD(&p->list);
       kobject_init(&p->kobj, &ktype_cdev_dynamic);
   }
   return p;
}
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
   memset(cdev, 0, sizeof *cdev);
   INIT_LIST_HEAD(&cdev->list);
   kobject_init(&cdev->kobj, &ktype_cdev_default);
   cdev->ops = fops;
}

可見期犬,兩個函數(shù)完成都功能基本一致。

三避诽、添加cdev

初始化 cdev 后龟虎,需要把它添加到系統(tǒng)中去。為此可以調(diào)用 cdev_add() 函數(shù)茎用。傳入 cdev 結(jié)構(gòu)的指針遣总,起始設(shè)備編號睬罗,以及設(shè)備編號范圍。

int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
   p->dev = dev;
   p->count = count;
   return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}

簡單地說旭斥,設(shè)備驅(qū)動程序通過調(diào)用cdev_add把它所管理的設(shè)備對象的指針嵌入到一個類型為struct probe的節(jié)點之中容达,然后再把該節(jié)點加入到cdev_map所實現(xiàn)的哈希鏈表中。

對系統(tǒng)而言垂券,當(dāng)設(shè)備驅(qū)動程序成功調(diào)用了cdev_add之后花盐,就意味著一個字符設(shè)備對象已經(jīng)加入到了系統(tǒng),在需要的時候菇爪,系統(tǒng)就可以找到它算芯。對用戶態(tài)的程序而言,cdev_add調(diào)用之后凳宙,就已經(jīng)可以通過文件系統(tǒng)的接口調(diào)用到我們的驅(qū)動程序熙揍。

四、卸載cdev

當(dāng)一個字符設(shè)備驅(qū)動不再需要的時候(比如模塊卸載)氏涩,就可以用 cdev_del() 函數(shù)來釋放 cdev 占用的內(nèi)存届囚。

void cdev_del(struct cdev *p)
{
   cdev_unmap(p->dev, p->count);
   kobject_put(&p->kobj);
}

其中 cdev_unmap() 調(diào)用 kobj_unmap() 來釋放 cdev_map 散列表中的對象。kobject_put() 釋放 cdev 結(jié)構(gòu)本身是尖。


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末意系,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子饺汹,更是在濱河造成了極大的恐慌蛔添,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,807評論 6 518
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件兜辞,死亡現(xiàn)場離奇詭異迎瞧,居然都是意外死亡,警方通過查閱死者的電腦和手機弦疮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,284評論 3 399
  • 文/潘曉璐 我一進店門夹攒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人胁塞,你說我怎么就攤上這事咏尝。” “怎么了啸罢?”我有些...
    開封第一講書人閱讀 169,589評論 0 363
  • 文/不壞的土叔 我叫張陵编检,是天一觀的道長。 經(jīng)常有香客問我扰才,道長允懂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,188評論 1 300
  • 正文 為了忘掉前任衩匣,我火速辦了婚禮蕾总,結(jié)果婚禮上粥航,老公的妹妹穿的比我還像新娘。我一直安慰自己生百,他們只是感情好递雀,可當(dāng)我...
    茶點故事閱讀 69,185評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蚀浆,像睡著了一般缀程。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上市俊,一...
    開封第一講書人閱讀 52,785評論 1 314
  • 那天杨凑,我揣著相機與錄音,去河邊找鬼摆昧。 笑死撩满,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的据忘。 我是一名探鬼主播鹦牛,決...
    沈念sama閱讀 41,220評論 3 423
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼勇吊!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起窍仰,我...
    開封第一講書人閱讀 40,167評論 0 277
  • 序言:老撾萬榮一對情侶失蹤汉规,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后驹吮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體针史,經(jīng)...
    沈念sama閱讀 46,698評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,767評論 3 343
  • 正文 我和宋清朗相戀三年碟狞,在試婚紗的時候發(fā)現(xiàn)自己被綠了啄枕。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,912評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡族沃,死狀恐怖频祝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情脆淹,我是刑警寧澤常空,帶...
    沈念sama閱讀 36,572評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站盖溺,受9級特大地震影響漓糙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜烘嘱,卻給世界環(huán)境...
    茶點故事閱讀 42,254評論 3 336
  • 文/蒙蒙 一昆禽、第九天 我趴在偏房一處隱蔽的房頂上張望蝗蛙。 院中可真熱鬧,春花似錦醉鳖、人聲如沸歼郭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,746評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽病曾。三九已至,卻和暖如春漾根,著一層夾襖步出監(jiān)牢的瞬間泰涂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,859評論 1 274
  • 我被黑心中介騙來泰國打工辐怕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留逼蒙,地道東北人。 一個月前我還...
    沈念sama閱讀 49,359評論 3 379
  • 正文 我出身青樓寄疏,卻偏偏與公主長得像是牢,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子陕截,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,922評論 2 361

推薦閱讀更多精彩內(nèi)容

  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經(jīng)改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數(shù)據(jù)革命閱讀 12,176評論 2 33
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法驳棱,類相關(guān)的語法,內(nèi)部類的語法农曲,繼承相關(guān)的語法社搅,異常的語法,線程的語...
    子非魚_t_閱讀 31,668評論 18 399
  • Ubuntu的發(fā)音 Ubuntu笙以,源于非洲祖魯人和科薩人的語言,發(fā)作 oo-boon-too 的音冻辩。了解發(fā)音是有意...
    螢火蟲de夢閱讀 99,372評論 9 467
  • 作者:徐志摩 我不知道風(fēng) 是在哪一個方向吹—— 我是在夢中猖腕, 在夢的輕波里依洄。 是在哪一個方向吹—— 我是在夢中...
    聰云閱讀 430評論 0 0
  • 真正的高手善于“功成身退”,也善于東山再起重整雄風(fēng)凛剥。 半年前侠仇,女友放棄六年的工作來了一場說走就走的旅行。今天發(fā)來郵...
    祖娟閱讀 1,767評論 20 22