日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析

發(fā)布時間:2024/8/24 Android 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

文章轉(zhuǎn)載至CSDN社區(qū)羅升陽的安卓之旅,原文地址:http://blog.csdn.net/luoshengyang/article/details/6664554

在上一文章Android系統(tǒng)匿名共享內(nèi)存Ashmem(Anonymous Shared Memory)簡要介紹和學(xué)習(xí)計劃中, 我們簡要介紹了Android系統(tǒng)的匿名共享內(nèi)存機制,其中,簡要提到了它具有輔助內(nèi)存管理系統(tǒng)來有效地管理內(nèi)存的特點,但是沒有進一步去了解它是如何實 現(xiàn)的。在本文中,我們將通過分析Android系統(tǒng)的匿名共享內(nèi)存Ashmem驅(qū)動程序的源代碼,來深入了解它是如何輔助內(nèi)存管理系

?? ? ? ?Android系統(tǒng)的匿名共享內(nèi)存Ashmem機制并沒有自立山頭,從頭搞一套自己的共享內(nèi)存機制,而是建立在Linux內(nèi)核實現(xiàn)的共享內(nèi)存的基礎(chǔ)上 的。與此同時,它又向Linux內(nèi)存管理系統(tǒng)的內(nèi)存回收算法注冊接口,告訴Linux內(nèi)存管理系統(tǒng)它的某些內(nèi)存塊不再使用了,可以被回收了,不過,這些不 再使用的內(nèi)存需要由它的使用者來告訴Ashmem驅(qū)動程序。通過這種用戶-Ashmem驅(qū)動程序-內(nèi)存管理系統(tǒng)三者的緊密合作,實現(xiàn)有效的內(nèi)存管理機制, 適合移動設(shè)備小內(nèi)存的特點。

?? ? ? ?Android系統(tǒng)的匿名共享內(nèi)存Ashmem驅(qū)動程序利用了Linux的共享內(nèi)存子系統(tǒng)導(dǎo)出的接口來實現(xiàn)自己的功能,因此,它的實現(xiàn)非常小巧,總共代 碼不到700行。雖然代碼很少,但是這里不打算機械式地一行一行地閱讀和分析Ashmem驅(qū)動程序的源代碼,而是通過使用情景來分析,這樣可以幫助我們清 晰地理解它的實現(xiàn)原理。我們這里所說的使用情景,將從Android系統(tǒng)的應(yīng)用程序框架層提供的匿名共享內(nèi)存接口開始,經(jīng)過系統(tǒng)運行時庫層,最終到達驅(qū)動 程序?qū)?#xff0c;通過這樣一個完整的過程來理解Android系統(tǒng)的匿名共享內(nèi)存Ashmem機制。這里,我們將從上一篇文章Android系統(tǒng)匿名共享內(nèi)存Ashmem(Anonymous Shared Memory)簡要介紹和學(xué)習(xí)計劃介紹的Android應(yīng)用程序框架層提供MemoryFile接口開始,分別介紹Android系統(tǒng)匿名共享內(nèi)存的創(chuàng)建(open)、映射(mmap)、讀寫(read/write)以及鎖定和解鎖(pin/unpin)四個使用情景。

?? ? ? ?在進入到這個四個使用情景前,我們先來看一下Ashmem驅(qū)動程序模塊的初始化函數(shù),看看它給用戶空間暴露了什么接口,即它創(chuàng)建了什么樣的設(shè)備文件,以 及提供了什么函數(shù)來操作這個設(shè)備文件。Ashmem驅(qū)動程序?qū)崿F(xiàn)在kernel/common/mm/ashmem.c文件中,它的模塊初始化函數(shù)定義為 ashmem_init:

