一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

linux cdev詳解-steven

 射天狼星 2014-10-06
分類: LINUX
linux cdev詳解
謹(jǐn)以此文紀(jì)念過往的歲月
一.前言
以前對于cdev僅僅是知其然,而不知其所以然。在本文中,將深入理解cdev的架構(gòu)以及具體的實現(xiàn)。
二.真實的cdev
2.1 設(shè)備號
搞驅(qū)動的都應(yīng)該知道的東西,在寫gpio驅(qū)動時,往往會用到以下兩個函數(shù)。
alloc_chrdev_region --自動分配設(shè)備號
register_chrdev_region --分配以設(shè)定的設(shè)備號。
上面兩個函數(shù)的調(diào)用很簡單,當(dāng)時卻沒有深入去理解其實現(xiàn)的原理,只知道其采用了hash表,但是具體怎么實現(xiàn)的卻不知道。在這里來好好理解一下。上面兩個函數(shù)的核心是__register_chrdev_region;那來看源碼的實現(xiàn)。
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; --CHRDEV_MAJOR_HASH_SIZE = 255
你看這個結(jié)構(gòu)體會發(fā)現(xiàn),其定義了一個指針數(shù)組,大小為255.
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) { --當(dāng)調(diào)用alloc_chrdev_region時,傳入的major為0,從表面上看調(diào)用alloc_chrdev_region會自動分配設(shè)備,但是有一個缺點(diǎn)就是其
分配的設(shè)備號只能在255內(nèi),而且并沒有使用hash表,從而大大減少了linux所支持的設(shè)備號數(shù)。
for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
--以下以register_chrdev_region為例即傳入major不為0。
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strncpy(cd->name,name, 64);
i = major_to_index(major); -- major % CHRDEV_MAJOR_HASH_SIZE 這里采用除留余數(shù)法來產(chǎn)生地址。
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) --這個是用于處理散列表的沖突。在這里采用拉鏈法來處理散列沖突。
if ((*cp)->major > major ||((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) { --這里判斷從設(shè)備號是否出現(xiàn)重合。
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp; --這里是將cd插入鏈表中。
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
上面是分配設(shè)備號的核心函數(shù),上面的描述比較空洞,那來看一個例子。
在兩個驅(qū)動文件中都采用register_chrdev_region來分配設(shè)備號。
A文件.
dev_t devt = MKDEV(506,0);
register_chrdev_region(devt,1,'A');
B文件.
dev_t devt = MKDEV(506,1);
register_chrdev_region(devt,1,'B');
也許有人會奇怪這兩個module怎么會用同一個主設(shè)備號,有人會認(rèn)為是在同一個主設(shè)備號下有兩個從設(shè)備,所以會采用同一個主設(shè)備號,其實不然,在命令行中輸入cat /proc/devices 時會發(fā)現(xiàn)竟然有兩個主設(shè)備號為506的設(shè)備,并且這設(shè)備號竟然大于255。到此時,你在回去去看cdev 設(shè)備號的hash表實現(xiàn)就不太難懂了。其實對于hash表的地址為506%255 = 251,而B的cd 等于 A的cd->next.就是hash出現(xiàn)地址沖突時采用拉鏈法來解決沖突的。
OK,到此對于cdev的設(shè)備號是如何分配的應(yīng)該很清楚了吧。
2.2 cdev的初始化和注冊。
一般使用cdev的過程如下
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
cdev_alloc->cdev_init->cdev_add
那就按照上面的順序來分別解釋。
cdev_alloc和cdev_init都比較簡單,這里就不說了。在cdev_add中有一個很重要的函數(shù)kobj_map。
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);
}
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
其傳入的參數(shù)cdev_map在剛開始的時候就分配內(nèi)存并且初始化了。
int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,struct module *module, kobj_probe_t *probe,int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; --當(dāng)rang小于1<<20時,n=1
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
for (i = 0; i < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
上面還是一個鏈表型的數(shù)組,用于保存module的信息。至于這些信息的作用哪兒有用,不急,且聽我慢慢道來。
三.cdev打開
話說每一個設(shè)備在打開的時候都會使用open,而每一個cdev open的時候都會調(diào)用chrdev_open這個函數(shù),而上面所說的cdev_add中的一些某明奇妙的東東都會在這里看到。
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev; --以此時p=NULL為例。
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, --查找kobject。這里面就知道kobj_mmap為什么那么做了。
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj); --根據(jù)kobj找到cdev
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
inode->i_cindex = idx;
list_add(&inode->i_devices, list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
filp->f_op = fops_get(p->ops); --將file的f_op用cdev自己的ops代替。
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) { --這里將會調(diào)用設(shè)備自己的open。
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
{
struct kobject *kobj;
struct probe *p;
unsigned long best = ~0UL;
retry:
mutex_lock(domain->lock);
for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { --從鏈表頭開始查詢
struct kobject *(*probe)(dev_t, int *, void *);
struct module *owner;
void *data;
if (p->dev > dev || p->dev + p->range - 1 < dev) --還記否上面是根據(jù)什么來將probe節(jié)點(diǎn)插入到以probes[x]為頭的鏈表中的。
continue;
if (p->range - 1 >= best) --我感覺這個錯誤時不會發(fā)生的。
break;
if (!try_module_get(p->owner)) --判斷module是否在內(nèi)核中,在就增加計數(shù)。否則返回0表示module不在kernel中。
continue;
owner = p->owner; --這里就是cdev->owner
data = p->data; --data = cdev
probe = p->get; --在cdev_add中就說明是exact_match
best = p->range - 1; --就是cdev_add中的參數(shù)count。
*index = dev - p->dev;
if (p->lock lock(dev, data) < 0) { --p->lock = exact_lock
module_put(owner);
continue;
}
mutex_unlock(domain->lock);
kobj = probe(dev, index, data); --返回時cdev的kobject
static struct kobject *exact_match(dev_t dev, int *part, void *data)
{
struct cdev *p = data;
return kobj;
}
/* Currently ->owner protects _only_ ->probe() itself. */
module_put(owner);
if (kobj)
return kobj;
goto retry;
}
mutex_unlock(domain->lock);
return NULL;
}
四.總結(jié)
到此cdev的核心算是理解了,其理解有錯之處請各位多多指教。
閱讀(4542) | 評論(0) | 轉(zhuǎn)發(fā)(3) |

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多

    久久婷婷综合色拍亚洲| 精品久久少妇激情视频| 国产欧美日韩一级小黄片| 69精品一区二区蜜桃视频| 国产一区二区三区不卡| 2019年国产最新视频| 久久精品国产99精品最新| 在线免费视频你懂的观看| 久久99这里只精品热在线| 国产精品香蕉一级免费| 国产精品免费不卡视频| 亚洲最大的中文字幕在线视频| 九九热九九热九九热九九热| 一区中文字幕人妻少妇| 国产精品十八禁亚洲黄污免费观看| 日韩精品一级一区二区| 我要看日本黄色小视频| 国产成人亚洲精品青草天美| 亚洲中文在线男人的天堂| 91亚洲国产成人久久精品麻豆| 日韩三级黄色大片免费观看| 午夜国产精品福利在线观看 | 亚洲最大福利在线观看| 欧美有码黄片免费在线视频| 国产亚洲精品香蕉视频播放| 人妻露脸一区二区三区| 日韩精品少妇人妻一区二区| 福利视频一区二区在线| 欧美日韩综合综合久久久| 亚洲最大的中文字幕在线视频| 国产水滴盗摄一区二区| 亚洲中文字幕高清乱码毛片| 99国产高清不卡视频| 欧美日韩国产午夜福利| 中文字幕亚洲精品在线播放| 欧美熟妇一区二区在线| 国产成人午夜在线视频| 国产女高清在线看免费观看| 精品人妻少妇二区三区| 老外那个很粗大做起来很爽| 日韩精品一级一区二区|