本章主要介紹binder驅(qū)動的三個重要函數(shù),binder_open()、binder_mmap()以及binder_ioctl()小腊。在介紹這三個函數(shù)的過程中驰徊,簡單提binder中的數(shù)據(jù)結(jié)構(gòu)。
在上一篇《ServiceManager啟動過程》中序愚,在servicemanager.c的main方法中,servicemanager進(jìn)程調(diào)用了自己的binder_open()方法,申請了128K大小的內(nèi)存空間读恃,
我們從這里開始說起。
在servicemanager自己實現(xiàn)的binder_open()中代态,它分別調(diào)用了open()寺惫,mmap()以及ioctl()三個系統(tǒng)調(diào)用。這三個方法最終會調(diào)用到binder驅(qū)動層的binder_open()蹦疑,binder_mmap()以及binder_ioctl()方法西雀。
之所以會這樣,是因為在binder驅(qū)動的調(diào)用binder_init()->init_binder_device()初始化過程中歉摧,在binder_device結(jié)構(gòu)體中艇肴,已經(jīng)對這些驅(qū)動層的方法做了映射。
kernel/msm-5.4/drivers/android/binder.c以及binder_internal.h
/**
* struct binder_device - information about a binder device node(binder設(shè)備的節(jié)點信息)
* @hlist: list of binder devices (only used for devices requested via
* CONFIG_ANDROID_BINDER_DEVICES)
* @miscdev: information about a binder character device node
* @context: binder context information
* @binderfs_inode: This is the inode of the root dentry of the super block
* belonging to a binderfs mount.
*/
struct binder_device {
struct hlist_node hlist;
struct miscdevice miscdev;
struct binder_context context;
struct inode *binderfs_inode;
refcount_t ref;
};
//映射的結(jié)構(gòu)體
const struct file_operations binder_fops = {
.owner = THIS_MODULE,
.poll = binder_poll,
.unlocked_ioctl = binder_ioctl,
.compat_ioctl = binder_ioctl,
.mmap = binder_mmap,
.open = binder_open,
.flush = binder_flush,
.release = binder_release,
};
//init_binder_device方法
static int __init init_binder_device(const char *name)
{
int ret;
struct binder_device *binder_device;
//在內(nèi)核空間為binder_device申請內(nèi)存
binder_device = kzalloc(sizeof(*binder_device), GFP_KERNEL);
//在這里做的映射
binder_device->miscdev.fops = &binder_fops;
binder_device->miscdev.minor = MISC_DYNAMIC_MINOR;
binder_device->miscdev.name = name;
refcount_set(&binder_device->ref, 1);
binder_device->context.binder_context_mgr_uid = INVALID_UID;
binder_device->context.name = name;
mutex_init(&binder_device->context.context_mgr_node_lock);
//掛載binder驅(qū)動
ret = misc_register(&binder_device->miscdev);
hlist_add_head(&binder_device->hlist, &binder_devices);
return ret;
}
1: binder_open()
任何一個進(jìn)程想要使用binder驅(qū)動進(jìn)行通信時叁温,都需要執(zhí)行binder_open方法再悼,打開binder驅(qū)動。
為的是膝但,創(chuàng)建初始化binder_proc冲九,并將當(dāng)前進(jìn)程的binder_proc保存在binder驅(qū)動管理的全局鏈表binder_procs中。
在binder驅(qū)動中锰镀,binder_procs鏈表管理著所有進(jìn)程執(zhí)行完binder_open()創(chuàng)建的binder_proc娘侍。
binder_open()
static int binder_open(struct inode *nodp, struct file *filp)
{
//binder_proc結(jié)構(gòu)體,每一個進(jìn)程在binder驅(qū)動中都對應(yīng)著一個此結(jié)構(gòu)體泳炉。binder_proc是進(jìn)程在使用binder通信時在內(nèi)核中的數(shù)據(jù)結(jié)構(gòu)憾筏。
struct binder_proc *proc;
//為binder_proc結(jié)構(gòu)體申請內(nèi)存,并初始化為0花鹅,并返回一個指向分配的內(nèi)存塊起始地址的地址指針氧腰。
proc = kzalloc(sizeof(*proc), GFP_KERNEL);
//獲取一個當(dāng)前線程的task_struct(PCB進(jìn)程控制塊),在linux中,無論是進(jìn)程還是線程都是有PCB進(jìn)程控制塊來表示的古拴。
get_task_struct(current);
//將當(dāng)前線程的task保存到binder進(jìn)程的tsk
proc->tsk = current;
//初始化todo隊列(也叫做工作隊列)箩帚,用于存放待處理的請求(server端),由于服務(wù)進(jìn)程不會是一個一個處理請求的黄痪,在todo隊列中的請求會多線程處理紧帕。
INIT_LIST_HEAD(&proc->todo);
//初始化wait隊列,當(dāng)某個服務(wù)進(jìn)程被請多的次數(shù)過多時桅打,多余的任務(wù)會放在wait隊列中是嗜。
init_waitqueue_head(&proc->wait);
//將當(dāng)前線程線程優(yōu)先級保存在binder進(jìn)程的default_priority中
proc->default_priority = task_nice(current); /*獲取當(dāng)前進(jìn)程的優(yōu)先級*/
//持有鎖
binder_lock(__func__);
//將當(dāng)前的binder_proc對象加入到binder驅(qū)動管理的全局binder_procs鏈表中
hlist_add_head(&proc->proc_node, &binder_procs);
//將當(dāng)前線程所處進(jìn)程的pid記錄在binder進(jìn)程的pid中
proc->pid = current->group_leader->pid;
INIT_LIST_HEAD(&proc->delivered_death);
//將proc記錄到文件對象(傳到用戶空間的desc描述符就是描述此文件對象的)的私有數(shù)據(jù)當(dāng)中,為的是在其它函數(shù)中也可以訪問到此binder_proc對象
filp->private_data = proc;
//釋放鎖
binder_unlock(__func__);
return 0;
}
2: binder_mmap()
以下為binder驅(qū)動一次拷貝原理圖挺尾,binder_mmap主要是做了以下的事情鹅搪。
binder_mmap
static int binder_mmap(struct file *filp, struct vm_area_struct *vma)
{
int ret;
//這里先申請一個內(nèi)核虛擬內(nèi)存的指針地址
struct vm_struct *area;
//從filp中拿出私有數(shù)據(jù),在binder_open的時候就把本進(jìn)程的binder_proc保存進(jìn)去了遭铺。
struct binder_proc *proc = filp->private_data;
const char *failure_string;
//申請一個binder_buffer的地址丽柿,此地址是用來進(jìn)程間傳遞數(shù)據(jù)的內(nèi)核緩沖區(qū)
struct binder_buffer *buffer;
//從這里可以看出,用戶空間一次申請內(nèi)存的大小不能大于4M魂挂,如果大于4M就再加4M甫题。在用戶空間的時候可以看到serviemanager申請了128*1024(128K)的大小。
if ((vma->vm_end - vma->vm_start) > SZ_4M)
vma->vm_end = vma->vm_start + SZ_4M;
mutex_lock(&binder_mmap_lock);
//一個進(jìn)程只能映射一次
if (proc->buffer) {
ret = -EBUSY;
failure_string = "already mapped";
goto err_already_mapped;
}
//使用IOREMAP的方式锰蓬,在內(nèi)核虛擬內(nèi)存申請大小為vma->vm_end - vma->vm_start的空間幔睬。
area = get_vm_area(vma->vm_end - vma->vm_start, VM_IOREMAP);
//把申請到的area的首地址,保存到pro->buffer芹扭。這樣麻顶,*buffer就指向剛剛申請到的內(nèi)核空間的首地址了。
//其中proc->buffer為當(dāng)前進(jìn)程buffer的首地址舱卡,后面還會新增其他buffer
proc->buffer = area->addr;
//這里是在計算用戶空間vm_area_struct vam的首地址與內(nèi)核空間vm_struct area首地址的差值(偏移量)辅肾,并保存至proc->user_buffer_offset
//由于虛擬內(nèi)存是連續(xù)的(32位應(yīng)用為0~4G),通過計算偏移量轮锥,就可以在用戶空間拿到內(nèi)核空間的地址矫钓。
proc->user_buffer_offset = vma->vm_start - (uintptr_t)proc->buffer;
mutex_unlock(&binder_mmap_lock);
// 在物理內(nèi)存上申請對應(yīng)頁數(shù)總大小的物理內(nèi)存。這里這樣算一下是為了取整舍杜,因為待會分配物理內(nèi)存時會按照page進(jìn)行分配新娜。“sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE”
proc->pages = kzalloc(sizeof(proc->pages[0]) * ((vma->vm_end - vma->vm_start) / PAGE_SIZE), GFP_KERNEL);
//設(shè)置內(nèi)核空間的buffer_size既绩。
proc->buffer_size = vma->vm_end - vma->vm_start;
vma->vm_ops = &binder_vm_ops;
vma->vm_private_data = proc;
//分配物理頁面概龄,同時映射到內(nèi)核空間和進(jìn)程空間,先分配1個物理頁
if (binder_update_page_range(proc, 1, proc->buffer, proc->buffer + PAGE_SIZE, vma)) {
ret = -ENOMEM;
failure_string = "alloc small buf";
goto err_alloc_small_buf_failed;
}
//讓binder_buffer 對象指向binder_proc的buffer地址饲握,也就是binder_buffer指向了area的首地址私杜。
buffer = proc->buffer;
// 初始化binder_proc的buffers鏈表頭
INIT_LIST_HEAD(&proc->buffers);
//將當(dāng)前binder_proc擁有的binder_buffer鏈接到binder_proc當(dāng)中蚕键,注意這個時候還只有一個binder_buffer
list_add(&buffer->entry, &proc->buffers);
buffer->free = 1; /*表明當(dāng)前buffer是空閑buffer*/
binder_insert_free_buffer(proc, buffer); /*將當(dāng)前的binder_buffer插入到binder_proc空閑鏈表中*/
proc->free_async_space = proc->buffer_size / 2;
barrier();
proc->files = get_files_struct(current);
proc->vma = vma; /*保留這128K在用戶空間地址空間*/
proc->vma_vm_mm = vma->vm_mm; /*保留這128在kernel的地址空間*/
return 0;
}
然后是虛擬地址與物理地址的映射,包括用戶空間與內(nèi)核空間的兩次映射衰粹。(這里主要是linux內(nèi)核的知識锣光,暫不做深究)
binder_update_page_range()
static int binder_update_page_range(struct binder_proc *proc, int allocate,
void *start, void *end,
struct vm_area_struct *vma)
{
void *page_addr;
unsigned long user_page_addr;
struct page **page;
// mm_struct 是 task_struct 結(jié)構(gòu)體中虛擬地址管理的結(jié)構(gòu)體
struct mm_struct *mm;
if (vma)
// binder_mmap 過程 vma 不為空,其他情況都為空
mm = NULL;
else
// 獲取 mm 結(jié)構(gòu)體铝耻,從 tsk 中獲取
mm = get_task_mm(proc->tsk);
if (mm) {
// 獲取 mm_struct 的寫信號量
down_write(&mm->mmap_sem);
vma = proc->vma;
}
// 此處 allocate 為 1誊爹,代表分配過程。如果為 0 則代表釋放過程
if (allocate == 0)
goto free_range;
for (page_addr = start; page_addr < end; page_addr += PAGE_SIZE) {
int ret;binder_update_page_range()
page = &proc->pages[(page_addr - proc->buffer) / PAGE_SIZE];
// 分配一個 page 的物理內(nèi)存
*page = alloc_page(GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO);
// 物理空間映射到虛擬內(nèi)核空間
ret = map_kernel_range_noflush((unsigned long)page_addr,
PAGE_SIZE, PAGE_KERNEL, page);
// 用戶空間地址 = 內(nèi)核空間地址 + 偏移量
user_page_addr = (uintptr_t)page_addr + proc->user_buffer_offset;
//物理空間映射到虛擬進(jìn)程空間
ret = vm_insert_page(vma, user_page_addr, page[0]);
}
if (mm) {
// 釋放內(nèi)存的寫信號量
up_write(&mm->mmap_sem);
// 減少 mm->mm_users 計數(shù)
mmput(mm);
}
return 0;
//釋放內(nèi)存的流程
free_range:
...
return -ENOMEM;
}
3瓢捉、linux內(nèi)核分配內(nèi)存函數(shù)
此部分為賦值了他們的文檔替废,為的是看內(nèi)核內(nèi)存分配函數(shù)時可以查詢。
1.kmalloc()
文件包含:#include
功能描述:kmalloc()用于分配在物理上連續(xù)的內(nèi)存泊柬,虛擬地址也是連續(xù)的,是基于slab分配實際上存在的內(nèi)存诈火。
但是kmalloc最多只能開辟大小為32XPAGE_SIZE的內(nèi)存,一般的PAGE_SIZE=4kB,也就是128kB的大小的內(nèi)存兽赁。
函數(shù)定義:static __always_inline void * kmalloc (size_t size, gfp_t flags);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
flags:分配標(biāo)志,要分配內(nèi)存的類型
返回參數(shù):返回一個指向分配的內(nèi)存塊起始地址的地址指針
釋放函數(shù):kfree()
2.kcalloc()
文件包含:#include
功能描述:kcalloc()是基于slab分配實際上存在的內(nèi)存冷守,并且在分配后將內(nèi)存中的內(nèi)容都初始化為0刀崖,但kcalloc()主要為一個數(shù)組分配
內(nèi)存空間,數(shù)組中的一個元素對應(yīng)一個內(nèi)存對象
函數(shù)定義:static inline void * kcalloc (size_t n, size_t size, gfp_t flags);
輸入?yún)?shù):n:數(shù)組中的元素個數(shù)
size:分配數(shù)組中每個元素所對應(yīng)的內(nèi)存對象的字節(jié)數(shù)
flags:分配標(biāo)志拍摇,要分配內(nèi)存的類型
返回參數(shù):返回一個對所分配的內(nèi)存對象數(shù)組的引用
釋放函數(shù):kfree()
3.kzalloc()
文件包含:#include
功能描述:kzalloc()是基于slab分配實際上存在的內(nèi)存亮钦,在分配內(nèi)存后將內(nèi)存中的內(nèi)容都初始化為0,和kcalloc()有些相似,
但不針對數(shù)組充活。
函數(shù)定義:static inline void * kzalloc (size_t size, gfp_t flags);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
flags:分配標(biāo)志蜂莉,要分配內(nèi)存的類型
返回參數(shù):返回一個指向分配的內(nèi)存塊起始地址的地址指針
釋放函數(shù):kfree()
4.krealloc()
文件包含:#include
功能描述:重新分配內(nèi)存,但不改變原地址空間中的內(nèi)容
函數(shù)定義:void * __must_check krealloc (const void *p, size_t new_size, gfp_t flags);
輸入?yún)?shù):p:重新分配的原內(nèi)存空間的起始地址
new_size:重新分配內(nèi)存的字節(jié)數(shù)
flags:分配標(biāo)志混卵,要分配內(nèi)存的類型
返回參數(shù):重新分配的內(nèi)存空間的起始地址
釋放函數(shù):遵從原分配內(nèi)存函數(shù)的分配函數(shù)
5.vmalloc()
文件包含:#include
功能描述:用于分配一塊非連續(xù)的地址空間映穗,物理地址一般是非連續(xù)的,但是虛擬地址是連續(xù)的幕随,分配的內(nèi)存空間
被映射進(jìn)入內(nèi)核數(shù)據(jù)段中蚁滋,從用戶空間是不可見的。vmalloc比kmalloc要慢赘淮。
函數(shù)定義:void *vmalloc(unsigned long size);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
返回參數(shù):返回創(chuàng)建的地址區(qū)間的虛擬地址辕录,如果分配失敗返回NULL。
釋放函數(shù):vfree()
6.vmalloc_to_page()
文件包含:#include
功能描述:找到vmalloc()所分配內(nèi)存的虛擬地址所映射的物理頁梢卸,并返回該頁的指針描述符
函數(shù)定義:struct page *vmalloc_to_page(const void *addr);
輸入?yún)?shù):addr:一般是vmalloc()的返回地址走诞,也是一個虛擬地址
返回參數(shù):addr映射的物理頁的指針描述符
7.vmalloc_user()
文件包含:#include
功能描述:功能類似于vmalloc(),在vmalloc()的基礎(chǔ)上將地址空間清零,這樣該地址空間被映射到用戶空間不會發(fā)生數(shù)據(jù)泄露
函數(shù)定義:void *vmalloc_user(unsigned long size);
輸入?yún)?shù):size:分配內(nèi)存的字節(jié)數(shù)
返回參數(shù):返回創(chuàng)建的地址區(qū)間的虛擬地址低剔,如果分配失敗返回NULL速梗。
釋放函數(shù):vfree()