[cpp] view plaincopy
  • static?struct?file_operations?ashmem_fops?=?{??
  • ????.owner?=?THIS_MODULE,??
  • ????.open?=?ashmem_open,??
  • ????.release?=?ashmem_release,??
  • ????.mmap?=?ashmem_mmap,??
  • ????.unlocked_ioctl?=?ashmem_ioctl,??
  • ????.compat_ioctl?=?ashmem_ioctl,??
  • };??
  • ??
  • static?struct?miscdevice?ashmem_misc?=?{??
  • ????.minor?=?MISC_DYNAMIC_MINOR,??
  • ????.name?=?"ashmem",??
  • ????.fops?=?&ashmem_fops,??
  • };??
  • ??
  • static?int?__init?ashmem_init(void)??
  • {??
  • ????int?ret;??
  • ??
  • ????......??
  • ??
  • ????ret?=?misc_register(&ashmem_misc);??
  • ????if?(unlikely(ret))?{??
  • ????????printk(KERN_ERR?"ashmem:?failed?to?register?misc?device!\n");??
  • ????????return?ret;??
  • ????}??
  • ??
  • ????......??
  • ??
  • ????return?0;??
  • }??
  • ?? ? ? 這里,我們可以看到,Ahshmem驅(qū)動程序在加載時,會創(chuàng)建一個/dev/ashmem的設(shè)備文件,這是一個misc類型的設(shè)備。注冊misc設(shè)備是通過misc_register函數(shù)進行的,關(guān)于這個函數(shù)的詳細實現(xiàn),可以參考前面Android日志系統(tǒng)驅(qū)動程序Logger源代碼分析一 文,調(diào)用這個函數(shù)成功后,就會在/dev目錄下生成一個ashmem設(shè)備文件了。同時,我們還可以看到,這個設(shè)備文件提供了open、mmap、 release和ioctl四種操作。為什么沒有read和write操作呢?這是因為讀寫共享內(nèi)存的方法是通過內(nèi)存映射地址來進行的,即通過mmap系 統(tǒng)調(diào)用把這個設(shè)備文件映射到進程地址空間中,然后就直接對內(nèi)存進行讀寫了,不需要通過read 和write文件操作,后面我們將會具體分析是如何實現(xiàn)的。
    ?? ? ? 有了這個基礎(chǔ)之后,下面我們就分四個部分來分別介紹匿名共享內(nèi)存的創(chuàng)建(open)、映射(mmap)、讀寫(read/write)以及鎖定和解鎖(pin/unpin)使用情景。

    ?? ? ? ?一. 匿名共享內(nèi)存的創(chuàng)建操作

    ?? ? ? ?在Android應(yīng)用程序框架層提供MemoryFile類的構(gòu)造函數(shù)中,進行了匿名共享內(nèi)存的創(chuàng)建操作,我們先來看一下這個構(gòu)造函數(shù)的實現(xiàn),它位于 frameworks/base/core/java/android/os/MemoryFile.java文件中:

    [java] view plaincopy
  • public?class?MemoryFile??
  • {??
  • ????......??
  • ??
  • ????private?static?native?FileDescriptor?native_open(String?name,?int?length)?throws?IOException;??
  • ??????
  • ????......??
  • ??
  • ????private?FileDescriptor?mFD;????????//?ashmem?file?descriptor??
  • ????......??
  • ????private?int?mLength;????//?total?length?of?our?ashmem?region??
  • ??????
  • ????......??
  • ??
  • ????/**?
  • ????*?Allocates?a?new?ashmem?region.?The?region?is?initially?not?purgable.?
  • ????*?
  • ????*?@param?name?optional?name?for?the?file?(can?be?null).?
  • ????*?@param?length?of?the?memory?file?in?bytes.?
  • ????*?@throws?IOException?if?the?memory?file?could?not?be?created.?
  • ????*/??
  • ????public?MemoryFile(String?name,?int?length)?throws?IOException?{??
  • ????????mLength?=?length;??
  • ????????mFD?=?native_open(name,?length);??
  • ????????......??
  • ????}??
  • ??
  • ????......??
  • }??
  • ?? ? ? ?這里我們看到,這個構(gòu)造函數(shù)最終是通過JNI方法native_open來創(chuàng)建匿名內(nèi)存共享文件。這個JNI方法native_open實現(xiàn)在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:

    [cpp] view plaincopy
  • static?jobject?android_os_MemoryFile_open(JNIEnv*?env,?jobject?clazz,?jstring?name,?jint?length)??
  • {??
  • ????const?char*?namestr?=?(name???env->GetStringUTFChars(name,?NULL)?:?NULL);??
  • ??
  • ????int?result?=?ashmem_create_region(namestr,?length);??
  • ??
  • ????if?(name)??
  • ????????env->ReleaseStringUTFChars(name,?namestr);??
  • ??
  • ????if?(result?<?0)?{??
  • ????????jniThrowException(env,?"java/io/IOException",?"ashmem_create_region?failed");??
  • ????????return?NULL;??
  • ????}??
  • ??
  • ????return?jniCreateFileDescriptor(env,?result);??
  • }??
  • ?? ? ? ?這個函數(shù)又通過運行時庫提供的接口ashmem_create_region來創(chuàng)建匿名共享內(nèi)存,這個函數(shù)實現(xiàn)在system/core/libcutils/ashmem-dev.c文件中:

    [cpp] view plaincopy
  • /*?
  • ?*?ashmem_create_region?-?creates?a?new?ashmem?region?and?returns?the?file?
  • ?*?descriptor,?or?<0?on?error?
  • ?*?
  • ?*?`name'?is?an?optional?label?to?give?the?region?(visible?in?/proc/pid/maps)?
  • ?*?`size'?is?the?size?of?the?region,?in?page-aligned?bytes?
  • ?*/??
  • int?ashmem_create_region(const?char?*name,?size_t?size)??
  • {??
  • ????int?fd,?ret;??
  • ??
  • ????fd?=?open(ASHMEM_DEVICE,?O_RDWR);??
  • ????if?(fd?<?0)??
  • ????????return?fd;??
  • ??
  • ????if?(name)?{??
  • ????????char?buf[ASHMEM_NAME_LEN];??
  • ??
  • ????????strlcpy(buf,?name,?sizeof(buf));??
  • ????????ret?=?ioctl(fd,?ASHMEM_SET_NAME,?buf);??
  • ????????if?(ret?<?0)??
  • ????????????goto?error;??
  • ????}??
  • ??
  • ????ret?=?ioctl(fd,?ASHMEM_SET_SIZE,?size);??
  • ????if?(ret?<?0)??
  • ????????goto?error;??
  • ??
  • ????return?fd;??
  • ??
  • error:??
  • ????close(fd);??
  • ????return?ret;??
  • }??
  • ?? ? ? ?這里,一共通過執(zhí)行三個文件操作系統(tǒng)調(diào)用來和Ashmem驅(qū)動程序進行交互,分雖是一個open和兩個ioctl操作,前者是打開設(shè)備文件ASHMEM_DEVICE,后者分別是設(shè)置匿名共享內(nèi)存的名稱和大小。

    ?? ? ? ?在介紹這三個文件操作之前,我們先來了解一下Ashmem驅(qū)動程序的一個相關(guān)數(shù)據(jù)結(jié)構(gòu)struct ashmem_area,這個數(shù)據(jù)結(jié)構(gòu)就是用來表示一塊共享內(nèi)存的,它定義在kernel/common/mm/ashmem.c文件中:

    [cpp] view plaincopy
  • /*?
  • ?*?ashmem_area?-?anonymous?shared?memory?area?
  • ?*?Lifecycle:?From?our?parent?file's?open()?until?its?release()?
  • ?*?Locking:?Protected?by?`ashmem_mutex'?
  • ?*?Big?Note:?Mappings?do?NOT?pin?this?structure;?it?dies?on?close()?
  • ?*/??
  • struct?ashmem_area?{??
  • ????char?name[ASHMEM_FULL_NAME_LEN];/*?optional?name?for?/proc/pid/maps?*/??
  • ????struct?list_head?unpinned_list;?/*?list?of?all?ashmem?areas?*/??
  • ????struct?file?*file;??????/*?the?shmem-based?backing?file?*/??
  • ????size_t?size;????????????/*?size?of?the?mapping,?in?bytes?*/??
  • ????unsigned?long?prot_mask;????/*?allowed?prot?bits,?as?vm_flags?*/??
  • };??
  • ?? ? ? ?域name表示這塊共享內(nèi)存的名字,這個名字會顯示/proc/<pid>/maps文件中,<pid>表示打開這個共享內(nèi)存 文件的進程ID;域unpinned_list是一個列表頭,它把這塊共享內(nèi)存中所有被解鎖的內(nèi)存塊連接在一起,下面我們講內(nèi)存塊的鎖定和解鎖操作時會看 到它的用法;域file表示這個共享內(nèi)存在臨時文件系統(tǒng)tmpfs中對應(yīng)的文件,在內(nèi)核決定要把這塊共享內(nèi)存對應(yīng)的物理頁面回收時,就會把它的內(nèi)容交換到 這個臨時文件中去;域size表示這塊共享內(nèi)存的大小;域prot_mask表示這塊共享內(nèi)存的訪問保護位。

    ?? ? ? ?在Ashmem驅(qū)動程中,所有的ashmem_area實例都是從自定義的一個slab緩沖區(qū)創(chuàng)建的。這個slab緩沖區(qū)是在驅(qū)動程序模塊初始化函數(shù)創(chuàng)建的,我們來看一個這個初始化函數(shù)的相關(guān)實現(xiàn):

    [cpp] view plaincopy
  • static?int?__init?ashmem_init(void)??
  • {??
  • ????int?ret;??
  • ??
  • ????ashmem_area_cachep?=?kmem_cache_create("ashmem_area_cache",??
  • ????????sizeof(struct?ashmem_area),??
  • ????????0,?0,?NULL);??
  • ????if?(unlikely(!ashmem_area_cachep))?{??
  • ????????printk(KERN_ERR?"ashmem:?failed?to?create?slab?cache\n");??
  • ????????return?-ENOMEM;??
  • ????}??
  • ??
  • ????......??
  • ??
  • ????return?0;??
  • }??
  • ?? ? ? ? 全局變量定義在文件開頭的地方:

    [cpp] view plaincopy
  • static?struct?kmem_cache?*ashmem_area_cachep?__read_mostly;??
  • ?? ? ? ?它的類型是struct kmem_cache,表示這是一個slab緩沖區(qū),由內(nèi)核中的內(nèi)存管理系統(tǒng)進行管理。

    ?? ? ? ?這里就是通過kmem_cache_create函數(shù)來創(chuàng)建一個名為"ashmem_area_cache"、對象大小為sizeof(struct ashmem_area)的緩沖區(qū)了。緩沖區(qū)創(chuàng)建了以后,就可以每次從它分配一個struct ashmem_area對象了。關(guān)于Linux內(nèi)核的slab緩沖區(qū)的相關(guān)知識,可以參考前面Android學(xué)習(xí)啟動篇一文中提到的一本參考書籍《Understanding the Linux Kernel》的第八章Memory Managerment。

    ?? ? ? ?有了這些基礎(chǔ)知識后,我們回到前面的ashmem_create_region函數(shù)中。

    ?? ? ? ?首先是執(zhí)行打開文件的操作:

    [cpp] view plaincopy
  • fd?=?open(ASHMEM_DEVICE,?O_RDWR);??
  • ?? ? ? ?ASHMEM_DEVICE是一個宏,定義為:

    [cpp] view plaincopy
  • #define?ASHMEM_DEVICE???"/dev/ashmem"??
  • ?? ? ? ? 這里就是匿名共享內(nèi)存設(shè)備文件/dev/ashmem了。

    ?? ? ? ?從上面的描述我們可以知道,調(diào)用這個open函數(shù)最終會進入到Ashmem驅(qū)動程序中的ashmem_open函數(shù)中去:

    [cpp] view plaincopy
  • static?int?ashmem_open(struct?inode?*inode,?struct?file?*file)??
  • {??
  • ????struct?ashmem_area?*asma;??
  • ????int?ret;??
  • ??
  • ????ret?=?nonseekable_open(inode,?file);??
  • ????if?(unlikely(ret))??
  • ????????return?ret;??
  • ??
  • ????asma?=?kmem_cache_zalloc(ashmem_area_cachep,?GFP_KERNEL);??
  • ????if?(unlikely(!asma))??
  • ????????return?-ENOMEM;??
  • ??
  • ????INIT_LIST_HEAD(&asma->unpinned_list);??
  • ????memcpy(asma->name,?ASHMEM_NAME_PREFIX,?ASHMEM_NAME_PREFIX_LEN);??
  • ????asma->prot_mask?=?PROT_MASK;??
  • ????file->private_data?=?asma;??
  • ??
  • ????return?0;??
  • }??
  • ?? ? ? ?首先是通過nonseekable_open函數(shù)來設(shè)備這個文件不可以執(zhí)行定位操作,即不可以執(zhí)行seek文件操作。接著就是通過 kmem_cache_zalloc函數(shù)從剛才我們創(chuàng)建的slab緩沖區(qū)ashmem_area_cachep來創(chuàng)建一個ashmem_area結(jié)構(gòu)體 了,并且保存在本地變量asma中。再接下去就是初始化變量asma的其它域,其中,域name初始為ASHMEM_NAME_PREFIX,這是一個 宏,定義為:

    [cpp] view plaincopy
  • #define?ASHMEM_NAME_PREFIX?"dev/ashmem/"??
  • #define?ASHMEM_NAME_PREFIX_LEN?(sizeof(ASHMEM_NAME_PREFIX)?-?1)??
  • ?? ? ? ?函數(shù)的最后是把這個ashmem_area結(jié)構(gòu)保存在打開文件結(jié)構(gòu)體的private_data域中,這樣,Ashmem驅(qū)動程序就可以在其它地方通過這個private_data域來取回這個ashmem_area結(jié)構(gòu)了。

    ?? ? ? ?到這里,設(shè)備文件/dev/ashmem的打開操作就完成了,它實際上就是在Ashmem驅(qū)動程序中創(chuàng)建了一個ashmem_area結(jié)構(gòu),表示一塊新的共享內(nèi)存。

    ?? ? ? ?再回到ashmem_create_region函數(shù)中,又調(diào)用了兩次ioctl文件操作分別來設(shè)備這塊新建的匿名共享內(nèi)存的名字和大小。在 kernel/comon/mm/include/ashmem.h文件中,ASHMEM_SET_NAME和ASHMEM_SET_SIZE的定義為:

    [cpp] view plaincopy
  • #define?ASHMEM_NAME_LEN?????256??
  • ??
  • #define?__ASHMEMIOC?????0x77??
  • ??
  • #define?ASHMEM_SET_NAME?????_IOW(__ASHMEMIOC,?1,?char[ASHMEM_NAME_LEN])??
  • #define?ASHMEM_SET_SIZE?????_IOW(__ASHMEMIOC,?3,?size_t)??
  • ?? ? ? 先來看ASHMEM_SET_NAME命令的ioctl調(diào)用,它最終進入到Ashmem驅(qū)動程序的ashmem_ioctl函數(shù)中:

    [cpp] view plaincopy
  • static?long?ashmem_ioctl(struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg)??
  • {??
  • ????struct?ashmem_area?*asma?=?file->private_data;??
  • ????long?ret?=?-ENOTTY;??
  • ??
  • ????switch?(cmd)?{??
  • ????case?ASHMEM_SET_NAME:??
  • ????????ret?=?set_name(asma,?(void?__user?*)?arg);??
  • ????????break;??
  • ????......??
  • ????}??
  • ??
  • ????return?ret;??
  • }??
  • ?? ? ? 這里通過set_name函數(shù)來進行實際操作:

    [cpp] view plaincopy
  • static?int?set_name(struct?ashmem_area?*asma,?void?__user?*name)??
  • {??
  • ????int?ret?=?0;??
  • ??
  • ????mutex_lock(&ashmem_mutex);??
  • ??
  • ????/*?cannot?change?an?existing?mapping's?name?*/??
  • ????if?(unlikely(asma->file))?{??
  • ????????ret?=?-EINVAL;??
  • ????????goto?out;??
  • ????}??
  • ??
  • ????if?(unlikely(copy_from_user(asma->name?+?ASHMEM_NAME_PREFIX_LEN,??
  • ????????????????????name,?ASHMEM_NAME_LEN)))??
  • ????????ret?=?-EFAULT;??
  • ????asma->name[ASHMEM_FULL_NAME_LEN-1]?=?'\0';??
  • ??
  • out:??
  • ????mutex_unlock(&ashmem_mutex);??
  • ??
  • ????return?ret;??
  • }??
  • ?? ? ? ?這個函數(shù)實現(xiàn)很簡單,把用戶空間傳進來的匿名共享內(nèi)存的名字設(shè)備到asma->name域中去。注意,匿名共享內(nèi)存塊的名字的內(nèi)容分兩部分,前一 部分是前綴,這是在open操作時,由驅(qū)動程序默認設(shè)置的,固定為ASHMEM_NAME_PREFIX,即"dev/ashmem/";后一部分由用戶 指定,這一部分是可選的,即用戶可以不調(diào)用ASHMEM_SET_NAME命令來設(shè)置匿名共享內(nèi)存塊的名字。

    ?? ? ? ?再來看ASHMEM_SET_SIZE命令的ioctl調(diào)用,它最終也是進入到Ashmem驅(qū)動程序的ashmem_ioctl函數(shù)中:

    [cpp] view plaincopy
  • static?long?ashmem_ioctl(struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg)??
  • {??
  • ????struct?ashmem_area?*asma?=?file->private_data;??
  • ????long?ret?=?-ENOTTY;??
  • ??
  • ????switch?(cmd)?{??
  • ????......??
  • ????case?ASHMEM_SET_SIZE:??
  • ????????ret?=?-EINVAL;??
  • ????????if?(!asma->file)?{??
  • ????????????ret?=?0;??
  • ????????????asma->size?=?(size_t)?arg;??
  • ????????}??
  • ????????break;??
  • ????......??
  • ????}??
  • ??
  • ????return?ret;??
  • }??
  • ?? ? ? ?這個實現(xiàn)很簡單,只是把用戶空間傳進來的匿名共享內(nèi)存的大小值保存在對應(yīng)的asma->size域中。

    ?? ? ? ?這樣,ashmem_create_region函數(shù)就執(zhí)先完成了,層層返回,最后回到應(yīng)用程序框架層提供的接口Memory的構(gòu)造函數(shù)中,整個匿名共 享內(nèi)存的創(chuàng)建過程就完成了。前面我們說過過,Ashmem驅(qū)動程序不提供read和write文件操作,進程若要訪問這個共享內(nèi)存,必須要把這個設(shè)備文件 映射到自己的進程空間中,然后進行直接內(nèi)存訪問,這就是我們下面要介紹的匿名共享內(nèi)存設(shè)備文件的內(nèi)存映射操作了。

    ?? ? ? ?二.?匿名共享內(nèi)存設(shè)備文件的內(nèi)存映射操作

    ?? ? ? ?在MemoryFile類的構(gòu)造函數(shù)中,進行了匿名共享內(nèi)存的創(chuàng)建操作后,下一步就是要把匿名共享內(nèi)存設(shè)備文件映射到進程空間來了:

    [java] view plaincopy
  • public?class?MemoryFile??
  • {??
  • ????......??
  • ??
  • ????//?returns?memory?address?for?ashmem?region??
  • ????private?static?native?int?native_mmap(FileDescriptor?fd,?int?length,?int?mode)??
  • ????????throws?IOException;??
  • ??????
  • ????......??
  • ??
  • ????private?int?mAddress;???//?address?of?ashmem?memory??
  • ??????
  • ????......??
  • ??
  • ????/**?
  • ????*?Allocates?a?new?ashmem?region.?The?region?is?initially?not?purgable.?
  • ????*?
  • ????*?@param?name?optional?name?for?the?file?(can?be?null).?
  • ????*?@param?length?of?the?memory?file?in?bytes.?
  • ????*?@throws?IOException?if?the?memory?file?could?not?be?created.?
  • ????*/??
  • ????public?MemoryFile(String?name,?int?length)?throws?IOException?{??
  • ????????......??
  • ????????mAddress?=?native_mmap(mFD,?length,?PROT_READ?|?PROT_WRITE);??
  • ????????......??
  • ????}??
  • }??
  • ?? ? ? ? 映射匿名共享內(nèi)存設(shè)備文件到進程空間是通過JNI方法native_mmap來進行的。這個JNI方法實現(xiàn)在frameworks/base/core/jni/adroid_os_MemoryFile.cpp文件中:

    [cpp] view plaincopy
  • static?jint?android_os_MemoryFile_mmap(JNIEnv*?env,?jobject?clazz,?jobject?fileDescriptor,??
  • ????????jint?length,?jint?prot)??
  • {??
  • ????int?fd?=?jniGetFDFromFileDescriptor(env,?fileDescriptor);??
  • ????jint?result?=?(jint)mmap(NULL,?length,?prot,?MAP_SHARED,?fd,?0);??
  • ????if?(!result)??
  • ????????jniThrowException(env,?"java/io/IOException",?"mmap?failed");??
  • ????return?result;??
  • }??
  • ?? ? ? ?這里的文件描述符fd是在前面open匿名設(shè)備文件/dev/ashmem獲得的,有個這個文件描述符后,就可以直接通過mmap來執(zhí)行內(nèi)存映射操作了。這個mmap系統(tǒng)調(diào)用最終進入到Ashmem驅(qū)動程序的ashmem_mmap函數(shù)中:

    [cpp] view plaincopy
  • static?int?ashmem_mmap(struct?file?*file,?struct?vm_area_struct?*vma)??
  • {??
  • ????struct?ashmem_area?*asma?=?file->private_data;??
  • ????int?ret?=?0;??
  • ??
  • ????mutex_lock(&ashmem_mutex);??
  • ??
  • ????/*?user?needs?to?SET_SIZE?before?mapping?*/??
  • ????if?(unlikely(!asma->size))?{??
  • ????????ret?=?-EINVAL;??
  • ????????goto?out;??
  • ????}??
  • ??
  • ????/*?requested?protection?bits?must?match?our?allowed?protection?mask?*/??
  • ????if?(unlikely((vma->vm_flags?&?~asma->prot_mask)?&?PROT_MASK))?{??
  • ????????ret?=?-EPERM;??
  • ????????goto?out;??
  • ????}??
  • ??
  • ????if?(!asma->file)?{??
  • ????????char?*name?=?ASHMEM_NAME_DEF;??
  • ????????struct?file?*vmfile;??
  • ??
  • ????????if?(asma->name[ASHMEM_NAME_PREFIX_LEN]?!=?'\0')??
  • ????????????name?=?asma->name;??
  • ??
  • ????????/*?...?and?allocate?the?backing?shmem?file?*/??
  • ????????vmfile?=?shmem_file_setup(name,?asma->size,?vma->vm_flags);??
  • ????????if?(unlikely(IS_ERR(vmfile)))?{??
  • ????????????ret?=?PTR_ERR(vmfile);??
  • ????????????goto?out;??
  • ????????}??
  • ????????asma->file?=?vmfile;??
  • ????}??
  • ????get_file(asma->file);??
  • ??
  • ????if?(vma->vm_flags?&?VM_SHARED)??
  • ????????shmem_set_file(vma,?asma->file);??
  • ????else?{??
  • ????????if?(vma->vm_file)??
  • ????????????fput(vma->vm_file);??
  • ????????vma->vm_file?=?asma->file;??
  • ????}??
  • ????vma->vm_flags?|=?VM_CAN_NONLINEAR;??
  • ??
  • out:??
  • ????mutex_unlock(&ashmem_mutex);??
  • ????return?ret;??
  • }??
  • ?? ? ? ?這個函數(shù)的實現(xiàn)也很簡單,它調(diào)用了Linux內(nèi)核提供的shmem_file_setup函數(shù)來在臨時文件系統(tǒng)tmpfs中創(chuàng)建一個臨時文件,這個臨時 文件與Ashmem驅(qū)動程序創(chuàng)建的匿名共享內(nèi)存對應(yīng)。函數(shù)shmem_file_setup是Linux內(nèi)核中用來創(chuàng)建共享內(nèi)存文件的方法,而Linux 內(nèi)核中的共享內(nèi)存機制其實是一種進程間通信(IPC)機制,它的實現(xiàn)相對也是比較復(fù)雜,Android系統(tǒng)的匿名共享內(nèi)存機制正是由于直接使用了 Linux內(nèi)核共享內(nèi)存機制,它才會很小巧,它站在巨人的肩膀上了。關(guān)于Linux內(nèi)核中的共享內(nèi)存的相關(guān)知識,可以參考前面Android學(xué)習(xí)啟動篇一文中提到的一本參考書籍《Linux內(nèi)核源代碼情景分析》的第六章傳統(tǒng)的Unix進程間通信第七小節(jié)共享內(nèi)存。

    ?? ? ? ?通過shmem_file_setup函數(shù)創(chuàng)建的臨時文件vmfile最終就保存在vma->file中了。這里的vma是由Linux內(nèi)核的文 件系統(tǒng)層傳進來的,它的類型為struct vm_area_struct,它表示的是當前進程空間中一塊連續(xù)的虛擬地址空間,它的起始地址可以由用戶來指定,也可以由內(nèi)核自己來分配,這里我們從 JNI方法native_mmap調(diào)用的mmap的第一個參數(shù)為NULL可以看出,這塊連續(xù)的虛擬地址空間的起始地址是由內(nèi)核來指定的。文件內(nèi)存映射操作 完成后,用戶訪問這個范圍的地址空間就相當于是訪問對應(yīng)的文件的內(nèi)容了。有關(guān)Linux文件的內(nèi)存映射操作,同樣可以參考前面Android學(xué)習(xí)啟動篇一文中提到的一本參考書籍《Linux內(nèi)核源代碼情景分析》的第二章內(nèi)存管理第十三小節(jié)系統(tǒng)調(diào)用mmap。從這里我們也可以看出,Android系統(tǒng)的匿名共享內(nèi)存是在虛擬地址空間連續(xù)的,但是在物理地址空間就不一定是連續(xù)的了。

    ?? ? ? ?同時,這個臨時文件vmfile也會保存asma->file域中,這樣,Ashmem驅(qū)動程序后面就可以通過在asma->file來操作這個匿名內(nèi)存共享文件了。

    ?? ? ? ?函數(shù)ashmem_mmap執(zhí)行完成后,經(jīng)過層層返回到JNI方法native_mmap中去,就從mmap函數(shù)的返回值中得到了這塊虛擬空間的起始地 址了,這個起始地址最終返回到應(yīng)用程序框架層的MemoryFile類的構(gòu)造函數(shù)中,并且保存在成員變量mAddress中,后面,共享內(nèi)存的讀寫操作就 是對這個地址空間進行操作了。

    ?? ? ? ?三.?匿名共享內(nèi)存的讀寫操作

    ?? ? ? ?因為前面對匿名共享內(nèi)存文件進行內(nèi)存映射操作,這里對匿名內(nèi)存文件內(nèi)容的讀寫操作就比較簡單了,就像訪問內(nèi)存變量一樣就行了。

    ?? ? ? ?我們來看一下MemoryFile類的讀寫操作函數(shù):

    [cpp] view plaincopy
  • public?class?MemoryFile??
  • {??
  • ????......??
  • ??
  • ????private?static?native?int?native_read(FileDescriptor?fd,?int?address,?byte[]?buffer,??
  • ????????int?srcOffset,?int?destOffset,?int?count,?boolean?isUnpinned)?throws?IOException;??
  • ????private?static?native?void?native_write(FileDescriptor?fd,?int?address,?byte[]?buffer,??
  • ????????int?srcOffset,?int?destOffset,?int?count,?boolean?isUnpinned)?throws?IOException;??
  • ??????
  • ????......??
  • ??
  • ????private?FileDescriptor?mFD;????????//?ashmem?file?descriptor??
  • ????private?int?mAddress;???//?address?of?ashmem?memory??
  • ????private?int?mLength;????//?total?length?of?our?ashmem?region??
  • ????private?boolean?mAllowPurging?=?false;??//?true?if?our?ashmem?region?is?unpinned??
  • ??
  • ????......??
  • ??
  • ????/**?
  • ????*?Reads?bytes?from?the?memory?file.?
  • ????*?Will?throw?an?IOException?if?the?file?has?been?purged.?
  • ????*?
  • ????*?@param?buffer?byte?array?to?read?bytes?into.?
  • ????*?@param?srcOffset?offset?into?the?memory?file?to?read?from.?
  • ????*?@param?destOffset?offset?into?the?byte?array?buffer?to?read?into.?
  • ????*?@param?count?number?of?bytes?to?read.?
  • ????*?@return?number?of?bytes?read.?
  • ????*?@throws?IOException?if?the?memory?file?has?been?purged?or?deactivated.?
  • ????*/??
  • ????public?int?readBytes(byte[]?buffer,?int?srcOffset,?int?destOffset,?int?count)???
  • ????throws?IOException?{??
  • ????????if?(isDeactivated())?{??
  • ????????????throw?new?IOException("Can't?read?from?deactivated?memory?file.");??
  • ????????}??
  • ????????if?(destOffset?<?0?||?destOffset?>?buffer.length?||?count?<?0??
  • ????????????||?count?>?buffer.length?-?destOffset??
  • ????????????||?srcOffset?<?0?||?srcOffset?>?mLength??
  • ????????????||?count?>?mLength?-?srcOffset)?{??
  • ????????????????throw?new?IndexOutOfBoundsException();??
  • ????????}??
  • ????????return?native_read(mFD,?mAddress,?buffer,?srcOffset,?destOffset,?count,?mAllowPurging);??
  • ????}??
  • ??
  • ????/**?
  • ????*?Write?bytes?to?the?memory?file.?
  • ????*?Will?throw?an?IOException?if?the?file?has?been?purged.?
  • ????*?
  • ????*?@param?buffer?byte?array?to?write?bytes?from.?
  • ????*?@param?srcOffset?offset?into?the?byte?array?buffer?to?write?from.?
  • ????*?@param?destOffset?offset??into?the?memory?file?to?write?to.?
  • ????*?@param?count?number?of?bytes?to?write.?
  • ????*?@throws?IOException?if?the?memory?file?has?been?purged?or?deactivated.?
  • ????*/??
  • ????public?void?writeBytes(byte[]?buffer,?int?srcOffset,?int?destOffset,?int?count)??
  • ????????throws?IOException?{??
  • ????????????if?(isDeactivated())?{??
  • ????????????????throw?new?IOException("Can't?write?to?deactivated?memory?file.");??
  • ????????????}??
  • ????????????if?(srcOffset?<?0?||?srcOffset?>?buffer.length?||?count?<?0??
  • ????????????????||?count?>?buffer.length?-?srcOffset??
  • ????????????????||?destOffset?<?0?||?destOffset?>?mLength??
  • ????????????????||?count?>?mLength?-?destOffset)?{??
  • ????????????????????throw?new?IndexOutOfBoundsException();??
  • ????????????}??
  • ????????????native_write(mFD,?mAddress,?buffer,?srcOffset,?destOffset,?count,?mAllowPurging);??
  • ????}??
  • ??
  • ????......??
  • }??
  • ?? ? ? ?這里,我們可以看到,MemoryFile的匿名共享內(nèi)存讀寫操作都是通過JNI方法來實現(xiàn)的,讀操作和寫操作的JNI方法分別是 native_read和native_write,它們都是定義在frameworks/base/core/jni /adroid_os_MemoryFile.cpp文件中:

    [cpp] view plaincopy
  • static?jint?android_os_MemoryFile_read(JNIEnv*?env,?jobject?clazz,??
  • ????????jobject?fileDescriptor,?jint?address,?jbyteArray?buffer,?jint?srcOffset,?jint?destOffset,??
  • ????????jint?count,?jboolean?unpinned)??
  • {??
  • ????int?fd?=?jniGetFDFromFileDescriptor(env,?fileDescriptor);??
  • ????if?(unpinned?&&?ashmem_pin_region(fd,?0,?0)?==?ASHMEM_WAS_PURGED)?{??
  • ????????ashmem_unpin_region(fd,?0,?0);??
  • ????????jniThrowException(env,?"java/io/IOException",?"ashmem?region?was?purged");??
  • ????????return?-1;??
  • ????}??
  • ??
  • ????env->SetByteArrayRegion(buffer,?destOffset,?count,?(const?jbyte?*)address?+?srcOffset);??
  • ??
  • ????if?(unpinned)?{??
  • ????????ashmem_unpin_region(fd,?0,?0);??
  • ????}??
  • ????return?count;??
  • }??
  • ??
  • static?jint?android_os_MemoryFile_write(JNIEnv*?env,?jobject?clazz,??
  • ????????jobject?fileDescriptor,?jint?address,?jbyteArray?buffer,?jint?srcOffset,?jint?destOffset,??
  • ????????jint?count,?jboolean?unpinned)??
  • {??
  • ????int?fd?=?jniGetFDFromFileDescriptor(env,?fileDescriptor);??
  • ????if?(unpinned?&&?ashmem_pin_region(fd,?0,?0)?==?ASHMEM_WAS_PURGED)?{??
  • ????????ashmem_unpin_region(fd,?0,?0);??
  • ????????jniThrowException(env,?"java/io/IOException",?"ashmem?region?was?purged");??
  • ????????return?-1;??
  • ????}??
  • ??
  • ????env->GetByteArrayRegion(buffer,?srcOffset,?count,?(jbyte?*)address?+?destOffset);??
  • ??
  • ????if?(unpinned)?{??
  • ????????ashmem_unpin_region(fd,?0,?0);??
  • ????}??
  • ????return?count;??
  • }??
  • ?? ? ? ?這里的address參數(shù)就是我們在前面執(zhí)行mmap來映射匿名共享內(nèi)存文件到內(nèi)存中時,得到的進程虛擬地址空間的起始地址了,因此,這里就直接可以訪 問,不必進入到Ashmem驅(qū)動程序中去,這也是為什么Ashmem驅(qū)動程序沒有提供read和write文件操作的原因。

    ?? ? ? ?這里我們看到的ashmem_pin_region和ashmem_unpin_region兩個函數(shù)是系統(tǒng)運行時庫提供的接口,用來執(zhí)行我們前面說的 匿名共享內(nèi)存的鎖定和解鎖操作,它們的作用是告訴Ashmem驅(qū)動程序,它的哪些內(nèi)存塊是正在使用的,需要鎖定,哪些內(nèi)存是不需要使用了,可以它解鎖,這 樣,Ashmem驅(qū)動程序就可以輔助內(nèi)存管理系統(tǒng)來有效地管理內(nèi)存了。下面我們就看看Ashmem驅(qū)動程序是如果輔助內(nèi)存管理系統(tǒng)來有效地管理內(nèi)存的。

    ?? ? ? ?四.?匿名共享內(nèi)存的鎖定和解鎖操作
    ?? ? ? ?前面提到,Android系統(tǒng)的運行時庫提到了執(zhí)行匿名共享內(nèi)存的鎖定和解鎖操作的兩個函數(shù)ashmem_pin_region和 ashmem_unpin_region,它們實現(xiàn)在system/core/libcutils/ashmem-dev.c文件中:

    [cpp] view plaincopy
  • int?ashmem_pin_region(int?fd,?size_t?offset,?size_t?len)??
  • {??
  • ????struct?ashmem_pin?pin?=?{?offset,?len?};??
  • ????return?ioctl(fd,?ASHMEM_PIN,?&pin);??
  • }??
  • ??
  • int?ashmem_unpin_region(int?fd,?size_t?offset,?size_t?len)??
  • {??
  • ????struct?ashmem_pin?pin?=?{?offset,?len?};??
  • ????return?ioctl(fd,?ASHMEM_UNPIN,?&pin);??
  • }??
  • ?? ? ? 它們的實現(xiàn)很簡單,通過ASHMEM_PIN和ASHMEM_UNPIN兩個ioctl操作來實現(xiàn)匿名共享內(nèi)存的鎖定和解鎖操作。

    ?? ? ? 我們先看來一下ASHMEM_PIN和ASHMEM_UNPIN這兩個命令號的定義,它們的定義可以在kernel/common/include/linux/ashmem.h文件中找到:

    [cpp] view plaincopy
  • #define?__ASHMEMIOC?????0x77??
  • ??
  • #define?ASHMEM_PIN??????_IOW(__ASHMEMIOC,?7,?struct?ashmem_pin)??
  • #define?ASHMEM_UNPIN????????_IOW(__ASHMEMIOC,?8,?struct?ashmem_pin)??
  • ?? ? ? 它們的參數(shù)類型為struct ashmem_pin,它也是定義在kernel/common/include/linux/ashmem.h文件中:

    [cpp] view plaincopy
  • struct?ashmem_pin?{??
  • ????__u32?offset;???/*?offset?into?region,?in?bytes,?page-aligned?*/??
  • ????__u32?len;??/*?length?forward?from?offset,?in?bytes,?page-aligned?*/??
  • };??
  • ?? ? ? 這個結(jié)構(gòu)體只有兩個域,分別表示要鎖定或者要解鎖的內(nèi)塊塊的起始大小以及大小。

    ?? ? ? 在分析這兩個操作之前,我們先來看一下Ashmem驅(qū)動程序中的一個數(shù)據(jù)結(jié)構(gòu)struct ashmem_range,這個數(shù)據(jù)結(jié)構(gòu)就是用來表示某一塊被解鎖(unpinnd)的內(nèi)存:

    [cpp] view plaincopy
  • /*?
  • ?*?ashmem_range?-?represents?an?interval?of?unpinned?(evictable)?pages?
  • ?*?Lifecycle:?From?unpin?to?pin?
  • ?*?Locking:?Protected?by?`ashmem_mutex'?
  • ?*/??
  • struct?ashmem_range?{??
  • ????struct?list_head?lru;???????/*?entry?in?LRU?list?*/??
  • ????struct?list_head?unpinned;??/*?entry?in?its?area's?unpinned?list?*/??
  • ????struct?ashmem_area?*asma;???/*?associated?area?*/??
  • ????size_t?pgstart;?????????/*?starting?page,?inclusive?*/??
  • ????size_t?pgend;???????????/*?ending?page,?inclusive?*/??
  • ????unsigned?int?purged;????????/*?ASHMEM_NOT?or?ASHMEM_WAS_PURGED?*/??
  • };??
  • ?? ? ? ?域asma表示這塊被解鎖的內(nèi)存所屬于的匿名共享內(nèi)存,它通過域unpinned連接在asma->unpinned_list表示的列表中;域 pgstart和paend表示這個內(nèi)存塊的開始和結(jié)束頁面號,它們表示一個前后閉合的區(qū)間;域purged表示這個內(nèi)存塊占用的物理內(nèi)存是否已經(jīng)被回 收;這塊被解鎖的內(nèi)存塊除了保存在它所屬的匿名共享內(nèi)存asma的解鎖列表unpinned_list之外,還通過域lru保存在一個全局的最近最少使用 列表ashmem_lru_list列表中,它的定義如下:

    [cpp] view plaincopy
  • /*?LRU?list?of?unpinned?pages,?protected?by?ashmem_mutex?*/??
  • static?LIST_HEAD(ashmem_lru_list);??
  • ?? ? ? ?了解了這個數(shù)據(jù)結(jié)構(gòu)之后,我們就可以來看ashmem_ioctl函數(shù)中關(guān)于ASHMEM_PIN和ASHMEM_UNPIN的操作了:

    [cpp] view plaincopy
  • static?long?ashmem_ioctl(struct?file?*file,?unsigned?int?cmd,?unsigned?long?arg)??
  • {??
  • ????struct?ashmem_area?*asma?=?file->private_data;??
  • ????long?ret?=?-ENOTTY;??
  • ??
  • ????switch?(cmd)?{??
  • ????......??
  • ????case?ASHMEM_PIN:??
  • ????case?ASHMEM_UNPIN:??
  • ????????ret?=?ashmem_pin_unpin(asma,?cmd,?(void?__user?*)?arg);??
  • ????????break;??
  • ????......??
  • ????}??
  • ??
  • ????return?ret;??
  • }??
  • ?? ? ? ?它們都是通過ashmem_pin_unpin來進一步處理:

    [cpp] view plaincopy
  • static?int?ashmem_pin_unpin(struct?ashmem_area?*asma,?unsigned?long?cmd,??
  • ????????????????void?__user?*p)??
  • {??
  • ????struct?ashmem_pin?pin;??
  • ????size_t?pgstart,?pgend;??
  • ????int?ret?=?-EINVAL;??
  • ??
  • ????if?(unlikely(!asma->file))??
  • ????????return?-EINVAL;??
  • ??
  • ????if?(unlikely(copy_from_user(&pin,?p,?sizeof(pin))))??
  • ????????return?-EFAULT;??
  • ??
  • ????/*?per?custom,?you?can?pass?zero?for?len?to?mean?"everything?onward"?*/??
  • ????if?(!pin.len)??
  • ????????pin.len?=?PAGE_ALIGN(asma->size)?-?pin.offset;??
  • ??
  • ????if?(unlikely((pin.offset?|?pin.len)?&?~PAGE_MASK))??
  • ????????return?-EINVAL;??
  • ??
  • ????if?(unlikely(((__u32)?-1)?-?pin.offset?<?pin.len))??
  • ????????return?-EINVAL;??
  • ??
  • ????if?(unlikely(PAGE_ALIGN(asma->size)?<?pin.offset?+?pin.len))??
  • ????????return?-EINVAL;??
  • ??
  • ????pgstart?=?pin.offset?/?PAGE_SIZE;??
  • ????pgend?=?pgstart?+?(pin.len?/?PAGE_SIZE)?-?1;??
  • ??
  • ????mutex_lock(&ashmem_mutex);??
  • ??
  • ????switch?(cmd)?{??
  • ????case?ASHMEM_PIN:??
  • ????????ret?=?ashmem_pin(asma,?pgstart,?pgend);??
  • ????????break;??
  • ????case?ASHMEM_UNPIN:??
  • ????????ret?=?ashmem_unpin(asma,?pgstart,?pgend);??
  • ????????break;??
  • ????......??
  • ????}??
  • ??
  • ????mutex_unlock(&ashmem_mutex);??
  • ??
  • ????return?ret;??
  • }??
  • ?? ? ? ?首先是獲得用戶空間傳進來的參數(shù),并保存在本地變量pin中,這是一個struct ashmem_pin類型的變量,這個結(jié)構(gòu)體我們在前面已經(jīng)見過了,它包括了要pin/unpin的內(nèi)存塊的起始地址和大小,這里的起始地址和大小都是以 字節(jié)為單位的,因此,通過轉(zhuǎn)換把它們換成以頁面為單位的,并且保存在本地變量pgstart和pgend中。這里除了要對參數(shù)作一個安全性檢查外,還要一 個處理邏輯是,如果從用戶空間傳進來的內(nèi)塊塊的大小值為0 ,則認為是要pin/unpin整個匿名共享內(nèi)存。

    ?? ? ? ?函數(shù)最后根據(jù)當前要執(zhí)行的是ASHMEM_PIN操作還是ASHMEM_UNPIN操作來分別執(zhí)行ashmem_pin和ashmem_unpin來進 一步處理。創(chuàng)建匿名共享內(nèi)存時,默認所有的內(nèi)存都是pinned狀態(tài)的,只有用戶告訴Ashmem驅(qū)動程序要unpin某一塊內(nèi)存時,Ashmem驅(qū)動程 序才會把這塊內(nèi)存unpin,之后,用戶可以再告訴Ashmem驅(qū)動程序要重新pin某一塊之前被unpin過的內(nèi)塊,從而把這塊內(nèi)存從unpinned 狀態(tài)改為pinned狀態(tài),也就是說,執(zhí)行ASHMEM_PIN操作時,目標對象必須是一塊當前處于unpinned狀態(tài)的內(nèi)存塊。

    ?? ? ? 我們先來看一下ASHMEM_UNPIN操作,進入到ashmem_unpin函數(shù):

    [cpp] view plaincopy
  • /*?
  • ?*?ashmem_unpin?-?unpin?the?given?range?of?pages.?Returns?zero?on?success.?
  • ?*?
  • ?*?Caller?must?hold?ashmem_mutex.?
  • ?*/??
  • static?int?ashmem_unpin(struct?ashmem_area?*asma,?size_t?pgstart,?size_t?pgend)??
  • {??
  • ????struct?ashmem_range?*range,?*next;??
  • ????unsigned?int?purged?=?ASHMEM_NOT_PURGED;??
  • ??
  • restart:??
  • ????list_for_each_entry_safe(range,?next,?&asma->unpinned_list,?unpinned)?{??
  • ????????/*?short?circuit:?this?is?our?insertion?point?*/??
  • ????????if?(range_before_page(range,?pgstart))??
  • ????????????break;??
  • ??
  • ????????/*?
  • ?????????*?The?user?can?ask?us?to?unpin?pages?that?are?already?entirely?
  • ?????????*?or?partially?pinned.?We?handle?those?two?cases?here.?
  • ?????????*/??
  • ????????if?(page_range_subsumed_by_range(range,?pgstart,?pgend))??
  • ????????????return?0;??
  • ????????if?(page_range_in_range(range,?pgstart,?pgend))?{??
  • ????????????pgstart?=?min_t(size_t,?range->pgstart,?pgstart),??
  • ????????????pgend?=?max_t(size_t,?range->pgend,?pgend);??
  • ????????????purged?|=?range->purged;??
  • ????????????range_del(range);??
  • ????????????goto?restart;??
  • ????????}??
  • ????}??
  • ??
  • ????return?range_alloc(asma,?range,?purged,?pgstart,?pgend);??
  • }??
  • ?? ? ? ?這個函數(shù)的主體就是在遍歷asma->unpinned_list列表,從中查找當前處于unpinned狀態(tài)的內(nèi)存塊是否與將要unpin的內(nèi) 存塊[pgstart, pgend]是否相交,如果相交,則要執(zhí)行合并操作,即調(diào)整pgstart和pgend的大小,然后通過調(diào)用range_del函數(shù)刪掉原來的已經(jīng)被 unpinned過的內(nèi)存塊,最后再通過range_alloc函數(shù)來重新unpinned這塊調(diào)整過后的內(nèi)存塊[pgstart, pgend],這里新的內(nèi)存塊[pgstart, pgend]已經(jīng)包含了剛才所有被刪掉的unpinned狀態(tài)的內(nèi)存。注意,這里如果找到一塊相并的內(nèi)存塊,并且調(diào)整了pgstart和pgend的大小 之后,要重新再掃描一遍asma->unpinned_list列表,因為新的內(nèi)存塊[pgstart, pgend]可能還會與前后的處于unpinned狀態(tài)的內(nèi)存塊發(fā)生相交。

    ?? ? ? ?我們來看一下range_before_page的操作,這是一個宏定義:

    [cpp] view plaincopy
  • #define?range_before_page(range,?page)?\??
  • ??((range)->pgend?<?(page))??
  • ?? ? ? ?表示range描述的內(nèi)存塊是否在page頁面之前,如果是,則整個描述就結(jié)束了。從這里我們可以看出asma->unpinned_list列表是按照頁面號從大到小進行排列的,并且每一塊被unpin的內(nèi)存都是不相交的。

    ?? ? ? ?再來看一下page_range_subsumed_by_range的操作,這也是一個宏定義:

    [cpp] view plaincopy
  • #define?page_range_subsumed_by_range(range,?start,?end)?\??
  • ??(((range)->pgstart?<=?(start))?&&?((range)->pgend?>=?(end)))??
  • ?? ? ? 表示range描述的內(nèi)存塊是不是包含了[start, end]這個內(nèi)存塊,如果包含了,則說明當前要unpin的內(nèi)存塊已經(jīng)處于unpinned狀態(tài),什么也不用操作,直接返回即可。

    ?? ? ? 再看page_range_in_range的操作,它也是一個宏定義:

    [cpp] view plaincopy
  • #define?page_range_in_range(range,?start,?end)?\??
  • ??(page_in_range(range,?start)?||?page_in_range(range,?end)?||?\??
  • ???page_range_subsumes_range(range,?start,?end))??
  • ?? ? ?它用到的其它兩個宏分別定義為:

    [cpp] view plaincopy
  • #define?page_range_subsumed_by_range(range,?start,?end)?\??
  • ??(((range)->pgstart?<=?(start))?&&?((range)->pgend?>=?(end)))??
  • ??
  • #define?page_in_range(range,?page)?\??
  • ?(((range)->pgstart?<=?(page))?&&?((range)->pgend?>=?(page)))??
  • ?? ? ?它們都是用來判斷兩個內(nèi)存區(qū)間是否相交的。

    ?? ? ?兩個內(nèi)存塊相交分為四種情況:

    ?? ? ?|-------range-----|? ? ? ? |-------range------|?? ? ? |--------range---------|? ? ? ? ? ? ? ? ?|----range---|

    ?? ? ? |-start----end-| ? ? ? |-start-----end-| ?? ? ? ? ? ? ? ? ? ? ? |-start-------end-| ? ? ? ?|-start-----------end-|
    ?? ? ? ? ? ? ? ? (1) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(2) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(3) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?(4)
    ?? ? ?第一種情況,前面已經(jīng)討論過了,對于第二到第四種情況,都是需要執(zhí)行合并操作的。

    ?? ? ?再來看從asma->unpinned_list中刪掉內(nèi)存塊的range_del函數(shù):

    [cpp] view plaincopy
  • static?void?range_del(struct?ashmem_range?*range)??
  • {??
  • ????list_del(&range->unpinned);??
  • ????if?(range_on_lru(range))??
  • ????????lru_del(range);??
  • ????kmem_cache_free(ashmem_range_cachep,?range);??
  • }??
  • ?? ? ?這個函數(shù)首先把range從相應(yīng)的unpinned_list列表中刪除,然后判斷它是否在lru列表中:

    [cpp] view plaincopy
  • #define?range_on_lru(range)?\??
  • ??((range)->purged?==?ASHMEM_NOT_PURGED)??
  • ?? ? ?如果它的狀態(tài)purged等于ASHMEM_NOT_PURGED,即對應(yīng)的物理頁面尚未被回收,它就位于lru列表中,通過調(diào)用lru_del函數(shù)進行刪除:

    [cpp] view plaincopy
  • static?inline?void?lru_del(struct?ashmem_range?*range)??
  • {??
  • ????list_del(&range->lru);??
  • ????lru_count?-=?range_size(range);??
  • }??
  • ?? ? ? 最后調(diào)用kmem_cache_free將它從slab緩沖區(qū)ashmem_range_cachep中釋放。

    ?? ? ? 這里的slab緩沖區(qū)ashmem_range_cachep定義如下:

    [cpp] view plaincopy
  • static?struct?kmem_cache?*ashmem_range_cachep?__read_mostly;??
  • ?? ? ? 它和前面介紹的slab緩沖區(qū)ashmem_area_cachep一樣,是在Ashmem驅(qū)動程序模塊初始化函數(shù)ashmem_init進行初始化的:

    [cpp] view plaincopy
  • static?int?__init?ashmem_init(void)??
  • {??
  • ????int?ret;??
  • ??
  • ????......??
  • ??
  • ????ashmem_range_cachep?=?kmem_cache_create("ashmem_range_cache",??
  • ????????sizeof(struct?ashmem_range),??
  • ????????0,?0,?NULL);??
  • ????if?(unlikely(!ashmem_range_cachep))?{??
  • ????????printk(KERN_ERR?"ashmem:?failed?to?create?slab?cache\n");??
  • ????????return?-ENOMEM;??
  • ????}??
  • ??
  • ????......??
  • ??
  • ????printk(KERN_INFO?"ashmem:?initialized\n");??
  • ??
  • ????return?0;??
  • }??
  • ?? ? ? 回到ashmem_unpin函數(shù)中,我們再來看看range_alloc函數(shù)的實現(xiàn):

    [cpp] view plaincopy
  • /*?
  • ?*?range_alloc?-?allocate?and?initialize?a?new?ashmem_range?structure?
  • ?*?
  • ?*?'asma'?-?associated?ashmem_area?
  • ?*?'prev_range'?-?the?previous?ashmem_range?in?the?sorted?asma->unpinned?list?
  • ?*?'purged'?-?initial?purge?value?(ASMEM_NOT_PURGED?or?ASHMEM_WAS_PURGED)?
  • ?*?'start'?-?starting?page,?inclusive?
  • ?*?'end'?-?ending?page,?inclusive?
  • ?*?
  • ?*?Caller?must?hold?ashmem_mutex.?
  • ?*/??
  • static?int?range_alloc(struct?ashmem_area?*asma,??
  • ???????????????struct?ashmem_range?*prev_range,?unsigned?int?purged,??
  • ???????????????size_t?start,?size_t?end)??
  • {??
  • ????struct?ashmem_range?*range;??
  • ??
  • ????range?=?kmem_cache_zalloc(ashmem_range_cachep,?GFP_KERNEL);??
  • ????if?(unlikely(!range))??
  • ????????return?-ENOMEM;??
  • ??
  • ????range->asma?=?asma;??
  • ????range->pgstart?=?start;??
  • ????range->pgend?=?end;??
  • ????range->purged?=?purged;??
  • ??
  • ????list_add_tail(&range->unpinned,?&prev_range->unpinned);??
  • ??
  • ????if?(range_on_lru(range))??
  • ????????lru_add(range);??
  • ??
  • ????return?0;??
  • }??
  • ?? ? ? 這個函數(shù)的作用是從slab 緩沖區(qū)中ashmem_range_cachep分配一個ashmem_range,然后對它作相應(yīng)的初始化,放在相應(yīng)的 ashmem_area->unpinned_list列表中,并且還要判斷這個range的purged是否是 ASHMEM_NOT_PURGED狀態(tài),如果是,還要把它放在lru列表中:

    [cpp] view plaincopy
  • static?inline?void?lru_add(struct?ashmem_range?*range)??
  • {??
  • ????list_add_tail(&range->lru,?&ashmem_lru_list);??
  • ????lru_count?+=?range_size(range);??
  • }??
  • ?? ? ? 這樣,ashmem_unpin的源代碼我們就分析完了。

    ?? ? ? 接著,我們再來看一下ASHMEM_PIN操作,進入到ashmem_pin函數(shù):

    [cpp] view plaincopy
  • /*?
  • ?*?ashmem_pin?-?pin?the?given?ashmem?region,?returning?whether?it?was?
  • ?*?previously?purged?(ASHMEM_WAS_PURGED)?or?not?(ASHMEM_NOT_PURGED).?
  • ?*?
  • ?*?Caller?must?hold?ashmem_mutex.?
  • ?*/??
  • static?int?ashmem_pin(struct?ashmem_area?*asma,?size_t?pgstart,?size_t?pgend)??
  • {??
  • ????struct?ashmem_range?*range,?*next;??
  • ????int?ret?=?ASHMEM_NOT_PURGED;??
  • ??
  • ????list_for_each_entry_safe(range,?next,?&asma->unpinned_list,?unpinned)?{??
  • ????????/*?moved?past?last?applicable?page;?we?can?short?circuit?*/??
  • ????????if?(range_before_page(range,?pgstart))??
  • ????????????break;??
  • ??
  • ????????/*?
  • ?????????*?The?user?can?ask?us?to?pin?pages?that?span?multiple?ranges,?
  • ?????????*?or?to?pin?pages?that?aren't?even?unpinned,?so?this?is?messy.?
  • ?????????*?
  • ?????????*?Four?cases:?
  • ?????????*?1.?The?requested?range?subsumes?an?existing?range,?so?we?
  • ?????????*????just?remove?the?entire?matching?range.?
  • ?????????*?2.?The?requested?range?overlaps?the?start?of?an?existing?
  • ?????????*????range,?so?we?just?update?that?range.?
  • ?????????*?3.?The?requested?range?overlaps?the?end?of?an?existing?
  • ?????????*????range,?so?we?just?update?that?range.?
  • ?????????*?4.?The?requested?range?punches?a?hole?in?an?existing?range,?
  • ?????????*????so?we?have?to?update?one?side?of?the?range?and?then?
  • ?????????*????create?a?new?range?for?the?other?side.?
  • ?????????*/??
  • ????????if?(page_range_in_range(range,?pgstart,?pgend))?{??
  • ????????????ret?|=?range->purged;??
  • ??
  • ????????????/*?Case?#1:?Easy.?Just?nuke?the?whole?thing.?*/??
  • ????????????if?(page_range_subsumes_range(range,?pgstart,?pgend))?{??
  • ????????????????range_del(range);??
  • ????????????????continue;??
  • ????????????}??
  • ??
  • ????????????/*?Case?#2:?We?overlap?from?the?start,?so?adjust?it?*/??
  • ????????????if?(range->pgstart?>=?pgstart)?{??
  • ????????????????range_shrink(range,?pgend?+?1,?range->pgend);??
  • ????????????????continue;??
  • ????????????}??
  • ??
  • ????????????/*?Case?#3:?We?overlap?from?the?rear,?so?adjust?it?*/??
  • ????????????if?(range->pgend?<=?pgend)?{??
  • ????????????????range_shrink(range,?range->pgstart,?pgstart-1);??
  • ????????????????continue;??
  • ????????????}??
  • ??
  • ????????????/*?
  • ?????????????*?Case?#4:?We?eat?a?chunk?out?of?the?middle.?A?bit?
  • ?????????????*?more?complicated,?we?allocate?a?new?range?for?the?
  • ?????????????*?second?half?and?adjust?the?first?chunk's?endpoint.?
  • ?????????????*/??
  • ????????????range_alloc(asma,?range,?range->purged,??
  • ????????????????????pgend?+?1,?range->pgend);??
  • ????????????range_shrink(range,?range->pgstart,?pgstart?-?1);??
  • ????????????break;??
  • ????????}??
  • ????}??
  • ??
  • ????return?ret;??
  • }??
  • ?? ? ? ?前面我們說過,被pin的內(nèi)存塊,必須是在unpinned_list列表中的,如果不在,就什么都不用做。要判斷要pin的內(nèi)存塊是否在 unpinned_list列表中,又要通過遍歷相應(yīng)的asma->unpinned_list列表來找出與之相交的內(nèi)存塊了。這個函數(shù)的處理方法 大體與前面的ashmem_unpin函數(shù)是一致的,也是要考慮四種不同的相交情況,這里就不詳述了,讀者可以自己分析一下。

    ?? ? ? ?這里我們只看一下range_shrink函數(shù)的實現(xiàn):

    [cpp] view plaincopy
  • /*?
  • ?*?range_shrink?-?shrinks?a?range?
  • ?*?
  • ?*?Caller?must?hold?ashmem_mutex.?
  • ?*/??
  • static?inline?void?range_shrink(struct?ashmem_range?*range,??
  • ????????????????size_t?start,?size_t?end)??
  • {??
  • ????size_t?pre?=?range_size(range);??
  • ??
  • ????range->pgstart?=?start;??
  • ????range->pgend?=?end;??
  • ??
  • ????if?(range_on_lru(range))??
  • ????????lru_count?-=?pre?-?range_size(range);??
  • }??
  • ?? ? ? ?這個函數(shù)的實現(xiàn)很簡單,只是調(diào)整一下range描述的內(nèi)存塊的起始頁面號,如果它是位于lru列表中,還要調(diào)整一下在lru列表中的總頁面數(shù)大小。

    ?? ? ? ?這樣,匿名共享內(nèi)存的ASHMEM_PIN和ASHMEM_UNPIN操作就介紹完了,但是,我們還看不出來Ashmem驅(qū)動程序是怎么樣輔助內(nèi)存管理 系統(tǒng)來有效管理內(nèi)存的。有了前面這些unpinned的內(nèi)存塊列表之后,下面我們就看一下Ashmem驅(qū)動程序是怎么樣輔助內(nèi)存管理系統(tǒng)來有效管理內(nèi)存 的。

    ?? ? ? ?首先看一下Ashmem驅(qū)動程序模塊初始化函數(shù)ashmem_init:

    [cpp] view plaincopy
  • static?struct?shrinker?ashmem_shrinker?=?{??
  • ????.shrink?=?ashmem_shrink,??
  • ????.seeks?=?DEFAULT_SEEKS?*?4,??
  • };??
  • ??
  • static?int?__init?ashmem_init(void)??
  • {??
  • ????int?ret;??
  • ??
  • ????......??
  • ??
  • ????register_shrinker(&ashmem_shrinker);??
  • ??
  • ????printk(KERN_INFO?"ashmem:?initialized\n");??
  • ??
  • ????return?0;??
  • }??
  • ?? ? ? ?這里通過調(diào)用register_shrinker函數(shù)向內(nèi)存管理系統(tǒng)注冊一個內(nèi)存回收算法函數(shù)。在Linux內(nèi)核中,當系統(tǒng)內(nèi)存緊張時,內(nèi)存管理系統(tǒng)就 會進行內(nèi)存回收算法,將一些最近沒有用過的內(nèi)存換出物理內(nèi)存去,這樣可以增加物理內(nèi)存的供應(yīng)。因此,當內(nèi)存管理系統(tǒng)進行內(nèi)存回收時,就會調(diào)用到這里的 ashmem_shrink函數(shù),讓Ashmem驅(qū)動程序執(zhí)行內(nèi)存回收操作:

    [cpp] view plaincopy
  • /*?
  • ?*?ashmem_shrink?-?our?cache?shrinker,?called?from?mm/vmscan.c?::?shrink_slab?
  • ?*?
  • ?*?'nr_to_scan'?is?the?number?of?objects?(pages)?to?prune,?or?0?to?query?how?
  • ?*?many?objects?(pages)?we?have?in?total.?
  • ?*?
  • ?*?'gfp_mask'?is?the?mask?of?the?allocation?that?got?us?into?this?mess.?
  • ?*?
  • ?*?Return?value?is?the?number?of?objects?(pages)?remaining,?or?-1?if?we?cannot?
  • ?*?proceed?without?risk?of?deadlock?(due?to?gfp_mask).?
  • ?*?
  • ?*?We?approximate?LRU?via?least-recently-unpinned,?jettisoning?unpinned?partial?
  • ?*?chunks?of?ashmem?regions?LRU-wise?one-at-a-time?until?we?hit?'nr_to_scan'?
  • ?*?pages?freed.?
  • ?*/??
  • static?int?ashmem_shrink(int?nr_to_scan,?gfp_t?gfp_mask)??
  • {??
  • ????struct?ashmem_range?*range,?*next;??
  • ??
  • ????/*?We?might?recurse?into?filesystem?code,?so?bail?out?if?necessary?*/??
  • ????if?(nr_to_scan?&&?!(gfp_mask?&?__GFP_FS))??
  • ????????return?-1;??
  • ????if?(!nr_to_scan)??
  • ????????return?lru_count;??
  • ??
  • ????mutex_lock(&ashmem_mutex);??
  • ????list_for_each_entry_safe(range,?next,?&ashmem_lru_list,?lru)?{??
  • ????????struct?inode?*inode?=?range->asma->file->f_dentry->d_inode;??
  • ????????loff_t?start?=?range->pgstart?*?PAGE_SIZE;??
  • ????????loff_t?end?=?(range->pgend?+?1)?*?PAGE_SIZE?-?1;??
  • ??
  • ????????vmtruncate_range(inode,?start,?end);??
  • ????????range->purged?=?ASHMEM_WAS_PURGED;??
  • ????????lru_del(range);??
  • ??
  • ????????nr_to_scan?-=?range_size(range);??
  • ????????if?(nr_to_scan?<=?0)??
  • ????????????break;??
  • ????}??
  • ????mutex_unlock(&ashmem_mutex);??
  • ??
  • ????return?lru_count;??
  • }??
  • ?? ? ? ?這里的參數(shù)nr_to_scan表示要掃描的頁數(shù),如果是0,則表示要查詢一下,當前Ashmem驅(qū)動程序有多少頁面可以回收,這里就等于掛在lru列 表的內(nèi)塊頁面的總數(shù)了,即lru_count;否則,就要開始掃描lru列表,從中回收內(nèi)存了,直到回收的內(nèi)存頁數(shù)等于nr_to_scan,或者已經(jīng)沒 有內(nèi)存可回收為止。回收內(nèi)存頁面是通過vm_truncate_range函數(shù)進行的,這個函數(shù)定義在kernel/common/mm /memory.c文件中,它是Linux內(nèi)核內(nèi)存管理系統(tǒng)實現(xiàn)的,有興趣的讀者可以研究一下。

    ?? ? ? ?這樣,Android系統(tǒng)匿名共享內(nèi)存Ashmem驅(qū)動程序源代碼就分析完了,在下一篇文章中,我們將繼續(xù)分析Android系統(tǒng)的匿名共享內(nèi)存機制,研究它是如何通過Binder進程間通信機制實現(xiàn)在不同進程程進行內(nèi)存共享的,敬請關(guān)注。

    老羅的新浪微博:http://weibo.com/shengyangluo,歡迎關(guān)注!

    轉(zhuǎn)載于:https://www.cnblogs.com/Free-Thinker/p/4142112.html

    總結(jié)

    以上是生活随笔為你收集整理的Android系统匿名共享内存Ashmem(Anonymous Shared Memory)驱动程序源代码分析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。

    国产午夜精品视频 | 国产精品成人a免费观看 | 9在线观看免费高清完整版 玖玖爱免费视频 | 亚洲国产激情 | 免费观看性生交 | 国产精品6999成人免费视频 | av官网在线 | 久综合网 | 黄色国产高清 | 日韩伦理片hd | 99精品久久久久久久 | 五月天激情视频在线观看 | 国产精品涩涩屋www在线观看 | 丁香视频全集免费观看 | 91精品欧美一区二区三区 | 国产精品成人在线 | 久久久久一区二区三区四区 | 精品国产免费一区二区三区五区 | av大片网站| 天天干天天操天天拍 | 国产精品久久一区二区三区, | 香蕉视频色 | 免费在线观看黄色网 | 久久久黄色 | 欧美日韩高清 | 色婷婷激情五月 | 中文字幕大全 | 99精品欧美一区二区三区黑人哦 | 国产免费av一区二区三区 | 在线观看日韩免费视频 | 美女免费av | 国产人免费人成免费视频 | 一二三四精品 | 亚洲视频,欧洲视频 | 亚洲男男gaygay无套同网址 | av成人资源 | 久久国产精品视频观看 | 99久久久久免费精品国产 | 午夜精品电影 | 在线看一级片 | 欧美视频99| 亚州国产精品久久久 | 97人人模人人爽人人喊网 | 国产91精品高清一区二区三区 | 国产精品va在线观看入 | 在线看片a | 中文字幕日韩高清 | 国产在线理论片 | 99视频免费看 | 天天操人人要 | 色综合久久久久综合体桃花网 | 丁香六月色 | 亚洲视频免费在线观看 | 99国产一区| 久久这里精品视频 | 97色婷婷人人爽人人 | 国产99久久久国产 | 色99在线 | 国产vs久久 | 看v片| 国产夫妻性生活自拍 | 九九国产精品视频 | 伊人久久国产精品 | 最近的中文字幕大全免费版 | 亚洲成a人片在线观看网站口工 | 91精品免费视频 | 天天av天天 | 干天天| 日日碰狠狠躁久久躁综合网 | 欧美色图p | 中文字幕在线视频精品 | 久久久国产一区二区 | 国产专区一 | 久久不见久久见免费影院 | 天天爱天天操天天爽 | 日韩电影中文,亚洲精品乱码 | 国产精品com | 激情综合网在线观看 | 一级做a爱片性色毛片www | 久久久黄色免费网站 | 深爱五月网 | 91丨九色丨蝌蚪丰满 | 丰满少妇在线观看资源站 | 91人人澡人人爽人人精品 | 国产精品久久久久久高潮 | 国产免费国产 | 91精品在线播放 | 国产一区二区免费在线观看 | 国产aa精品 | 国产91全国探花系列在线播放 | 久久综合影视 | 久久综合久色欧美综合狠狠 | 香蕉网在线 | 91爱爱免费观看 | 综合久久五月天 | 超碰人人乐 | 不卡日韩av | 在线观看精品视频 | a级国产乱理伦片在线播放 久久久久国产精品一区 | 91在线区 | 国产精品不卡在线播放 | 五月婷婷色| 欧洲激情在线 | 国产精品久久久久久久久免费看 | 欧美综合久久 | 国产一级在线看 | 久在线 | 久久久91精品国产 | 国产va精品免费观看 | 波多野结衣电影一区 | 91精品免费在线 | 欧美日韩国产一二 | 青青久草在线视频 | 一区二区av | 亚洲午夜久久久久久久久电影网 | 黄色片免费电影 | 亚洲国产精品激情在线观看 | 日韩av免费在线电影 | 久久不射网站 | 奇米网在线观看 | 好看av在线 | 久久免费国产视频 | 免费中文字幕在线观看 | 亚洲精品动漫在线 | 亚洲一区二区麻豆 | 狠狠狠干 | 久久久91精品国产一区二区三区 | 蜜臀av性久久久久蜜臀aⅴ四虎 | 91视频久久 | 久久神马影院 | 97色在线观看免费视频 | 欧美激情综合色综合啪啪五月 | 毛片一级免费一级 | 日韩精品一区二区在线观看 | 成人a视频在线观看 | 国产精品久久久久久久久久久久午夜 | 成人一级在线 | 国产精品福利一区 | 免费在线观看午夜视频 | 91精品久久久久久久久 | 天天干天天射天天爽 | 永久免费毛片 | 五月婷婷电影网 | 成人在线观看av | 亚洲精品网页 | 国产精品18久久久久久久久 | 91精品久久久久久久久久入口 | 伊人狠狠干 | 99热超碰 | 夜色资源站国产www在线视频 | 久久黄色片子 | 国产中文字幕视频在线 | 夜夜干夜夜 | 亚洲欧美国产精品18p | 九九热只有这里有精品 | 欧美日韩在线观看一区二区 | 中字幕视频在线永久在线观看免费 | 国产精品一区二区久久精品爱微奶 | 久久精品国产一区二区三区 | 国产a级片免费观看 | 久久99精品热在线观看 | 午夜美女福利 | 日本三级全黄少妇三2023 | 国产在线97 | 麻豆网站免费观看 | 四虎影视精品永久在线观看 | 狠狠色丁香婷婷综合视频 | 日日夜夜免费精品视频 | 少妇啪啪av入口 | 国产成人精品av久久 | 国产日韩精品一区二区在线观看播放 | 日韩欧美在线影院 | 欧美坐爱视频 | 亚洲国产欧美在线看片xxoo | 成人在线视 | 成人国产精品av | 国产自产在线视频 | 日韩欧美高清一区二区三区 | 久久亚洲免费视频 | av电影在线不卡 | av丝袜在线 | 一区二区三区在线电影 | 91日韩在线播放 | 免费精品国产 | 日韩伦理片一区二区三区 | 在线91播放 | 国产精品久久一区二区三区, | 91中文字幕在线观看 | 久久少妇免费视频 | 综合网中文字幕 | 在线免费高清一区二区三区 | 国产福利a | 91福利视频免费 | 日韩在线电影一区 | 免费毛片一区二区三区久久久 | 成人一区二区在线 | 不卡精品 | 国产一区二区影院 | 中文字幕区 | 日韩www在线 | 91九色视频导航 | 激情丁香在线 | 久久影院中文字幕 | 亚洲一级片在线观看 | 手机看片99| 鲁一鲁影院 | 欧美日韩中文视频 | 日韩av免费一区 | 日韩免费在线视频 | 国产黄色av网站 | 久久婷婷色 | 不卡国产视频 | 在线观看色网站 | 免费涩涩网站 | 在线视频麻豆 | 五月激情丁香婷婷 | 一级黄色在线免费观看 | 久久污视频 | 欧美成人精品三级在线观看播放 | 国产精品原创视频 | 色九色| 欧美综合干 | 久久永久视频 | 亚洲精品久久久久久国 | 丁香激情综合 | 免费 在线 中文 日本 | 国产精品99免费看 | 亚洲美女免费精品视频在线观看 | 日韩精品免费在线视频 | 久久丁香 | 欧美人牲 | 天海翼一区二区三区免费 | 日韩经典一区二区三区 | 午夜久草 | 天天操人人要 | 成年人视频在线免费观看 | 久久在线精品视频 | 日韩在线观看视频在线 | 99精品在线看 | 日韩激情视频在线 | 四虎在线永久免费观看 | 亚洲成av人片在线观看 | 亚洲精品视频在线免费播放 | 天堂入口网站 | 亚洲精品动漫久久久久 | 国产精品人人做人人爽人人添 | 成人av在线直播 | 97在线视频网站 | 国产精品久久久久久久久久久免费 | 六月丁香婷 | 国产专区在线播放 | 国产91精品一区二区麻豆亚洲 | 国产99re | 日韩免费大片 | 高清一区二区三区av | 毛片一区二区 | 国产喷水在线 | 91精品在线视频观看 | 国产三级精品在线 | 欧美视频xxx| 天天干天天怕 | 99热在线精品观看 | 特级毛片在线免费观看 | 国产伦精品一区二区三区四区视频 | 成人免费在线播放 | 天天天天天天天操 | 91黄色视屏| 色婷婷免费 | 日韩精品一区二区三区免费视频观看 | 久久激情视频 | 精品国产一区二区三区四区在线观看 | 日韩在线视频播放 | 韩国av三级 | 五月婷婷一级片 | 91丨九色丨高潮 | 亚洲jizzjizz日本少妇 | 国产精品久久久久久久久久久久午夜片 | 九九热在线精品 | 亚洲精品国偷自产在线99热 | 91高清在线看 | 久久免费精品国产 | 日韩r级在线 | 欧洲精品视频一区 | av成人在线观看 | 久久在线免费观看视频 | 免费日韩一区二区三区 | 国产三级av在线 | 日韩在线电影一区二区 | 91在线观 | 一区二区中文字幕在线播放 | 97精品国产97久久久久久免费 | 国产精品小视频网站 | 在线观看视频一区二区 | 欧美一级看片 | 超碰97免费 | 天天插天天狠 | 国产96在线视频 | 在线电影a | 国产在线观看免 | 国产成人久久精品 | 亚洲va欧美va人人爽 | 色激情五月 | 精品美女久久久久 | 波多野结衣理论片 | 久久精品视频免费 | 成人av电影免费观看 | 久久精品欧美一区 | 日日爽天天爽 | 99久久9| 欧美综合国产 | 亚洲精品国产自产拍在线观看 | 日韩精品一区二区在线观看视频 | 国产精品成人av在线 | 久久精品国产第一区二区三区 | 999超碰 | 五月激情五月激情 | 国产男女爽爽爽免费视频 | 日韩精品视频在线免费观看 | 欧美日韩中字 | 亚洲国内精品视频 | 在线观看亚洲视频 | 亚洲精品午夜aaa久久久 | 欧美大香线蕉线伊人久久 | 欧美国产日韩在线观看 | 91av视频免费在线观看 | 999久久精品 | 中文字幕av在线电影 | 日韩免费一区二区 | 久久久久久久久国产 | 在线看v片成人 | 欧美aa一级 | 麻豆观看| 亚洲国产精久久久久久久 | 国产亚洲精品久久久久5区 成人h电影在线观看 | 国产精品对白一区二区三区 | 天天操天天综合网 | 国产激情久久久 | 亚洲草视频 | 粉嫩av一区二区三区四区在线观看 | 中文字幕在线观看一区二区三区 | 青青河边草免费直播 | 久操97 | 中文字幕黄色网 | 久久99精品波多结衣一区 | 国产91精品看黄网站 | 久久免费视频这里只有精品 | 欧美激情操 | 五月香视频在线观看 | 欧美成年网站 | 国产区免费 | 欧美日韩亚洲国产一区 | 国产亚洲精品久久久久久无几年桃 | 探花视频在线观看+在线播放 | 97视频播放| 免费一级片观看 | 91在线91拍拍在线91 | 九九九在线观看 | 在线观看 亚洲 | 欧洲精品在线视频 | 欧美一级片免费播放 | 最近最新中文字幕 | 人人盈棋牌 | 激情久久伊人 | 香蕉视频网站在线观看 | 国产色a在线观看 | 婷婷精品进入 | 午夜视频一区二区三区 | 国产女人40精品一区毛片视频 | 正在播放五月婷婷狠狠干 | 成人国产在线 | 久国产在线播放 | 毛片基地黄久久久久久天堂 | 五月婷婷一区二区三区 | 丝袜足交在线 | 成人欧美一区二区三区黑人麻豆 | 国产成人一区二区三区电影 | 欧美色噜噜 | 久久久精品国产一区二区电影四季 | av电影免费在线看 | 最新成人av | 欧美最猛性xxxxx(亚洲精品) | 国产日韩精品在线 | 免费一级日韩欧美性大片 | 国产精品永久久久久久久www | 欧美analxxxx | 久久国内精品99久久6app | 精品a视频| 中文字幕影片免费在线观看 | 蜜臀av网址| 免费观看久久久 | 中文字幕在线免费 | 国产精品成人av久久 | 久久精品国产亚洲 | 91你懂的 | 亚洲性xxxx | 成人精品一区二区三区中文字幕 | 69亚洲视频 | avav片| 精品美女在线视频 | 91精品天码美女少妇 | 国外调教视频网站 | 四虎成人免费影院 | 亚洲国产美女精品久久久久∴ | 久久久国产精品一区二区三区 | 99视频偷窥在线精品国自产拍 | 中文字幕在线观看视频免费 | 在线亚洲天堂网 | 精品一区二区三区香蕉蜜桃 | 麻豆影视在线观看 | 99久热在线精品视频成人一区 | 欧美va电影 | 久久影视一区二区 | 一区二区三区在线播放 | 久久久黄视频 | 亚洲成人av在线播放 | 国产综合福利在线 | 日日干影院| 91视频在线免费下载 | 91探花国产综合在线精品 | 日韩免费一区二区 | 天天综合区| 亚洲区另类春色综合小说 | 国产在线国偷精品产拍免费yy | 国内小视频在线观看 | 麻豆手机在线 | 激情丁香5月 | 美女国内精品自产拍在线播放 | 天天综合成人网 | 国产黄视频在线观看 | 天天综合网天天综合色 | 国产精品九九九九九九 | 四虎在线观看网址 | 免费看一级特黄a大片 | 国产色道 | 国产精品日韩在线 | 国产亚洲人成网站在线观看 | 国产96在线观看 | 国产高清免费在线播放 | 人人舔人人干 | 在线亚洲日本 | 久久视频网址 | 四季av综合网站 | 99久久精品日本一区二区免费 | 国产精品黄网站在线观看 | 欧美日韩中字 | 日韩三级成人 | 国产日韩精品一区二区三区 | 亚洲第一av在线播放 | 一区二区三区日韩在线观看 | 草久视频在线观看 | 五月天综合网站 | 国产福利精品视频 | 97色婷婷成人综合在线观看 | 97色se| 丁香激情综合久久伊人久久 | 亚洲永久精品在线 | 一本一道久久a久久精品 | 草草草影院 | 日韩视频免费在线观看 | 色妞色视频一区二区三区四区 | 国产91在线 | 美洲 | 人人艹视频 | 一本一本久久a久久精品综合妖精 | 午夜久久久久久久 | 人人爽影院 | 99精品视频在线观看播放 | 天天干天天做天天操 | 国产乱码精品一区二区三区介绍 | 最近免费中文字幕mv在线视频3 | 国产日产精品一区二区三区四区的观看方式 | 日韩欧美在线高清 | 国产麻豆果冻传媒在线观看 | 国产高清免费 | 成人国产精品一区 | 色成人亚洲 | 国产精品av免费观看 | 国产美女视频免费 | 91av观看| 久草在线视频网站 | 久青草视频在线观看 | av在线播放免费 | 久久久国产精华液 | 在线免费观看黄色 | 午夜精品三区 | 日韩大片免费观看 | 亚洲精选99 | 日本精品一区二区 | 中文字幕在线视频精品 | 婷婷av综合 | 91在线视频观看免费 | 日韩69视频 | 成人一级电影在线观看 | 911香蕉视频 | 成人福利在线 | 日韩大片在线观看 | 97在线影视 | 天天天天干 | 在线观看网站黄 | 亚洲 欧美 变态 国产 另类 | 天天插日日插 | 一区中文字幕在线观看 | 国产999精品久久久久久绿帽 | 久久国产视频网站 | 91麻豆文化传媒在线观看 | 99精品国产福利在线观看免费 | 国产精品久久婷婷六月丁香 | 激情欧美在线观看 | 免费看黄的| 最新av电影网站 | 97视频资源 | 天天干夜夜夜操天 | 成人黄色电影视频 | 久久福利在线 | 久久国产电影院 | 在线精品在线 | 一级电影免费在线观看 | 免费观看91视频大全 | 亚洲高清在线观看视频 | 免费观看一级一片 | 国产视频资源在线观看 | 国内久久精品视频 | 91天天视频 | 伊人超碰在线 | 日韩在线二区 | 911国产 | 99在线热播 | 操碰av| 欧美日韩高清一区 | 国语黄色片 | 国产一区二区在线视频观看 | 久草在线在线精品观看 | 狂野欧美激情性xxxx | 亚洲美女久久 | 992tv又爽又黄的免费视频 | 婷婷精品在线视频 | 特级a毛片| 日韩精品一区二区三区免费视频观看 | 中文字幕黄色av | 日日夜夜骑 | 一区二区三区免费播放 | 超碰在线个人 | 久草视频视频在线播放 | 成人在线播放免费观看 | 亚洲理论电影 | 国产福利一区二区在线 | 欧美激情精品久久久久久 | 视频一区二区免费 | 免费高清在线观看成人 | 久久er99热精品一区二区 | 免费在线观看中文字幕 | 午夜国产福利在线观看 | av千婊在线免费观看 | 在线不卡中文字幕播放 | 欧美 日韩精品 | 国产精品美女久久久久久久久 | www色com| 久久久久久久网 | 中文字幕成人在线 | 亚洲国产精品va在线看 | 精品久久久久久久 | 欧美色综合天天久久综合精品 | 一区二区三区久久精品 | 色人久久 | 天天干夜夜 | 青青草视频精品 | 亚洲人成在线电影 | 成人免费视频播放 | 国产亚洲精品久久 | 国产视频一 | 国产免费又黄又爽 | 激情视频二区 | 在线看国产一区 | 国产精品久久久久久久久久99 | 久久国产欧美日韩精品 | 国产精品久久久久久久久久白浆 | 国产精品 999 | av福利超碰网站 | 国产91免费观看 | 亚洲国产精品女人久久久 | 国产成人一区二区三区在线观看 | 亚洲精品综合一二三区在线观看 | 国产免费久久av | 国产91成人在在线播放 | 欧美日韩亚洲精品在线 | 91香蕉视频在线 | 国产高清视频在线观看 | 亚洲精品人人 | 6080yy精品一区二区三区 | 国产一区福利 | 毛片久久久| 中文字幕黄色网址 | 四虎永久免费在线观看 | 午夜视频99 | 国产美女视频黄a视频免费 久久综合九色欧美综合狠狠 | 丁香六月综合网 | 亚洲另类视频在线观看 | 欧洲精品在线视频 | 成年人在线免费看视频 | 国产美女免费观看 | 成人蜜桃网 | 中文国产字幕 | 激情久久影院 | 成年人免费看av | 午夜美女福利直播 | 中文字幕久久精品一区 | 91在线色 | 丁香综合五月 | 亚洲无线视频 | 香蕉免费 | 国产成人黄色网址 | 高清免费在线视频 | 日韩av二区 | 看av免费| 91久久偷偷做嫩草影院 | 成人蜜桃网| 国产小视频在线观看免费 | 国产精品久久久久久高潮 | 久久婷婷精品 | 日韩欧美高清一区二区三区 | 麻豆手机在线 | 天天操人 | 色综合天| 99国产在线| 五月综合激情网 | 美女视频黄频大全免费 | 亚洲欧美日本一区二区三区 | 天天干天天拍天天操天天拍 | av千婊在线免费观看 | 久久久久久久久久久免费av | 日韩最新在线视频 | 久久8| 91免费看黄 | 国产成人专区 | 亚洲好视频 | 天天爽网站 | 欧美日韩亚洲一 | 亚洲第一香蕉视频 | 亚洲va韩国va欧美va精四季 | 高清一区二区三区av | 久久中文网 | 深爱激情站 | 一区二区伦理电影 | 91资源在线播放 | 99国产精品一区二区 | 亚洲精品国偷拍自产在线观看 | 麻豆视频在线免费 | 可以免费看av | 婷婷av资源 | 欧美成人一二区 | av三级av| 亚洲专区欧美 | 日韩动态视频 | 婷婷五月在线视频 | av丝袜在线| 国产精品99久久久久久久久 | 日本久久电影网 | 色 中文字幕| 超碰在线人人草 | 国产我不卡 | 国产精品麻豆一区二区三区 | 色综合久久久久综合体桃花网 | 中文字幕在线日亚洲9 | 国产精品久久久久久久久久尿 | 亚洲一区二区三区在线看 | 日韩精品久久一区二区 | 精品在线播放 | 日本久久成人中文字幕电影 | 国产一区二区在线播放视频 | 国产91免费在线 | 日韩一区二区三区高清在线观看 | 波多野结衣一区二区三区中文字幕 | 亚洲国产激情 | 天天操操操操操操 | 超碰97国产 | av在线免费播放网站 | 久草精品视频在线播放 | 日韩天天综合 | 欧美日韩中文字幕在线视频 | 深爱激情综合 | 在线小视频 | 亚洲婷婷丁香 | 久久99国产精品久久 | 日韩四虎 | 国产伦精品一区二区三区在线 | 色综合色综合久久综合频道88 | 在线中文字幕观看 | 久久欧洲视频 | 人人狠| 91av免费观看 | 五月激情丁香图片 | 人人搞人人干 | 日韩精品一卡 | 首页av在线| 99视频在线免费观看 | 一区三区在线欧 | 日韩午夜三级 | 久草国产在线观看 | 亚洲国产精品第一区二区 | 国产福利91精品一区 | 国产一区二区不卡视频 | 99这里只有久久精品视频 | 日韩在线网| 免费网站色 | 欧美日韩精品免费观看视频 | 日韩美一区二区三区 | 美女av在线免费 | 1024手机看片国产 | 免费a一级 | 超碰在线网 | 国产精品久久久久久久久久99 | 青青看片| 国产美女久久久 | 少妇啪啪av入口 | 中文字幕第一页在线视频 | 四虎影视国产精品免费久久 | 久久精品亚洲一区二区三区观看模式 | 91在线麻豆 | 精品成人a区在线观看 | 色爱成人网 | 国产精品久久久久永久免费看 | 一区二区三区日韩在线观看 | 亚洲欧美国产日韩在线观看 | 日韩理论视频 | 97超在线视频| 丁香婷婷激情五月 | 国产福利一区二区三区视频 | 久久再线视频 | 一区二区三区免费在线 | 伊人五月天综合 | 中文资源在线播放 | 精品久久久久久久久久久久久久久久 | 亚洲成a人片在线观看中文 中文字幕在线视频第一页 狠狠色丁香婷婷综合 | 91少妇精拍在线播放 | 91黄色免费网站 | 91亚色视频在线观看 | 国内三级在线观看 | 超碰免费av | 最近中文字幕完整高清 | 日本精品二区 | 青草视频在线看 | 视频在线观看99 | 在线观看一区 | 国产不卡在线观看视频 | 深爱激情五月婷婷 | 久久精品国产一区二区三区 | 日韩视频一 | 香蕉视频免费在线播放 | 亚洲精品高清在线观看 | 97超碰人人网 | 92精品国产成人观看免费 | 日韩视频在线观看免费 | 91免费国产在线观看 | 国产三级香港三韩国三级 | 久久成人在线视频 | 在线97| 丁香六月在线 | 亚洲永久精品国产 | 97精产国品一二三产区在线 | 深爱婷婷 | 国产精品毛片一区视频播 | 久久字幕精品一区 | 天天干天天射天天爽 | 五月天,com | 国产成人一二片 | 国产精品一区二区中文字幕 | 亚洲精品小视频 | 五月婷网站| 久精品视频免费观看2 | 中文字幕国产精品一区二区 | www91在线观看 | 天天爱天天 | 性日韩欧美在线视频 | 国产精品久久久久影视 | 久久久精品久久日韩一区综合 | 色噜噜在线观看视频 | 91看片麻豆 | 久久午夜国产 | 97碰碰视频| 99精品在线免费在线观看 | 亚洲国产天堂av | 国产亚洲婷婷免费 | 91麻豆国产福利在线观看 | 在线观看第一页 | 久久久久国产a免费观看rela | 蜜臀久久99精品久久久无需会员 | 国产亚洲精品久久19p | 成人h在线 | 欧美不卡视频在线 | 香蕉视频久久 | 午夜精品福利在线 | av在线免费观看黄 | 国产专区视频在线 | 国产男女免费完整视频 | 久久69精品久久久久久久电影好 | www.av中文字幕.com | 日韩免费一区二区 | 亚洲精品女人久久久 | 91麻豆精品国产自产 | 国产视频丨精品|在线观看 国产精品久久久久久久久久久久午夜 | 国产精品久久99综合免费观看尤物 | 成人av片免费观看app下载 | 久草精品视频在线观看 | 久久婷婷五月综合色丁香 | 成人黄色av网站 | 久久高清国产视频 | 99精品视频在线观看视频 | 精品a在线| 国产黄在线 | 国产一区在线看 | 在线 高清 中文字幕 | 女人18片毛片90分钟 | 日韩久久精品一区二区三区 | 精品国产一区二区三区蜜臀 | 日日骑| 日韩视频在线不卡 | 免费在线观看日韩视频 | 中文字幕在线播放av | 中文字幕 婷婷 | 亚洲国产精品免费 | 91探花在线视频 | 亚州精品一二三区 | 久久国产手机看片 | 午夜美女视频 | 日韩xxx视频 | 国内外激情视频 | 国产精品久久久久毛片大屁完整版 | 国产成人三级一区二区在线观看一 | 九九九九精品九九九九 | 91精品国产91久久久久久三级 | www.色婷婷 | 521色香蕉网站在线观看 | 97网站| 九色精品免费永久在线 | www婷婷| 亚洲天堂网站 | 国产精品日韩 | 国产成人精品在线观看 | 午夜精品久久久久久 | 久99久视频| 黄色一及电影 | 亚洲成人av电影 | 日韩午夜在线观看 | 国产一区在线观看免费 | 国产99精品 | 综合网天天色 | 久久国产精品影视 | av成人在线观看 | 国产精品第一页在线 | 午夜精品一区二区三区视频免费看 | 日韩电影在线观看一区 | 永久黄网站色视频免费观看w | 激情图片久久 | 丁香综合 | 亚洲黄色av | 日韩精品一区二区三区在线播放 | 成人禁用看黄a在线 | 美女视频黄,久久 | 国产成人区 | 亚洲六月丁香色婷婷综合久久 | 在线观看深夜视频 | 国产精品久久一区二区三区不卡 | 亚洲国产福利视频 | 亚洲精品麻豆视频 | 国产精品视频一二三 | 九九精品视频在线观看 | 最近乱久中文字幕 | 国产精品乱码久久久 | 中文字幕在线免费播放 | 一级免费观看 | 国产一区福利 | 精品国产一区二区三区蜜臀 | 亚洲精品在线免费看 | 日韩欧美一区二区三区视频 | 亚洲黄色免费电影 | 视频一区在线免费观看 | 久久久久久久久久伊人 | 日韩中文字幕免费在线播放 | 一级片免费观看视频 | 午夜成人免费电影 | 亚洲黄网站 | 日韩高清在线观看 | 一区二区三区高清在线 | 中文字幕一区二区三区精华液 | 国内精品久久久久久久久久 | 国产福利精品视频 | 日韩剧情 | 91精品在线观看视频 | 亚洲精品在线观看免费 | 91视频国产免费 | 国内精品久久久久久久久久久 | 中文字幕欲求不满 | 欧美日韩p片 | 免费日韩电影 | 国产二区视频在线观看 | 欧美成人aa| 日韩精品你懂的 | 99久久精品无码一区二区毛片 | 99热999 | 久久久久免费精品国产小说色大师 | 天天操天天摸天天干 | 久久久免费在线观看 | 国产精品初高中精品久久 | 国产精品高潮呻吟久久久久 | 日韩视频一区二区 | 精品91视频 | 97在线成人 | 国产香蕉久久精品综合网 | 欧美 高跟鞋交 xxxxhd | 日韩中文字幕视频在线观看 | 99久热精品 | 欧美日韩天堂 | 午夜视频在线观看一区 | 色综合天天爱 | 午夜精选视频 | 亚洲精品在线观看av | 青草视频免费观看 | 麻花豆传媒mv在线观看网站 | 国产一级免费电影 | av高清一区二区三区 | 麻豆系列在线观看 | 91精品麻豆 | 在线v片| 91成人精品视频 | 国产精品福利视频 | 午夜123 | 91中文字幕永久在线 | 久久精品电影网 | 欧美日韩亚洲一 | 国产免费一区二区三区网站免费 | 黄色.com| 尤物九九久久国产精品的分类 | 人人射人人爽 | av片一区二区 | 日韩欧美一区二区不卡 | 国产久视频 | 最新真实国产在线视频 | 久热色超碰 | 精品在线视频观看 | 国产又粗又猛又色又黄视频 | 激情偷乱人伦小说视频在线观看 | 天天拍天天色 | 国产蜜臀av | 网站在线观看日韩 | 青草视频在线看 | 爱干视频 | 国产福利精品视频 | 免费看三级网站 | 看全黄大色黄大片 | 97香蕉超级碰碰久久免费软件 | 久一网站| 精品视频在线免费 | 精品成人免费 | 日韩久久精品一区二区三区 | 亚洲电影av在线 | 美女网站免费福利视频 | 亚洲精品456在线播放乱码 | 精品欧美小视频在线观看 | 最新午夜电影 | 国产福利91精品张津瑜 | 免费碰碰 | 久久激情片 | www.xxxx变态.com | 国产精品久久久久一区二区国产 | 日韩免费电影一区二区三区 | 中文字幕一区在线观看视频 | 天堂网av 在线| 成人在线视 | 最新日韩电影 | 午夜精品视频一区二区三区在线看 | 精品久久久久久久久久久院品网 | 国产精品免费不卡 | 亚洲国产网址 | 亚洲欧美成人在线 | 九月婷婷综合网 | 中文字幕永久 | 99 色| 欧美福利久久 | 亚洲综合精品视频 | 亚洲午夜av | 中文字幕韩在线第一页 | 国产在线超碰 | 男女视频91 | 欧美激情精品久久久久久免费 | 午夜电影中文字幕 | 亚洲精品视频久久 | 免费福利片2019潦草影视午夜 | 婷婷在线综合 | 国产午夜精品一区二区三区在线观看 | 久久久久久电影 | 日韩免费视频在线观看 | 欧美激情综合色 | 精品国产电影 | 国产精品无av码在线观看 | 激情视频网页 | 一级一片免费观看 | 久久久www成人免费精品 | 成人黄色在线电影 | 草久在线 |