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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

参考资料学习APR库

發(fā)布時間:2024/1/23 编程问答 98 豆豆
生活随笔 收集整理的這篇文章主要介紹了 参考资料学习APR库 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

APR分析-整體篇

? ? 一、何為APR?

? ? ? ? ?Apache Server經(jīng)過這么多年的發(fā)展后,將一些通用的運行時接口封裝起來供給大家,這就是Apache Portable Run-time libraries,APR。

? ? 二、APR的目錄組織

? ? ? ? ? 1)所有的頭文件都放在$(APR)/include目錄中;

? ? ? ? ? 2)所有功能接口的實現(xiàn)都放在各自的獨立目錄下,如threadproc、mmap等;

? ? ? ? ? 3)此外就是相關(guān)平臺構(gòu)建工具文件如Makefile.in等。曾經(jīng)看過ACE的代碼,ACE的所有源文件(.cpp)都放在一個目錄下,顯得很混亂。

? ? ? ? ? ?4)進入各功能接口子目錄,以threadproc為例,在其下面的子目錄有5個,分別是beos、netware、os2、unix和win32.

? ? 三、APR構(gòu)建

? ? ? ? ? ?如果想要使用APR,需要先在特定平臺上構(gòu)建它,這里不考慮多個平臺的特性,僅針對Unix平臺進行分析。

? ? ? ? ? ?1) apr.h、apr.h.in、apr.h.hw和apr.h.hnw的關(guān)系

? ? ? ? ? ? ? ?在$(APR)/include目錄下,由于APR考慮移植性等原因,最基本的apr.h文件是在構(gòu)建時自動生成的,其中apr.h.in類似一模板作為apr.h生成程序的輸入源。其中apr.h.hw和apr.h.hnw分別是Windows和NetWare的特定版本。

? ? ? ? ? ?2) 編譯時注意事項

? ? ? ? ? ? ? ? 在Unix上編譯時,注意$(APR)/build下*.sh文件的訪問權(quán)限,應(yīng)該先chmod以下,否則Make的時候會提示ERROR。

? ? 四、應(yīng)用APR

? ? ? ? ? ? 我們首先make install一下,比如我們在Makefile中指定prefix=$(APR)/dist,則make install后,在$(APR)/dist下會發(fā)現(xiàn)4個子目錄,分別為bin、lib、include和build,其中我們感興趣的只有include和lib。

? ? ? ? ? ? ?下面是一個APR app的例子project。

? ? ? ? ? ? ?該工程的目錄組織如下:

$(apr_path)- dist- lib- include- examples- apr_app- Make.properties- Makefile- apr_app.c

? ? ? ? ? ? ? 我們的Make.properties文件內(nèi)容如下:

# # The APR app demo # CC = gcc -Wall BASEDIR =$(HOME)/apr-1.1.1/examples/apr_app APRDIR =$(HOME)/apr-1.1.1 APRVER = 1 APRINCL =$(APRDIR)/dist/include/apr-$(APRVER) APRLIB =$(APRDIR)/dist/lib DEFS = -D_REENTRANT -D_POSIX_PTHREAD_SEMANTICS -D_DEBUG_ LIBS = -L$(APRLIB) -lapr-$(APRVER) /-lpthread -lxnet -lposix4 -ldl -lkstat -lnsl -lkvm -lz -lelf -lm -lsocket -ladmINCL = -I$(APRINCL) CFLAGS =$(DEFS) $(INCL)Makefile文件內(nèi)容如下: include Make.properties TARGET = apr_app OBJS = apr_app.o all: $(TARGET) $(TARGET): $(OBJS)$(CC) ${CFLAGS} -o $@$(OBJS) ${LIBS} clean:rm -f core $(TARGET)$(OBJS)

? ? ?而apr_app.c文件采用的是$(par_path)/test目錄下的proc_child.c文件。

五、GO ON

? ? 5.1 APR分析-設(shè)計篇

? ? ? ? 5.1.1 類型

? ? ? ? ? ? ?1) APR提供的節(jié)本自定義數(shù)據(jù)類型包括:

typedef unsigned char apr_byte_t; typedef short apr_int16_t; typedef unsigned short apr_uint16_t; typedef int apr_int32_t; typedef unsigned int apr_uint32_t; typedef long long apr_uint32_t; typedef long long apr_int64_t; typedef unsigned long long apr_uint64_t;

? ? ? ? ? ? ?這些都是在apr.h中定義的。

? ? ? ? ? ? ?2)在APR的設(shè)計文檔中,它稱"dso、mmap、process、thread"等為"base types"。

? ? ? ? ? ? ?3) 另外的一個特點就是大多APR類型中都包含一個apr_pool_t類型的字段,你最好在該類型中假如一個apr_pool_t類型的字段,否則所有操作該類型的APR函數(shù)都需要一個apr_pool_t類型的參數(shù)。

? ? ? ?5.1.2 函數(shù)

? ? ? ? ? ? 1) APR的固定個數(shù)參數(shù)公共函數(shù)的聲明形式APR_DECLARE(rettype) apr_func(args);而非固定個數(shù)參數(shù)的公共函數(shù)的聲明形式為APR_DECLARE_NONSTD(rettype) apr_func(args,...);".在Unix上的apr.h中有著兩個宏的定義:

#define APR_DECLARE(type) type #define APR_DECLARE_NONSTD(type) type

? ? ? ? ? ? ? ? ? 在apr.h文件中解釋了這么做就是為了在不同平臺上編譯時使用“the most appropriate calling convention”,這里的"calling convention"是一術(shù)語,叫"調(diào)用約定".常見的調(diào)用約定有:stdcall、cdecl、fastcall、thiscall和naked call,其中cdecl調(diào)用約定又稱為C調(diào)用約定,是C語言缺省的調(diào)用約定。

? ? ? ? ? ? ?2)如果你想新增APR函數(shù),APR建議你最好按照如下做:

? ? ? ? ? ? ? ? ?a)輸出參數(shù)為第一個參數(shù):

? ? ? ? ? ? ? ? ? b)如果某個函數(shù)需要內(nèi)部分配內(nèi)存,則將一個apr_pool_t 參數(shù)放在最后。

? ? ? ? ? 5.1.3 錯誤處理

? ? ? ? ? ? ? ? APR作為一通用的庫接口集合詳細的說明了使用APR時如何進行錯誤處理。

? ? ? ? ? ? ? ?1) 錯誤處理的第一步就是"錯誤碼和狀態(tài)碼分類"。APR的函數(shù)大部分都返回apr_status_t類型的錯誤碼,這是一個int型,在apr_errno.h中定義,和它在一起定義的還有apr所用的所有錯誤碼和狀態(tài)碼。

? ? ? ? ? ? ? ? 2) 如何定義錯誤捕捉策略?

? ? ? ? ? ? ? ? ? ?由于APR是可移植的,這樣就可能遇到這樣一個問題:不同平臺錯誤碼的不一致。如何處理呢?APR給我們提供了2中策略:

? ? ? ? ? ? ? ? ? ? ?a) 跨多平臺返回相同的錯誤碼

? ? ? ? ? ? ? ? ? ? ?b)返回平臺相關(guān)錯誤碼,如果需要將它轉(zhuǎn)換為通用錯誤碼

? ? ? ? ? ? ? ? ? ? ? ? ?程序的執(zhí)行錄像往往要根據(jù)函數(shù)返回錯誤碼來定,這么做的缺點就是把這些工作推給了程序員。執(zhí)行流程如下:

? ? ? ? ? ? ? ? ? ? ? ? ?make syscall that fails

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?return error code

? ? ? ? ? ? ? ? ? ? ? ?----------------------------------------------------------------

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? convert to common error code (using ap_canonical_error)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? decide execution based on common error code

? ? ? ? ? ? ? ? ? ? ///

? ? ? ? ? ? ? ? ? ? 注1 調(diào)用約定

? ? ? ? ? ? ? ? ? ? ? ? ? ?我們知道函數(shù)調(diào)用是通過棧操作來完成的,在棧操作過程中需要函數(shù)的調(diào)用者和被調(diào)用者在下面的兩個問題上作出協(xié)調(diào),達成協(xié)議:

? ? ? ? ? ? ? ? ? ? ? ? ? ?a) 當參數(shù)個數(shù)多余一個時,按照什么順序把參數(shù)壓入堆棧

? ? ? ? ? ? ? ? ? ? ? ? ? ?b) 函數(shù)調(diào)用后,由誰來把堆棧回復(fù)原來狀態(tài)

? ? ? ? ? ? ? ? ? ? ? ? ? ?在像C/C++這樣的中、高級語言中,使用"調(diào)用約定"來說明這兩個問題。

? ? ? ? ? ? ? ? ? ?///

? ?5.2 APR分析-進程篇

? ? ? ? ?Apache Server的進程調(diào)度一直為人所稱道,Apache 2.0推出的APR對進程進行了封裝,特別是Apache 2.0的MPM(Multiple Precess Management)框架就是以APR封裝的進程為基礎(chǔ)的。

? ? ? ? ? ? ?APR進程封裝源代碼的位置在$(APR_HOME)/threadproc目錄下,本篇blog著重分析unix子目錄下的proc.c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_thread_proc.h。

? ? ? ? ?一、APR進程概述

? ? ? ? ? ? ? APR進程封裝采用了傳統(tǒng)的fork-exec配合方式(spawn),即父進程在fork出子進程后繼續(xù)執(zhí)行其自己的代碼,而子進程調(diào)用exec函數(shù)加載新的程序影響到其地址空間,執(zhí)行新的程序。我們先來看看使用APR創(chuàng)建一個新的進程的流程,然后再根據(jù)流程做細節(jié)分析:

apr_proc_t newproc; apr_pool_t *p; apr_status_t rv; const char *args[2]; apr_procattr_t *attr; /* 初始化APR內(nèi)部使用的內(nèi)存*/ rv = apr_pool_initialize(); HANDLE_RTVAL(apr_pool_initialize, rv); rv = apr_pool_create(&p, NULL); HANDLE_RTVAL(apr_pool_create, rv); /*創(chuàng)建并初始化新進程的屬性*/ rv = apr_procattr_create(&attr, p); HANDLE_RTVAL(apr_procattr_create, rv); rv = apr_procattr_io_set(attr, APR_FULL_BLOCK, APR_FULL_BLOCK, APR_NO_PIPE); /*可選*/ HANDLE_RTVAL(apr_procattr_io_set, rv); rv = apr_procattr_dir_set(attr, "startup_path");/*可選*/ HANDLE_RTVAL(apr_procattr_dir_set, rv); rv = apr_procattr_cmdtype_set(attr, APR_PROGRAM);/*可選*/ HANDLE_RTVAL(apr_procattr_cmdtype_set, rv); ... .../*其他設(shè)置進程屬性的函數(shù)*/ /*創(chuàng)建新進程*/ args[0] = "proc_child"; args[1] = NULL; rv = apr_proc_create(&newproc,"your_progname", args, NULL, attr, p); HANDLE_RTVAL(apr_proc_create, rv); /*等待子進程結(jié)束*/ rv = apr_proc_wait(&newproc, NULL, NULL, APR_WAIT); HANDLE_RTVAL(apr_proc_wait, rv);

? ? ? ? ? ?二、APR procattr創(chuàng)建

? ? ? ? ? ? ? ? ?在我們平時的Unix進程相關(guān)編程時,我們大致會接觸兩類進程操作函數(shù):進程創(chuàng)建函數(shù)(如fork和exec等)和進程屬性操作函數(shù)(getpid、chdir等),APR將進程的相關(guān)屬性信息封裝到apr_procattr_t結(jié)構(gòu)體中,我們來看看這個重要的結(jié)構(gòu)體定義:(這里只列出Unix下可用的屬性)

/* in $(APR_HOME)/include/arch/unix/apr_arch_threadproc.h */ struct apr_procattr_t {/* PART 1 */apr_pool_t *pool;/* PART 2 */apr_file_t *parent_in;apr_file_t *child_in;apr_file_t *parent_out;apr_file_t *child_out;apr_file_t *parent_err;apr_file_t *child_err;/* PART 3 */char *currdir;apr_int32_t cmdtype;apr_int32_t detached;/* PART 4 */struct rlimit *limit_cpu;struct rlimit *limit_mem;struct rlimit *limit_nproc;struct rlimit *limit_nofile;/* PART 5 */apr_child_errfn_t *errfn;apr_int32_t errchk;/* PART 6 */apr_uid_t uid;apr_gid_t gid; };

? ? ? ?我這里講apr_procattr_t 包含的字段大致分為6部分,下面逐一說明:

? ? ? ? [PART 1]

? ? ? ? 在上一篇關(guān)于APR的blog中說過,大部分的APR類型中都會有一個apr_pool_t 類型字段,用于APR內(nèi)部的內(nèi)存管理,此結(jié)構(gòu)也無例外。該字段用來標識procattr在哪個pool中分配的內(nèi)存。

? ? ? ? [PART 2]

? ? ? ? 進程不是孤立存在的,進程也是由父有子的。父子進程間通過傳統(tǒng)的匿名pipe進行通信。在apr_procattr_io_set(attr,APR_FULL_BLOCK,APR_FULL_BLOCK,APR_FULL_BLOCK)調(diào)用后,我們可以用下面的圖來表示這些字段的狀態(tài):

parent_in----------------------------------------------------------filedes[0] "in_pipe" filedes[1]------------------------ chiild_in ----- parent_out---------------------------filedes[0] "out_pipe" filedes[1]------------------------ child_out ---------------------- parent_err ---------------------------filedes[0] "err_pipe" filedes[1]------------------------ child_err ---------------------------

? ? ? ? 還有一點指的注意的是apr_procattr_io_set調(diào)用apr_file_pipe_create創(chuàng)建pipe的時候,為相應(yīng)的in/out字段注冊了cleanup函數(shù)apr_unix_file_cleanup, apr_unix_file_cleanup在相應(yīng)的in/out字段的pool銷毀時被調(diào)用,在后面的apr_proc_create時還會涉及到這塊。

? ? ? ? [PART 3]

? ? ? ? 進程的一些常規(guī)屬性。

? ? ? ? currdir標識新進程啟動時的工作路徑(執(zhí)行路徑),默認時為何父進程相同;

? ? ? ? cmdtype標識新的子進程將執(zhí)行什么類型的命令;共5種類型,默認為APR_PROGRAM

? ? ? ? detached標識新進程是否為分離后臺進程,默認為前臺進程。

? ? ? ? [PART 4]

? ? ? ? 這4個字段標識平臺對進程資源的限制,一般我們接觸不到。struct rlimit的定義在/usr/include/sys/resource.h中。

? ? ? ? [PART 5]?

? ? ? ? errfn為一函數(shù)指針,原型為typedef void (apr_child_errfn_t)(apr_pool_t *proc, apr_status_t err, const char *description);這個函數(shù)指針如果被賦值,那么當子進程遇到錯誤退出前將調(diào)用該函數(shù)。

? ? ? ? ? ? errchk一個標志值,用于告知apr_proc_create是否對子進程屬性進行檢查,如檢查curdir的access屬性等。

? ? ? ? [PART 6]

? ? ? ? ? ? 用戶ID和組ID,用于檢索允許該用戶所使用的權(quán)限。

? ? ? ? 三、APR proc創(chuàng)建

? ? ? ? ? ? APR proc的描述結(jié)構(gòu)為apr_proc_t:

typedef struct apr_proc_t {/** The process ID */pid_t pid;/** Parent's side of pipe to child's stdin */apr_file_t *in;/** Parent's side of pipe to child's stdout */apr_file_t *out;/** Parent's side of pipe to child's stderr*/apr_file_t *err; } apr_proc_t;

? ? ? ? ? ? 創(chuàng)建一個新的進程的接口為apr_proc_create,其參數(shù)也都很簡單。前面說過apr_proc_create先fork出一個子進程,眾所周知fork后子進程是父進程的復(fù)制品,然后子進程再通過exec函數(shù)加載新的程序映像,并開始執(zhí)行新的程序。這里分析一下apr_proc_create的執(zhí)行流程,其偽碼如下:

apr_proc_create {if (attr->errchk)對attr做有效性檢查,讓錯誤盡量發(fā)生在parentprocess中,而不是留給child process; ---(1)fork子進程;{ /*在子進程中*/清理一些不必要的從父進程繼承下來的描述符等,為exec提供一個"干凈的"的環(huán)境 ---(2)關(guān)閉attr->parent_int、parent_out和parent_err,并分別重定向attr->child_in、child_out和child_err為STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO; ---(3)判斷attr->cmdtype,選擇執(zhí)行exec函數(shù); --(4)}/* 在父進程中*/關(guān)閉attr->child_in、child_out和child_err; }

? ? ? ?下面針對上述偽碼進行具體分析:

? ? ? ? 1)有效性檢查

? ? ? ? ? ? attr->errchk屬性可以通過apr_procattr_error_check_set函數(shù)在apr_proc_create之前設(shè)置。一旦設(shè)置,apr_proc_create就會在fork子進程前對procattr的有效性進行檢查,比如attr->curdir的訪問屬性(利用access檢查)、progname文件的訪問權(quán)限檢查等。這些的目的就是一個"讓錯誤發(fā)生在fork前,不要等到在子進程中出錯"。

? ? ? ? 2) 清理"不必要的"繼承物

? ? ? ? ? ? ? ? ? ?由于子進程復(fù)制了父進程的地址空間,隨之而來的還包含一些"不必要"的"垃圾"。為了給exec提供一個"干凈的"環(huán)境,在exec之前首先要做一下必要的清理,APR使用apr_pool_cleanup_for_exec來完成這項任務(wù)。apr_pool_cleanup_for_exec做了哪些工作呢?apr_pool_cleanup_for_exec通過pool內(nèi)部的global_pool搜索其子節(jié)點,并逐一遞歸cleanup,這里的cleanup并不釋放任何內(nèi)存,也不flushI/O Buffer,僅是調(diào)用節(jié)點注冊的相關(guān)cleanup函數(shù),這里我們可以回顧一下apr_procattr_io_set調(diào)用,在創(chuàng)建相關(guān)pipe時就為相應(yīng)的in/out/err描述符注冊了cleanup函數(shù)。同樣就是因為這點,子進程再調(diào)用apr_pool_cleanup_for_exec之前,首先要kill掉(這里理解就是去掉相關(guān)文件描述符上的cleanup注冊函數(shù))這些注冊函數(shù)。防止相關(guān)pipe的描述符被意外關(guān)閉。

? ? ? ? 3) 建立起與父進程"對話通道"

? ? ? ? ? ?父進程在創(chuàng)建procattr時就建立了若干個pipe,fork后子進程繼承了這些。為了關(guān)掉一些不必要的描述符和更好的和父進程通訊,子進程作了一些重定向的工作,這里用圖來表示重定向前后的差別:(圖中顯示的是子進程關(guān)閉parent_in/out/err三個描述符后的文件描述符表)

? ? ? ? ? ? 重定向前:

? ? ? ? ? ? ? ?子進程文件描述符

? ? ? ? ? ? ? ? ? ?----------------------------|

? ? ? ? ? ? ? ? ? ?[0] STDIN_FILENO|

? ? ? ? ? ? ? ? ? ?-----------------------------|

? ? ? ? ? ? ? ? ? ?[1] STDOUT_FILENO

? ? ? ? ? ? ? ? ? ?-----------------------------|

? ? ? ? ? ? ? ? ? ?[2] STDERR_FILENO

? ? ? ? ? ? ? ? ? ?------------------------------|

? ? ? ? ? ? ? ? ? ?[3] child_in.fd |----> in_pipe的filedes[0]

? ? ? ? ? ? ? ? ? ?-------------------------|

? ? ? ? ? ? ? ? ? ?[4] child_out.fd|--->out_pipe的filedes[1]

? ? ? ? ? ? ? ? ? ?-------------------------|

? ? ? ? ? ? ? ? ? ?[5] child_err.fd|--->err_pipe的filedes[1]

? ? ? ? ? ? ? ? ? ?----------------------|

? ? ? ? ? ? ? ? ? ?重定向后:

? ? ? ? ? ? ? ? ? ?-----------|

? ? ? ? ? ? ? ? ? ?[0] child_in.fd |--->in_pipe的filedes[0]

? ? ? ? ? ? ? ? ? ?-----------|

? ? ? ? ? ? ? ? ? ?[1]child_out.fd |--->out_pipe的filedes[1]

? ? ? ? ? ? ? ? ? ?-----------|

? ? ? ? ? ? ? ? ? ?[2]child_err.fd |---->err_pipe的filedes[1]

? ? ? ? ? ? ? ? ? ? -----------|

? ? ? ? ? ? ? ? ? ?為了能更好的體現(xiàn)出"對話通道"的概念,這里再畫出父進程再關(guān)閉attr->child_in、child_out和child_err后的文件描述表:

? ? ? ? ? ? ? ? ? ? ?父進程文件描述表

? ? ? ? ? ? ? ? ? ? ?-----------------|

? ? ? ? ? ? ? ? ? ? ?[0] STDIN_FILENO |

? ? ? ? ? ? ? ? ? ? ?-----------------|

? ? ? ? ? ? ? ? ? ? ?[1] STDOUT_FILENO |

? ? ? ? ? ? ? ? ? ? ?------------------|

? ? ? ? ? ? ? ? ? ? ?[2] STDERR_FILENO |

? ? ? ? ? ? ? ? ? ? ? ------------------|

? ? ? ? ? ? ? ? ? ? ? [3]parent_in.fd | ---->in_pipe的filedes[1]

? ? ? ? ? ? ? ? ? ? ? -------------------|

? ? ? ? ? ? ? ? ? ? ? [4] parent_out.fd |---->out_pipe的filedes[0]

? ? ? ? ? ? ? ? ? ? ? -------------------|

? ? ? ? ? ? ? ? ? ? ? [5] parent_err.fd |---->err_pipe的filedes[0]

? ? ? ? ? ? ? ? ? ? ? -------------------|

? ? ? ? 4) 啟動新的程序

? ? ? ? ? ? 根據(jù)APR proc的設(shè)計,子進程在被fork出來后,將根據(jù)procattr的cmdtype等屬性信息決定調(diào)用哪種exec函數(shù)。當子進程調(diào)用一種exec函數(shù)時,子進程將完全由新程序代換,而新程序則從其main函數(shù)開始執(zhí)行(與fork不同,fork返回后子進程從fork點開始往下執(zhí)行)。因為調(diào)用exec并不創(chuàng)建新進程,所以前后的進程ID并未改變。exec只是用另一個新程序替換了當前進程的正文、數(shù)據(jù)、堆和棧段。

? ? ? ? 四、總結(jié)

? ? ? ? ? ?xx_in/xx_out都是相對于child process來說的,xx_in表示通過該描述符child process從in_pipe讀出parent process寫入in_pipe的數(shù)據(jù);xx_out表示通過該描述符child process將數(shù)據(jù)寫入out_pipe供parent process使用;xx_err則是child process將錯誤信息寫入err_pipe供parent process使用。

? ? ? ? ? ?fork后子進程和父進程的同和異

? ? ? ? ? ? ? 同:

? ? ? ? ? ? ? ?--父進程已經(jīng)打開的文件描述符;

? ? ? ? ? ? ? ?--實際用戶ID、實際組ID、有效用戶ID、有效組ID;

? ? ? ? ? ? ? ?--添加組ID;

? ? ? ? ? ? ? ?--進程組ID;

? ? ? ? ? ? ? ?--對話期ID;

? ? ? ? ? ? ? ?--控制終端;

? ? ? ? ? ? ? ?--設(shè)置用戶ID標志和設(shè)置組ID標志;

? ? ? ? ? ? ? ?--當前工作目錄;

? ? ? ? ? ? ? ?--根目錄;

? ? ? ? ? ? ? ?--文件方式創(chuàng)建屏蔽字;

? ? ? ? ? ? ? ?--信號屏蔽和排列;

? ? ? ? ? ? ? ?--對任一打開文件描述符的在執(zhí)行時關(guān)閉標志;

? ? ? ? ? ? ? ?--環(huán)境;

? ? ? ? ? ? ? ? --連接的共享存儲段;

? ? ? ? ? ? ? ? --資源限制.

? ? ? ? ? ? ? ?異:

? ? ? ? ? ? ? ? ?--fork的返回值;

? ? ? ? ? ? ? ? ?--進程ID;

? ? ? ? ? ? ? ? ? --不同的父進程ID;

? ? ? ? ? ? ? ? ?--子進程的tms_utime,tms_stime,tms_cutime以及tme_ustime設(shè)置為0;

? ? ? ? ? ? ? ? ?--父進程設(shè)置的鎖,子進程不繼承;

? ? ? ? ? ? ? ? ? --子進程的未決告警被清除;

? ? ? ? ? ? ? ? ? ?--子進程的未決信號集設(shè)置為空集

? 5.3 APR分析-內(nèi)存篇

? ? ? ? 內(nèi)存管理一直是讓C程序員頭痛的問題,作為一個通用接口集,APR當然也提供其自己的內(nèi)存管理接口--APR Pool。APR Pool作為整個APR的一個基礎(chǔ)功能接口,直接影響著APR的設(shè)計風(fēng)格。

? ? ? ? APR Pool源代碼的位置在$(APR_HOME)/memory目錄下,本篇blog著重分析unix子目錄下的apr_pools.c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_pools.h;在apr_pools.c中還實現(xiàn)了負責(zé)APR內(nèi)部內(nèi)存分配的APRallocator的相關(guān)操作接口(APR allocator相關(guān)頭文件為$(APR_HOME)/include/apr_allocator.h)。

? ? ? ? 一、APR Pool概述

? ? ? ? ? ? ?我們平時常用的內(nèi)存管理方式都是基于"request-style"的,即分配所請求大小的內(nèi)存,使用之,銷毀之。而APR Pool的設(shè)計初衷是為Complex Application提供良好的內(nèi)存管理接口,其使用方式與"request-style"有所不同。而$(APR_HOME)/docs/pool-design.htm文檔中,設(shè)計者道出了"使用好"APR Pool的幾個Rules,同時也從側(cè)面反映出APRPool的設(shè)計。

? ? ? ? ? ? ?1.任何Object都不應(yīng)該有自己的Pool,它應(yīng)在其構(gòu)造函數(shù)的調(diào)用者的Pool中分配。因為一般調(diào)用者知道該Object的聲明周期,并通過Pool管理之。也就說Object無須自己調(diào)用"Close" or "Free",這些操作在Object所在Pool被摧毀時被隱式調(diào)用的。

? ? ? ? ? ? ? 2.函數(shù)無須為了他們的行為而去Create/Destroy Pool,它們應(yīng)該使用它們調(diào)用者傳給它們的Pool。

? ? ? ? ? ? ? 3.為了防止內(nèi)存無限制的增長,APR Pool建議當遇到unbounded iteration時使用sub_pool,標準格式如下:

subpool=apr_pool_create(pool,NULL); for(i=0;i<n;++i) {apr_pool_clear(subpool);... ...do_operation(..., subpool); } apr_pool_destroy(subpool);

? ? ? ? ? ?二、深入APR Pool

? ? ? ? ? ? ? ?1.分析apr_pool_initialize

? ? ? ? ? ? ? ? ? 任何使用APR的應(yīng)用程序一般都會調(diào)用apr_app_initalize來初始化APR的內(nèi)部使用的數(shù)據(jù)結(jié)構(gòu),查看一下app_app_initialize的代碼,你會發(fā)現(xiàn)apr_pool_initialize在被apr_app_initialize調(diào)用的apr_initialize中被調(diào)用,該函數(shù)用來初始化使用Pool所需的內(nèi)部結(jié)構(gòu)(用戶無須直接調(diào)用apr_pool_initialize,在apr_app_initialize時它被自動調(diào)用,而apr_app_initailize又是APR? program調(diào)用的第一個function,其在apr_general.h中聲明,在misc/unix/start.c中實現(xiàn))。

? ? ? ? ? ? ? ? ?apr_pool_initialize {

? ? ? ? ? ? ? ? ? ? ? 如果(!apr_pools_initialized) {

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?創(chuàng)建global_allocator;? ?------(1)

? ? ? ? ? ? ? ? ? ? ? ?}


? ? ? ? ? ? ? ? ? ? ? ? 創(chuàng)建global_pool; ------(2)

? ? ? ? ? ? ? ? ? ? ? ? 給global_pool起名為"apr_global_pool";

? ? ? ? ? ? ? ? ? }

? ? ? ? ? ? ? ? ? ? ? (1) Pool和Allocator

? ? ? ? ? ? ? ? ? ? ? ? ? ?每個Pool都有一個allocator相伴,這個allocator可能是Pool自己的,也可能是其ParentPool的。allocator的結(jié)構(gòu)如下:

/* in apr_pools.c*/ struct apr_allocator_t {apr_uint32_t max_index;apr_uint32_t max_free_index;apr_uint32_t current_free_index;... ...apr_pool_t *owner;apr_memnode_t *free[MAX_INDEX]; }; ? ? ? ? ?在(1)調(diào)用后,global_allocator的所有xx_index字段都為0,owner-->NULL,free指針數(shù)組中的指針也都-->NULL。這里的index是大小的級別,這里最大級別為20(即MAX_INDEX=20),free指針數(shù)組中free[0]所指的node大小為MIN_ALLOC大小,即8192,即2的13次冪。按此類推free[19]所指的node大小應(yīng)為2的32次冪,即4G byte。allocator_alloc中是通過index=(size >> BOUNDARY_INDEX) - 1來得到這一index的。allocator維護了一個index不同的memnode池,每一index級別上又有一個

memnode list,以后用戶調(diào)用apr_palloc分配size大小內(nèi)存時,allocator_alloc函數(shù)就會在free memnode池中選和要尋找的size的index級別相同的memnode,而不是重新malloc一個size大小的memnode。另外要說明一點的是APR Pool中所有ADT中的xx_index字段都是大小級別的概念。

? ? ? ? ? ?(2) 創(chuàng)建global_pool

? ? ? ? ? ? ? 在APR Pool初始化的時候,唯一創(chuàng)建一個Pool --global_pool。apr_pool_t的非Debug版本如下:

/* in apr_pools.c */ struct apr_pool_t {apr_pool_t *parent;apr_pool_t *child;apr_pool_t *sibling;apr_pool_t **ref;cleanup_t *cleanups;cleanup_t *free_cleanups;apr_allocator_t *allocator;struct process_chain *subprocesses;apr_abortfunc_t abort_fn;apr_hash_t *user_data;const char *tag;apr_memnode_t *active;apr_memnode_t *self; /*The nodecontaining the pool itself */char *self_first_avail;... ... }

? ? ? ? ? ? ? 而apr_memnode_t的結(jié)構(gòu)如下:

/* in apr_allocator.h */ struct apr_memnode_t {apr_memnode_t *next; /*next memnode */apr_memnode_t **ref; /*reference to self*/apr_uint32_t index; /*size*/apr_uint32_t free_index; /*how much free*/char *first_avail; /*pointer to first free memory*/char *endp; /*pointer to end of free memory */ };

? ? ? ? ? ? ? apr_pool_create_ex首先通過allocator尋找合適的node用于創(chuàng)建Pool,但由于global_allocator尚未分配過任何node,所以global_allocator創(chuàng)建一個新的node,該node大小為MIN_ALLOC(即8192),該node的當前狀態(tài)如下:

node-->|----------|0| || ||----------|APR_MEMNODE_T_SIZE <----node->first_avail| || || ||----------size(一般為8192)<---node->endp

? ? ? ? ? ? ?其他屬性值如下:

? ? ? ? ? ? ?node->next = NULL;

? ? ? ? ? ? ?node->index = (APR_UINT32_TRUNC_CAST)index; /*這里為1*/

? ? ? ? ? ? ?創(chuàng)建完node后,我們將在該node上的avail space劃分出我們的global_pool來。劃分后狀態(tài)如下(pool與node關(guān)系):

node-->|----------|0 <---pool->self=pool_active| || ||-------------|APR_MEMNODE_T_SIZE <------global_pool| || ||--------------|APR_MEMNODE_T_SIZE+SIZEOF_POOL_T <------node->first_avail=pool->self_first_avail| || ||------------size(一般為8192) <------------node->endp

? ? ? ? ? ? ?pool其他一些屬性值(pool與pool之間關(guān)系)如下:

pool->allocator=global_allocator; pool->child = NULL; pool->sibling = NULL; pool->ref = NULL;

? ? ? ? ? ? ? ? ?2.APR Sub_Pool創(chuàng)建(pool與pool之間關(guān)系)

? ? ? ? ? ? ? ? ? 上面我們已經(jīng)初始化了global_pool,但是global_pool是不能直接拿來就用的,我們需要創(chuàng)建其sub_pool,也就是用戶自己的pool。一般創(chuàng)建user的sub_pool我們都使用apr_pool_create宏,它只需要2個參數(shù),并默認sub_pool繼承parent_pool的allocator和abort_fn。在apr_pool_create內(nèi)部調(diào)用的還是apr_pool_create_ex函數(shù)。我們來看一下創(chuàng)建sub_pool后pool之間的關(guān)系:

? ? ? ? ? ? ? ? ? 例:

? ? ? ? ? ? ? ? ? ?static apr_pool_t *sub_pool = NULL;

? ? ? ? ? ? ? ? ? ?apr_pool_create(&sub_pool, NULL);

? ? ? ? ? ? ? ? ? ?這里sub_pool的創(chuàng)建過程與global_pool相似,也是先創(chuàng)建其承載體node,然后設(shè)置相關(guān)屬性,使其成為global_pool的child_pool。創(chuàng)建完后global_pool和該sub_pool的關(guān)系如下圖:

global_pool <------/ ----->sub_pool ---------- // -------- sibling --->NULL /--------parent ---------- / -------- child--------------/ sibling--->NULL -------- ---------child--->NULL---------

? ? ? ? ? ? ? ? ?APR Pool是按照二叉樹結(jié)構(gòu)組織的,并采用"child-sibling"的鏈式存儲方式,global_pool作為整個樹的Root Node。

? ? ? ? ? ? ? ? ?3.從pool中分配內(nèi)存

? ? ? ? ? ? ? ? ? ? 上面我們已經(jīng)擁有了一個sub_pool,我們現(xiàn)在就可以從sub_pool中分配內(nèi)存了。APR提供了函數(shù)apr_palloc來做這件事情。

? ? ? ? ? ? ? ? ? ? ? 例如:apr_alloc(sub_pool,wanted_mem_size);

? ? ? ? ? ? ? ? ? ? ? ?apr_palloc在真正分配內(nèi)存前會把wanted_mem_size做一下處理。它使用APR_ALIGN_DEFAULT宏處理wanted_mem_size得到一個圓整到8的new_size,然后再在pool中分配new_size大小的內(nèi)存,也就是說pool中存在的用戶內(nèi)存塊的大小都是8的倍數(shù)。舉個例子,如果wanted_mem_size=30,apr_alloc實際會在pool中劃分出32個字節(jié)的空間。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? apr_palloc的工作流程簡單描述如下:

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?a) 如果在pool->active node的avail space足夠滿足要申請的內(nèi)存大小size時,則直接返回active->first_avail,并調(diào)整active->first_avail= active->first_avail+size;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?b)如果a)不滿足,則查看active->next這個node滿足與否;如果滿足則將返回所要內(nèi)存,并將該node設(shè)為active node,將以前的active node放在新active node的next位置上;

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? c)如果b)也不滿足,則新創(chuàng)建一個memnode,這個node可能為新創(chuàng)建的,也可能是從allocator的free memnode池中取出的,取決于當時整個Pool的狀態(tài)。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 從上面我們也可以看出node分為2類,一種是作為pool的承載體,但pool結(jié)構(gòu)的空間不足以完全占滿一個node,所以也可以用來分配用戶內(nèi)存;另一種就是完全用于分配用戶內(nèi)存的了。每個pool有一個node list,當然這個list中包括它自己所在的node了。

? ? ? ? ? ? ? ? ? 4.apr_pool_clear和apr_pool_destroy

? ? ? ? ? ? ? ? ? ? 創(chuàng)建和分配結(jié)束后,我們需要clear或者destroy掉Pool。

? ? ? ? ? ? ? ? ? ? clear和destroy的區(qū)別在于clear并不真正free內(nèi)存,只是清理便于以后alloc時重用,而destroy則是真正的free掉內(nèi)存了。

? 5.4 APR分析-信號篇

? ? ? ? ?信號是Unix的重要系統(tǒng)機制。

? ? ? ? ?一、信號介紹

? ? ? ? ? ? ? ?1.Signal的引入用來進行User Mode進程間的交互,系統(tǒng)內(nèi)核也可以利用它通知User Mode進程發(fā)生了哪些系統(tǒng)事件。從最開始引入到現(xiàn)在,信號只是做了很小的一些改動(不可靠信號模型到可靠信號模型).

? ? ? ? ? ? ? ? ?2.信號服務(wù)于兩個目的:

? ? ? ? ? ? ? ? ? ?1) 通知某進程某特定事件發(fā)生了;

? ? ? ? ? ? ? ? ? ?2) 強制其通知進程執(zhí)行相應(yīng)的信號處理程序。

? ? ? ? ? 二、基礎(chǔ)概念

? ? ? ? ? ? ? ? ? 1.信號的一個特性就是可以在任何時候發(fā)給某一進程,而無需知道該進程的狀態(tài)。如果該進程當前并未處于執(zhí)行態(tài),則該信號被內(nèi)核Save起來,知道該進程恢復(fù)執(zhí)行才傳遞給它;如果一個信號被進程設(shè)置為阻塞,則該信號的傳遞被延遲,直到其阻塞被取消它才被傳遞給進程。

? ? ? ? ? ? ? ? ? ?2.系統(tǒng)內(nèi)核嚴格區(qū)分信號傳送的兩個階段:

? ? ? ? ? ? ? ? ? ? ?1)Signal Generation: 系統(tǒng)內(nèi)核更新目標進程描述結(jié)構(gòu)來表示一個信號已經(jīng)發(fā)送出去。

? ? ? ? ? ? ? ? ? ? ?2)Signal Delivery:內(nèi)核強制目標進程對信號做出反應(yīng),或執(zhí)行相關(guān)信號處理函數(shù),或改變進程執(zhí)行狀態(tài)。

? ? ? ? ? ? ? ? ? ? ?信號的誕生和傳輸我們可以這樣理解:把信號作為"消費品",其Generation狀態(tài)就是"消費品誕生",其Delivery狀態(tài)就是理解為"被消費了"。這樣勢必存在這樣的一個情況:"消費品誕生了,但是還沒有被消費掉",在信號模型中,這樣的狀態(tài)被稱為"pending"(懸而未決)。

? ? ? ? ? ? ? ? ? ? ? 任何時候一個進程只能有一個這樣的某類型的pending信號,同一進程的其他同類型的pending信號將不排隊,將被簡單的discard(丟棄)掉。

? ? ? ? ? ? ? ? ? ?3.如何消費一個signal

? ? ? ? ? ? ? ? ? ? ? 1) 忽略該信號;

? ? ? ? ? ? ? ? ? ? ? 2)響應(yīng)該信號,執(zhí)行一特定的信號處理函數(shù);

? ? ? ? ? ? ? ? ? ? ? 3)響應(yīng)該信號,執(zhí)行系統(tǒng)默認的處理函數(shù)。包括:Terminate、Dump、Ignore、Stop、Continue等。

? ? ? ? ? ? ? ? ? ? ? 這里有特殊:SIGKILL和SIGSTOP兩個信號不能忽略、不能捕捉、不能阻塞,而只是執(zhí)行系統(tǒng)默認處理函數(shù)。

? ? ? ? ? ?三、APR Signal封裝

? ? ? ? ? ? ? ? ?APR Signal源代碼的位置在$(APR_HOME)/threadproc目錄下,本篇blog著重分析unix子目錄下的signals.c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_signal.h。

? ? ? ? ? ? ? ? ? ?1.apr_signal函數(shù)

? ? ? ? ? ? ? ? ? ? ? ?早期版本處理方式:進程每次處理信號后,隨機將信號的處理動作重置為默認值。

? ? ? ? ? ? ? ? ? ? ? ?后期版本處理方式:進程每次處理信號后,信號的處理動作不被重置為默認值。

? ? ? ? ? ? ? ? ? ? ? ?我們舉例測試一下:分別在Solaris9、Cygwin和RedHat Linux 9上。

? ? ? ? ? ? ? ? ? ? ? ?例子:

? ? ? ? ? ? ? ? ? ? ? ?eg 1:

void siguser1_handler(int sig); int main(void) {if (signal(SIGUSR1, siguser1_handler) == SIG_ERR) {perror("siguser1_handler error");exit(1);}while(1) {pause();} } void siguser1_handler(int sig) {printf("in siguser1_handler,%d/n", sig); } input:kill -USR1 9122kill -USR1 9122 output(Solaris 9):in siguser1_handler, 16用戶信號1(程序終止) output:(Cygwin and RH9):in siguser1_handler, 30in siguser1_handler, 30.....

? ? ? ? ? ? ? ? ? ? ? ? ? eg.1結(jié)果表示在Solaris 9上,信號的處理仍然按照早期版本的方式,而Cygwin和RH9則都按照后期版本的方式。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ?那么有什么替代signal函數(shù)的辦法么?在最新的X/Open和UNIXspecifications中都推薦使用一個新的信號接口sigaction,該接口采用后期版本的信號處理方式。在《Unix高級環(huán)境編程》中就有使用sigaction實現(xiàn)signal的方法,而APR恰恰也是使用了該方法實現(xiàn)了apr_signal。其代碼如下:

APR_DECLARE(apr_sigfunc_t *) apr_signal(int signo, apr_sigfunc_t *func) {struct sigaction act, oact;act.sa_handler = func;sigemptyset(&act.sa_mask); ----(1)act.sa_flags=0; #ifdef SA_INTERRUPT /* SunOS */act.sa_flags |= SA_INTERRUPT; #endif... ...if (sigaction(signo, &act, &oact) <0) return SIG_ERR;return oact.sa_handler; }

? ? ? ? ? ? ? ? ? ? ? ? (1)這里有一個Signal Set(信號集)的概念,通過相關(guān)函數(shù)操作信號集以改變內(nèi)核傳遞信號給進程時的行為。Unix用sigset_t結(jié)構(gòu)來表示信號集。信號集總是和sigprocmask或sigaction一起使用。

? ? ? ? ? ? ? ? ? ? ?2、apr_signal_block和apr_signal_unblock

? ? ? ? ? ? ? ? ? ? ? ? ? 這兩個函數(shù)分別負責(zé)阻塞和取消阻塞內(nèi)核傳遞某信號給目標進程。其主要利用的就是sigprocmask函數(shù)來實現(xiàn)的。每個進程都有其對應(yīng)的信號屏蔽字,它讓目標進程能夠通知內(nèi)核"哪些傳給我的信號該阻塞,哪些暢通無阻"。

? ? ? ? ? ? ? ? ? ? ? ? ? ?這里想舉例說明的是:如果多次調(diào)用SET_BLOCK的sigprocmask設(shè)置屏蔽字,結(jié)果是什么呢?

? ? ? ? ? ? ? ? ? ? ? ? ?eg.3

int main(void) {sigset_t newmask, oldmask, pendmask;/* 設(shè)置進程信號屏蔽字,阻塞SIGQUIT */sigemptyset(&newmask);sigaddset(&newmask, SIGQUIT);if(sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {perror("SIG_BLOCK error");}printf("1st towait 30 seconds/n");sleep(30);/*第一次查看當前的處于pend狀態(tài)的信號*/if(sigpending(&pendmask) <0) {perror("sigpending error");}if (sigismember(&pendmask, SIGQUIT)) {printf("SIGQUIT pending /n");} else {printf("SIGQUIT unpending/n");}if (sigismember(&pendmask, SIGUSR1)) {printf("SIGUSR1 pending/n");} else {printf("SIGUSR1 unpending/n");}/*重新設(shè)置屏蔽字,阻塞SIGUSR1*/sigemptyset(&newmask);sigaddset(&newmask, SIGUSR1);if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) {perror("SIG_BLOCK error");}printf("2nd to wait 30 seconds/n");sleep(30);/*再次查看當前的處于pend狀態(tài)的信號*/if (sigpending(&pendmask) < 0) {perror("sigpending error");}if (sigismember(&pendmask, SIGQUIT)) {printf("SIGQUIT pending/n");} else {printf("SIGQUIT unpending /n");}if (sigismember(&pendmask, SIGUSR1)) {printf("SIGUSR1 pending/n");} else {printf("SIGUSR1 unpending/n");}exit(0); }//output 1st to wait 30 seconds ^/ SIGQUIT pending SIGUSR1 unpending 2nd to wait 30 seconds --這之后發(fā)送kill -USR128821 SIGQUIT pending SIGUSR1 pending

? ? ? ? ? ? ? ? ? ? ? ?第一次輸出SIGUSR1 unpending是因為并未發(fā)送USR1信號,所以自然為unpending狀態(tài);我想說的是第二次重新sigprocmask時我們僅加入了SIGUSR1,并未顯示假如SIGQUIT,之后查看pending信號中SIGQUIT仍然為pending狀態(tài),這說明兩次SET_BLOCK的sigprocmask調(diào)用是"或"的關(guān)系,第二次SET_BLOCK的sigprocmask調(diào)用不會將第一次SET_BLOCK的sigprocmask調(diào)用設(shè)置的阻塞信號變成非阻塞的。

? 5.5 APR分析-文件IO篇

? ? ? ? ?文件I/O在Unix下占據(jù)著非常重要的地位。APR就是本著這個思想對Unix文件I/O進行了再一次的抽象封裝,以提供更為強大和友善的文件I/O接口。

? ? ? ? ? APR File I/O源代碼的位置在$(APR_HOME)/file_io目錄下,本篇blog著重分析unix子目錄下的相關(guān).c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_file_io.h和apr_file_info.h.

? ? ? ? ? ?一、APR File I/O介紹

? ? ? ? ? ? ? APR用了"不小的篇幅"來"描述"文件I/O,在$(APR_HOME)/file_io/unix目錄下,你會看到多個.c文件,每個.c都是一類文件I/O操作,比如:

? ? ? ? ? ? ? ? open.c --封裝了 文件的打開、關(guān)閉、改名和刪除等操作;

? ? ? ? ? ? ? ? readwrite.c -- 顧名思義,它里面包含了文件的讀寫操作;

? ? ? ? ? ? ? ? pipe.c -- 包含了pipe相關(guān)操作。

? ? ? ? ? ?二、基本APR I/O

? ? ? ? ? ? ? ?APR定義了apr_file_t類型來表示廣義的文件。先來看一下這個核心數(shù)據(jù)結(jié)構(gòu)的"模樣":

/* in apr_arch_file_io.h */ struct apr_file_t {apr_pool_t *pool;int filedes;char *fname;apr_int32_t flags;int eof_hit;int is_pipe;apr_interval_time_t timeout;int buffered;enum {BLK_UNKNOWN, BLK_OFF, BLK_ON } blocking;int ungetchar; /* Last charprovided by an unget op.(-1=no char)*/ #ifndef WAITIO_USES_POLL/* if there is a timeout set, then this pollsetis used */apr_pollset_t *pollset; #endif/* Stuff for buffered mode */char *buffer;int bufpos; /*Read/Write position in buffer */unsigned long dataRead; /* a mount of valid data read into buffer */int direction; /*buffer being used for 0 = read, 1 = write */unsigned long filePtr; /*position in file of handle */ #if APR_HAS_THREADSstruct apr_thread_mutex_t *thlock; #endif };

? ? ? ? ? ? 1.apr_file_open

? ? ? ? ? ? ? ?ANSI C標準庫和Unix系統(tǒng)庫函數(shù)都提供對"打開文件"這個操作語義的支持。他們提供的接口很相似,參數(shù)一般都為"文件名+打開標志位+權(quán)限標志位",apr_file_open也不能忽略習(xí)慣的巨大力量,也提供了類似的接口如下:

? ? ? ? ? ? ? ? APR_DECLARE(apr_status_t) apr_file_open(apr_file_t **new,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?const char *fname,?

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?apr_int32_t flag,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?apr_fileperms_t perm,

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?apr_pool_t *pool);

? ? ? ? ? ? ? ? ?每個封裝都有自定義的一些標志宏,這里也不例外,flag和perm參數(shù)都需要用戶傳入APR自定義的一些宏組合,這里介紹apr_file_open操作:

apr_file_open {"打開標志位"轉(zhuǎn)換; ----(1)"權(quán)限標志位"轉(zhuǎn)換; ----(2)調(diào)用Unix原生API打開文件;設(shè)置apr_file_t變量相關(guān)屬性值; ----(3) }

? ? ? ? ? ? ? ? ? (1)由于上面說了,APR定義了自己的"文件打開標志位",所以在apr_file_open的開始需要將這些專有的"文件打開標志位"轉(zhuǎn)換為Unix平臺通用的"文件打開標志位";

? ? ? ? ? ? ? ? ? ?(2)同(1)理,專有的"權(quán)限標志位"需要轉(zhuǎn)換為Unix平臺通用的"權(quán)限標志位";

? ? ? ? ? ? ? ? ? (3) APR file I/O封裝支持非阻塞I/O帶超時等待以及緩沖I/O,默認情況下為阻塞的,是否緩沖可通過"文件打開標志位"設(shè)置。一旦設(shè)置為緩沖I/O,則apr_file_open會在pool中開辟大小為APR_FILE_BUFSIZE(4096)的緩沖區(qū)供使用。

? ? ? ? ? ? ? 2.apr_file_read/apr_file_write

? ? ? ? ? ? ? ? ?該兩個接口的看點是其緩沖區(qū)管理(前提:在apr_file_open該文件時指定了是Buffer I/O及非阻塞I/O 帶超時等待)。還有一點就是通過這兩個借口的實現(xiàn)我們可以了解到上面提到的apr_file_t 中某些"晦澀"字段的真正含義。

? ? ? ? ? ? ? ? ? (1) 帶緩沖I/O

? ? ? ? ? ? ? ? ? ? 這里的緩沖是APR自己管理的,帶緩沖的好處很簡單,即減少直接操作文件的次數(shù),提高I/O性能。要知道無論lseek還是read/write都是很耗時的,盡可能的減少直接I/O操作次數(shù),會帶來性能上明顯改善。

? ? ? ? ? ? ? ? ? ?讀寫切換:如果先讀后寫,則每次寫的時候都要重新定位文件指針到上次讀的結(jié)尾處;如果先寫后讀,則每次讀前都要flush緩沖區(qū)。

? ? ? ? ? ? ? ? ? ?(2) 非阻塞I/O帶超時等待

? ? ? ? ? ? ? ? ? ? ?這里分析下面一段apr_file_read的代碼:

do {rv = read(thefile->filedes, buf, *nbytes); }while(rv == -1 && errno == EINTR); ---(a) #ifdef USE_WAIT_FOR_IOif (rv == -1 &&(errno == EAGAIN || errno == EWOULDBLOCK) &&thefile->timeout != 0) {apr_status_t arv = apr_wait_for_io_or_timeout(thefile, NULL, 1); ----(b)if (arv != APR_SUCCESS) {*nbytes = bytes_read;return arv;}else {do {rv = rad(thefile->filedes, buf, *nbytes);}while (rv == -1 && errno == EINTR);}} #endif

? ? ? ? ? ? ? ? ? ? (a) 第一個do-while塊:之所以使用do-while塊是為了當read操作被信號中斷后重啟read操作;

? ? ? ? ? ? ? ? ?(b) 一旦文件描述符設(shè)為非阻塞,(a)則瞬間返回,一旦(a)并未讀出數(shù)據(jù),則rv = -1并且errno被設(shè)置為errno = EAGAIN,這時開始帶超時的等待該文件描述符I/O就緒。這里的apr_wait_for_io_or_timeout使用了I/O的多路 復(fù)用技術(shù)Poll,在后面的APR分析中會詳細理解之。apr_file_t中的timeout字段就是用來做超時等待的。

? ? ? ? ? ? ?3.apr_file_close

? ? ? ? ? ? ? ? 該接口主要完成的工作為刷新緩沖區(qū)、關(guān)閉文件描述符、刪除文件(如果設(shè)置了APR_DELONCLOSE標志位)和清理Pool中內(nèi)存的工作。

5.6 APR分析-高級IO篇

? ? ?一、記錄鎖或(區(qū)域鎖)

? ? ? ? ? ? 我見過的對記錄鎖講解最詳細的書就是《Unix高級環(huán)境編程》,特別是關(guān)于進程、文件描述符和記錄鎖三者之間的關(guān)系的講解更是讓人受益匪淺。

? ? ? ? ? ? 關(guān)于記錄鎖的自動繼承和釋放有三條規(guī)則:

? ? ? ? ? ? (1) 鎖與進程、文件兩方面有關(guān)。這有兩重含義:第一重很明顯,當一個進程終止時,它所建立的鎖全部釋放;第二重意思就不很明顯,任何時候關(guān)閉一個描述符時,則該進程通過這一描述符可以存訪的文件上的任何一把鎖都被釋放(這些所都是該進程設(shè)置的)。

? ? ? ? ? ? ?(2) 由fork產(chǎn)生的子程序不繼承父進程所設(shè)置的鎖。這意味著,若一個進程得到一把鎖,然后調(diào)用fork,那么對于父進程獲得的鎖來說,子進程被視為另一個進程,對于從父進程處繼承過來的任一描述符,子進程要調(diào)用fcntl以獲得它自己的鎖。這與鎖的作用是一致的。鎖的作用是阻止多個進程同時寫同一個文件(或同一文件區(qū)域)。如果子進程繼承父進程的鎖,則父、子進程就可以同時寫同一個文件。

? ? ? ? ? ? ?(3) 在執(zhí)行exec后,新程序可以繼承原執(zhí)行程序的鎖。

? ? ? ? ? ? ? ? ? APR記錄鎖源碼位置在$(APR_HOME)/file_io/unix目錄下flock.c,頭文件仍然是apr_file_io.h。apr_file_lock和apr_file_unlock僅提供對整個文件的枷鎖和解鎖,而并不支持對文件中任意范圍數(shù)據(jù)的加鎖與解鎖。至于該鎖是建議鎖(advisory lock)還是強制鎖(mandatory lock),需要看具體的平臺實現(xiàn)了。兩個函數(shù)均利用fcntl實現(xiàn)記錄鎖功能。代碼中有一處值得借鑒:

while((rc = fcntl(thefile->filedes, fc, &l)) < 0 && errno == EINTR)continue;

? ? ? ? ? ? ? ? ? ? ? ?這么做的原因就是考慮到fcntl的調(diào)用可能被某信號中斷,一旦中斷我們要去重啟fcntl函數(shù)。

? ? ? ? ?二、I/O多路復(fù)用

? ? ? ? ? ? ?在經(jīng)典的《Unix網(wǎng)絡(luò)編程第1卷》 Chapter 6中作者詳細介紹了五種I/O模型,分別為:

? ? ? ? ? ? ?- blocking I/O

? ? ? ? ? ? ?- nonblocking I/O

? ? ? ? ? ? ?- I/O multiplexing (select and poll)

? ? ? ? ? ? ?- signal driven I/O(SIGIO)

? ? ? ? ? ? ?- asynchronous I/O (the POSIX aio_functions)

? ? ? ? ? ? ?這里所說的I/O多路復(fù)用就是第三種模型,它既解決了Blocking I/O數(shù)據(jù)處理不及時,又解決了Non-Blocking I/O采用輪詢的CPU浪費問題,同時它與異步I/O不同的是它得到了各大平臺的廣泛支持。

? ? ? ? ? ? ? APR I/O多路復(fù)用源碼主要在$(APR_HOME)/poll/unix目錄下的poll.c和select.c中,頭文件為apr_poll.h。APR提供統(tǒng)一的apr_poll接口,但是apr_pollset_t結(jié)構(gòu)定義和apr_poll的實現(xiàn)則根據(jù)宏P(guān)OLLSET_USES_SELECT、POLL_USES_POLL和POLLSET_USES_POLL的定義與否而不同。

? ? ? ? ? ? ? ?在poll的實現(xiàn)下,apr_pollset_t的定義如下:

/* in poll.c */ struct apr_pollset_t {apr_pool_t *pool;apr_uint32_t nelts;apr_uint32_t nalloc;struct pollfd *pollset;apr_pollfd_t *query_set;apr_pollfd_t *result_set; };

? ? ? ? ? ? ? ? 統(tǒng)一的apr_pollfd_t定義如下:

/* in apr_poll.h */ struct apr_pollfd_t {apr_pool_t *p; /*associated pool*/apr_datatype_e desc_type; /*descriptor type*/apr_int16_treqevents; /*requested events*/apr_int16_trtnevents; /*returned events*/apr_descriptordesc; /* @see apr_descriptor*/void *client_data; /*allowsapp to associate context */ };

? 5.7 APR分析-共享內(nèi)存篇

? ? ? ? ? ? ?共享內(nèi)存是一種重要的IPC方式。在項目中多次用到共享內(nèi)存,只是用而并未深入研究。

? ? ? ? ?APR共享內(nèi)存封裝的源代碼的位置在$(APR_HOME)shmem目錄下,本篇blog著重分析unix子目錄下的shm.c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_shm.h.

? ? ? ? ?一、共享內(nèi)存簡單小結(jié)

? ? ? ? ? ? 共享內(nèi)存時最快的IPC方式,因為一旦這樣的共享內(nèi)存段映射到各個進程的地址空間,這些進程間通過共享內(nèi)存的數(shù)據(jù)傳遞就不需要內(nèi)核的幫忙了。Stevens的解釋是"各進程不是通過執(zhí)行任何進入內(nèi)核的系統(tǒng)調(diào)用來傳遞數(shù)據(jù),顯然內(nèi)核的責(zé)任僅僅是建立各進程地址空間與共享內(nèi)存的映射,當然像處理頁面故障這一類的底層活還是要做的"。相比之下,管道和消息隊里交換數(shù)據(jù)時都需要內(nèi)核來中轉(zhuǎn)數(shù)據(jù),速度就相對較慢。

? ? ? ? ?二、APR共享內(nèi)存封裝

? ? ? ? ? ? ? APR提供多種創(chuàng)建共享內(nèi)存的方式,其中最主要的就是apr_shm_create接口,其偽碼如下:? ? ??

apr_shm_create {if (要創(chuàng)建匿名shm) { #if APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON #if APR_USE_SHMEM_MMAP_ZEROxxxx ---------(1) #elif APR_USE_SHMEM_MMAP_ANONxxxx ----------(2) #endif #endif /* APR_USE_SHMEM_MMAP_ZERO || APR_USE_SHMEM_MMAP_ANON */ #if APr_USE_SHMEM_SHMGET_ANONxxxx ----------(3) #endif } else { /* 創(chuàng)建有名shm */ #if APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM #if APR_USE_SHMEM_MMAP_TMPxxxx --------(4) #endif #if APR_USE_SHMEM_MMAP_SHMxxxx ----------(5) #endif #endif /* APR_USE_SHMEM_MMAP_TMP || APR_USE_SHMEM_MMAP_SHM */ #if APR_USE_SHMEM_SHMGETxxxx -----------(6) #endif} }

? ? ? ? ? ? ?其中不同版本Unix創(chuàng)建匿名shmem的做法如下:

? ? ? ? ? ? ?(1) SVR4通過映射"/dev/zero"設(shè)備文件來獲得匿名共享內(nèi)存,其代碼一般為:

fd = open("/dev/zero", ..); ptr = mmap(..., MAP_SHARED, fd, ...);

? ? ? ? ? ? ?(2) 4.4 BSD提供更加簡單的方式來支持匿名共享內(nèi)存(注意標志參數(shù)MAP_XX)

ptr = mmap(..., MAP_SHARED | MAP_ANON, -1, ...);

? ? ? ? ? ? ?(3) System V匿名共享內(nèi)存區(qū)的做法如下:

shmid = shmget(IPC_PRIVATE, ...); ptr = shmat(shmid, ...);

? ? ? ? ? ? ? ? 匿名共享內(nèi)存一般都用于有親緣關(guān)系的進程間的數(shù)據(jù)通訊。由父進程創(chuàng)建共享內(nèi)存,子進程自動繼承下來。由于是匿名,沒有親緣關(guān)系的進程是不能動態(tài)鏈接到該共享內(nèi)存區(qū)的。

? ? ? ? ? ? ? 不同版本Unix創(chuàng)建有名shmem的做法如下:

? ? ? ? ? ? ? (4) 由于是有名的shmem,所以與匿名不同的地方在于用filename替代"/dev/zero"做映射。

fd = open(filename, ...); apr_file_trunc(...); ptr = mmap(..., MAP_SHARED, fd, ...);

? ? ? ? ? ? ? (5) Posix共享內(nèi)存的做法

fd = shm_open(filename, ...); apr_file_trunc(...); ptr = mmap(..., MAP_SHARED, fd, ...);

? ? ? ? ? ? ? ? ? 值得注意的一點就是通過shm_open映射的共享內(nèi)存可以供無親緣關(guān)系的進程共享。apr_file_trunc用于重新設(shè)定共享內(nèi)存對象長度。

? ? ? ? ? ? ? ?(6) System V有名共享內(nèi)存區(qū)的做法如下:

shmkey = ftok(filename, 1); shmid = shmget(shmkey, ...); //相當于open orshm_open ptr = shmat(shmid, ...); //相當于mmap

? ? ? ? ? ? ? ?有名共享內(nèi)存一般都與一個文件相關(guān),該文件映射到共享內(nèi)存段,而不同的進程(包括無親緣關(guān)系的進程)則都映射到該文件以達到目的。在APR中通過apr_shm_attach可以動態(tài)將調(diào)用進程連接到已存在的共享內(nèi)存上,前提是你必須知道該共享內(nèi)存區(qū)的標識,在APr中一律用filename做標識。

? ? ? ? 三、總結(jié)

? ? ? ? ? ? 內(nèi)核架起了多個進程間共享數(shù)據(jù)的紐帶--共享內(nèi)存。通過上面的敘述你會發(fā)現(xiàn)共享內(nèi)存的創(chuàng)建其實并不困難,真正困難的是共享內(nèi)存的管理,在正規(guī)的軟件公司像內(nèi)存/共享內(nèi)存管理這樣的重要底層功能都是封裝成庫形式的。

? ? ? ? 四、參考資料

? ? ? ? ? ? SIGSEGV和SIGBUS

? ? ? ? ? ? 涉及共享內(nèi)存的管理就不能不提到訪問共享內(nèi)存對象。談到訪問共享內(nèi)存對象就要留神"SIGSEGV和SIGBUS"這兩個信號。

? ? ? ? ? ? ? 系統(tǒng)分配內(nèi)存頁來承載內(nèi)存映射區(qū),由于內(nèi)存頁大小是固定的,所以存在多余的頁空間空閑,比如待映射文件大小為5000 bytes,內(nèi)存映射區(qū)大小也為5000bytes。而一個內(nèi)存頁大小4096,系統(tǒng)勢必要分配兩頁來承載,這時空閑的有效空間為從5000-8191,如果進程訪問這段地址空間也不會發(fā)生錯誤。但是要超出8191,就會收到SIGSEGV信號,導(dǎo)致程序停止。關(guān)于SIGBUS信號的來歷,這里也舉例說明:若待映射文件大小為5000 bytes,我們在mmap時指定內(nèi)存映射區(qū)size = 15000 > 5000,這時內(nèi)核真正的共享區(qū)承載體大小只有8192(能包容映射文件大小即可),此時在[0, 8191]內(nèi)訪問均沒問題,但在[8192,14999]之間會得到SIGBUS信號;超出15000訪問時會觸發(fā)SIGSEGV信號。

? 5.8 APR分析-環(huán)篇

? ? ? ? ? APR中少見對數(shù)據(jù)結(jié)構(gòu)的封裝,好像唯一例外的就是其對循環(huán)鏈表,即環(huán)(RING)的封裝。

? ? ? ? ? 簡單說說環(huán)(RING):環(huán)是一個首尾相連的雙線鏈表,也就是我們所說的循環(huán)鏈表。

? ? ? ? ? 1.如何使用APR RING?

假設(shè)環(huán)節(jié)點的結(jié)構(gòu)如下: struct elem_t { /* APR RING鏈接的元素類型定義 */APR_RING_ENTRY(elem_t) link; /*鏈接域*/int foo; /*數(shù)據(jù)域*/ }; APR_RING_HEAD(elem_head_t, elem_t); int main() {struct elem_head_t head;struct elem_t *el;APR_RING_INIT(&head, elem_t, link);/* 使用其他操作宏插入、刪除等操作,例如*/el = malloc(sizeof(elem_t);el->foo = 20051103;APR_RING_ELEM_INIT(el, link);APR_RING_INSERT_TAIL(&h, el, elem_t, link); }

? ? ? ? ? ? 2.APR RING的難點---"哨兵"

? ? ? ? ? ?環(huán)是通過頭節(jié)點來管理的,頭節(jié)點是這樣一種節(jié)點,其next指針指向RING的第一個節(jié)點,其prev指針指向RING的最后一個節(jié)點,即尾節(jié)點。但是通過查看源碼發(fā)現(xiàn)APR RING通過APR_RING_HEAD宏定義的頭節(jié)點形式如下:

#define APR_RING_HEAD(head, elem) /struct head { /struct elem *next; /struct elem *prev; /}

? ? ? ? ? ? ?如果按照上面的例子進行宏展開,其形式如下:

struct elem_head_t {struct elem_t *next;struct elem_t *prev; };

? ? ? ? ? ? ? 而一個普通的元素elem_t 展開形式如下:

struct elem_t {struct { /struct elem_t *next; /struct elem_t *prev; /} link;int foo; };

? ? ? ? ? ? ? 通過對比可以看出頭節(jié)點僅僅相當于一個elem_t的link域。這樣做的話必然帶來對普通節(jié)點和頭節(jié)點在處理上的不一致,為了避免這種情況的發(fā)生,APR RING引入了"哨兵"節(jié)點的概念。我們先看看哨兵節(jié)點在整個鏈表中的位置。

? ? ? ? ? ? ? ? sentinel->next = 鏈表的第一個節(jié)點;

? ? ? ? ? ? ? ? sentinel->prev = 鏈表的最后一個節(jié)點;

? ? ? ? ? ? ? ? 但是查看APR RING的源碼你會發(fā)現(xiàn)sentinel節(jié)點只是個虛擬存在的節(jié)點,這個虛擬節(jié)點既有數(shù)據(jù)域(虛擬出來的,不能引用)又有鏈接域,好似與普通節(jié)點并無差別。

? ? ? ? ? ? ? ? ?再看看下面APR_RING_INIT的源代碼:

#define APR_RING_INIT(hp, elem, link) do{ /APR_RING_FIRST((hp)) = APR_RING_SENTINEL((hp), elem, link); /APR_RING_LAST((hp)) = APR_RING_SENTINEL((hp), elem, link); / }while(0)

? ? ? ? ? ? ? ? ? 你會發(fā)現(xiàn):初始化RING實際上是將head的next和prev指針都指向了sentinel虛擬節(jié)點了。從sentinel的角度來說相當于其自己的link域的next和prev都指向了自己。所以判斷APR RING是否為空只需要判斷RING的首個節(jié)點是否為sentinel虛擬節(jié)點即可。APR_RING_EMPTRY宏就是這么做的:

#define APR_RING_EMPTY(hp, elem, link) /(APR_RING_FIRST((hp)) == APR_RING_SENTINEL((hp), elem, link))

? ? ? ? ? ? ? ? ? ?那么如何計算sentinel虛擬節(jié)點的地址呢?

? ? ? ? ? ? ? ? ? ?我們這樣思考:從普通節(jié)點說起,如果我們知道一個普通節(jié)點的首地址(elem_addr),那么我們計算其link域的地址(link_addr)的公式就應(yīng)該為link_addr=elem_addr + offsetof(elem_t, link);前面我們一直在說sentinel虛擬節(jié)點看起來和普通節(jié)點沒什么區(qū)別,所以它仍然符合該計算公式。前面我們又說過head_addr是sentinel節(jié)點的link域,這樣的話我們將head_addr輸入到公式中得到head_addr = sentinel_addr + offsetof(elem_t, link),做一下變換即可得到sentinel_addr = head_addr - offsetof(elem_t, link)。看看APR RING源代碼就是這樣實現(xiàn)的:

#define APR_RING_SENTINEL(hp, elem, link) /(struct elem *)((char *)(hp) - APR_OFFSETOF(struct elem, link))

? ? ? ? ? ? ? ? ? ? ?至此APR RING使用一個虛擬sentinel節(jié)點分隔RING的首尾節(jié)點,已達到對節(jié)點操作一致的目的。

? ? ? ? ? ? ? ? ?3、APR RING不足之處

? ? ? ? ? ? ? ? ? ? 1)缺少遍歷接口

? ? ? ? ? ? ? ? ? ? ? ?瀏覽APR RING源碼后發(fā)現(xiàn)缺少一個遍歷宏接口,這里提供一種正向遍歷實現(xiàn):

#define APR_RING_TRAVERSE(ep, hp, elem, link) /for ((ep) = APR_RING_FIRST((hp)); /(ep) != APR_RING_SENTINEL((hp), elem, link); /(ep) = APR_RING_NEXT((ep), link))

? 5.9 APR分析-進程同步篇

  • 最新的統(tǒng)計數(shù)據(jù)顯示Apache服務(wù)器在全世界仍然占據(jù)著Web服務(wù)器龍頭老大的位置,而且市場占有率遙遙領(lǐng)先,所以學(xué)習(xí)Apache相關(guān)知識是完全正確的方向,這里我們繼續(xù)分析APR進程同步相關(guān)內(nèi)容。

? ? ? ? ?進程同步的源代碼的位置在$(APR_HOME)/locks目錄下,本篇blog著重分析unix子目錄下的proc_mutex.c、global_mutex文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_proc_mutex.h、apr_global_mutex.h。其用于不同進程之間的同步以及多進程多線程中的同步問題。

? ? ? ? ? ? ?apr_thread_mutex_t - 支持單個進程內(nèi)的多線程同步;

? ? ? ? ? ? ?apr_proc_mutex_t - 支持多個進程間的同步;

? ? ? ? ? ? ?apr_global_mutex_t - 支持不同進程內(nèi)的不同線程間同步。

? ? ? ? ? ? ?在本篇中著重分析apr_proc_mutex_t。

? ? ? ? 1.同步機制

? ? ? ? ? APR提供多種進程同步的機制供選擇使用。在apr_proc_mutex.h中列舉了如下同步機制:

typedef enum {APR_LOCK_FCNTL, /*記錄上鎖*/APR_LOCK_FLOCK, /* 文件上鎖*/APR_LOCK_SYSVSEM, /*系統(tǒng)V信號量*/APR_LOCK_PROC_PTHREAD, /* 利用pthread線程鎖特性*/APR_LOCK_POSIXSEM, /*POSIX信號量*/APr_LOCK_DEFAULT /*默認進程間鎖*/ } apr_lockmech_e;

? ? ? ? ?2.實現(xiàn)點滴

? ? ? ? ? ?APR提供每種同步機制的實現(xiàn),每種機制體現(xiàn)為一組函數(shù)接口,這些接口被封裝在一個結(jié)構(gòu)體類型中:

/* in apr_arch_proc_mutex.h */ struct apr_proc_mutex_unix_lock_methods_t {unsigned int flags;apr_status_t (*create)(apr_proc_mutex_t *, const char *);apr_status_t (*acquire)(apr_proc_mutex_t *);apr_status_t (*tryacquire)(apr_proc_mutex_t *);apr_status_t (*release)(apr_proc_mutex_t *);apr_status_t (*cleanup)(void *);apr_status_t (*child_init)(apr_proc_mutex_t **, apr_pool_t *, const char *);const char *name; };

? ? ? ? ? ?之后在apr_proc_mutex_t類型中,apr_proc_mutex_unix_lock_methods_t的出現(xiàn)也就在清理之中了

/* in apr_arch_proc_mutex.h */ struct apr_proc_mutex_t {apr_pool_t *pool;const apr_proc_mutex_unix_lock_methods_t *meth;const apr_proc_mutex_unix_lock_methods_t *inter_meth;int curr_locked;char *fname;... ... #if APR_HAS_PROC_PTHREAD_SERIALIZEpthread_mutex_t *pthread_interproc; #endif };

? ? ? ? ?這樣APR提供的用戶接口其實就是對mech各個"成員函數(shù)"功能的"薄封裝",而真正干活的其實是apr_proc_mutex_t中的meth字段的"成員函數(shù)",它們的工作包括mutex的創(chuàng)建、獲取(加鎖)和清除(解鎖)等。以"獲取鎖"為例APR的實現(xiàn)如下:

APR_DECLARE(apr_status_t) apr_proc_mutex_lock(apr_proc_mutex_t *mutex) {return mutex->meth->acquire(mutex); }

? ? ? ? ?3.同步機制

? ? ? ? ? 按照枚舉類型apr_lockmech_e的聲明,我們知道APR為我們提供了5中同步機制,下面分別說說:

? ? ? ? ? (1) 記錄鎖

? ? ? ? ? ? ?記錄鎖是一種建議性鎖,它不能防止一個進程寫已由另一個進程上了讀鎖的文件,它主要利用fcntl系統(tǒng)調(diào)用來完成鎖功能的,記得在以前的一篇關(guān)于APR文件I/O的Blog中談過記錄鎖,這里不再詳細敘述了。

? ? ? ? ? (2) 文件鎖

? ? ? ? ? ? ? 文件鎖是記錄鎖的一個特例,其功能由函數(shù)接口flock支持。值得說明的是它僅僅提供"寫入鎖"(獨占鎖),而不提供"讀入鎖"(共享鎖)。

? ? ? ? ? ?(3) System V信號量

? ? ? ? ? ? ? System V信號量是一種內(nèi)核維護的信號量,所以我們只需調(diào)用semget獲取一個System V信號量的描述符即可。值得注意的是與POSIX的單個"計數(shù)信號量"不同的是System V信號量是一個"計數(shù)信號量集"。? ?

? ? ? ? ? ?(4) 利用線程互斥鎖機制

? ? ? ? ? ? ? APR使用pthread提供的互斥鎖機制。原本pthread互斥鎖是用來互斥一個進程內(nèi)的各個現(xiàn)成的,但APR在共享內(nèi)存中創(chuàng)建了pthread_mutex_t,這樣使得不同進程的主線程實現(xiàn)互斥,從而達到進程間互斥的目的。截取部分代碼如下:

new_mutex->pthread_interproc = (pthread_mutex_t *)mmap((caddr_t)0,sizeof(pthread_mutex_t),PROT_READ | PROT_WRITE, MAP_SHARED,fd, 0);

? ? ? ? ? ? (5) POSIX信號量

? ? ? ? ? ? ? ?APR使用了POSIX有名信號量機制,下面代碼舉例說明:

/* in proc_mutex.c */ apr_snprintf(semname, sizeof(semname), "/ApR.%lxZ%lx", sec, usec); /* APR自定義一種POSIX信號量命名規(guī)則*/ psem =sem_open(semname, O_CREAT, 0644, 1);

? ? ? ? ? ?4.如何使用

? ? ? ? ? ? ?我們知道父進程的鎖其子進程并不繼承。APR進程同步機制的一個典型使用方法就是:"Create the mutex in the Parent, Attach to in the Child"。APR提供接口apr_proc_mutex_child_init在子進程中reopen themutex。

? 5.10 APR分析-線程篇

? ? ? ? ? 并行一直是程序設(shè)計領(lǐng)域的難點,而線程是并行的一種重要的手段,而且現(xiàn)成的一些特性也能在進程并行時發(fā)揮很好的作用(“在線程同步篇”中詳細闡述)。

? ? ? ? APR線程的源代碼的位置在$(APR_HOME)/threadproc目錄下,本篇blog著重分析unix子目錄下的thread.c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_threadproc.h。

? ? ? ? ? 一、線程基礎(chǔ)

? ? ? ? ? ? ? (1) 在傳統(tǒng)觀點中,進程是由存儲于用戶虛擬內(nèi)存中的代碼、數(shù)據(jù)和棧,以及由內(nèi)核維護的"進程上下文"組成的,其中"進程上下文"又可以看成"程序上下文"和"內(nèi)核上下文"組成,可參見下圖所示:

進程--|- 進程上下文|-程序上下文|-數(shù)據(jù)寄存器|-條件碼|-棧指針|-程序計數(shù)器|- 內(nèi)核上下文|- 進程ID|- VM結(jié)構(gòu)|- Open files|- 已設(shè)置的信號處理函數(shù)|- brk pointer|- 代碼、數(shù)據(jù)和棧(在虛存中)|- 棧區(qū)<--SP|- 共享庫區(qū)|- 運行時堆區(qū) <--brk|- 可讀/寫數(shù)據(jù)區(qū)|- 只讀代碼/數(shù)據(jù)區(qū) <--PC

? ? ? ? ? ? ? ?(2) 另一種觀點中,進程是由線程、代碼和數(shù)據(jù)以及內(nèi)核上下文組成的。下圖更能直觀的展示出兩種觀點的異同:

進程--+|- 線程|- 棧區(qū) <--SP|- 線程上下文|- 線程ID|- 數(shù)據(jù)寄存器|- 條件碼|- 棧指針|- 程序計數(shù)器|- 內(nèi)核上下文|- 進程ID|- VM結(jié)構(gòu)|- Open files|-已設(shè)置的信號處理函數(shù)|- brk pointer|- 代碼、數(shù)據(jù)(在虛存中)|- 共享庫區(qū)|- 運行時堆區(qū) <-- brk|- 可讀/寫數(shù)據(jù)區(qū)|- 只讀代碼/數(shù)據(jù)區(qū) <--PC

? ? ? ? ? ? ? ?對比兩種觀點我們可以得出以下幾點結(jié)論:

? ? ? ? ? ? ?(a) 從觀點(2)可以看出進程內(nèi)的多個線程共享進程的內(nèi)核上下文和代碼、數(shù)據(jù)(當然不包括棧區(qū));

? ? ? ? ? ? ?(b) 線程上下文比進程上下文小,且切換代價小;

? ? ? ? ? ? ?(c) 線程不像進程那樣有著"父-子"體系,同一個進程內(nèi)的線程都是"對等的",主線程與其他線程不同之處就在于其是進程創(chuàng)建的第一個線程。

? ? ? ? ?二、APR線程管理接口

? ? ? ? ? ? ?如今應(yīng)用最廣泛的線程包就是PosixThread了。APR對線程的封裝也是基于Posix thread的。

? ? ? ? ? ? ?APR線程管理接口針對apr_thread_t 這個基本的數(shù)據(jù)結(jié)構(gòu)進行操作,apr_thread_t的定義很簡單:

/* apr_arch_threadproc.h */ struct apr_thread_t {apr_pool_t *pool;pthread_t *td;void *data;apr_thread_start_t func;apr_status_t exitval; };

? ? ? ? ? ? ?這個結(jié)構(gòu)中包含了線程ID、線程函數(shù)以及該函數(shù)的參數(shù)數(shù)據(jù)。不過APR的線程函數(shù)定義與Pthread的有不同,“Pthread線程函數(shù)”是這樣的:

? ? ? ? ? typedef void *(start_routine)(void *);

? ? ? ? ? ?而"APR線程函數(shù)"如下:

? ? ? ? ? typedef void *(APR_THREAD_FUNC *apr_thread_start_t)(apr_thread_t *, void *);

? ? ? ? ?1.apr_thread_create

? ? ? ? ? ? apr_thread_create 內(nèi)部定義了一個dummy_worker的"Pthread線程函數(shù)",并將apr_thread_t結(jié)構(gòu)作為參數(shù)傳入,然后在dummy_worker中啟動"APR的線程函數(shù)"。在該函數(shù)的參數(shù)列表中有一項類型為apr_threadattr_t:

struct apr_threadattr_t {apr_pool_t *pool;pthread_attr_t attr; };

? ? ? ? ? 2.apr_thread_exit

? ? ? ? ? ? ? 進程退出我們可以直接調(diào)用exit函數(shù),而線程退出也有幾種方式:

? ? ? ? ? ? ?(1) 隱式退出 - 可以理解為線程main routine代碼結(jié)束返回;

? ? ? ? ? ? ?(2) 顯式退出 - 調(diào)用線程包提供的顯示退出接口,在apr中就是apr_thread_exit;

? ? ? ? ? ? ?(3) 另類顯式退出 - 調(diào)用exit函數(shù),不僅自己退出,其所在線程也跟著退出了;

? ? ? ? ? ? ?(4)被"黑"退出 - 被別的"對等"線程調(diào)用pthread_cancel而被迫退出。

? ? ? ? ? ? ?apr_thread_exit屬于種類(2),該種類退出應(yīng)該算是線程的優(yōu)雅退出了。apr_thread_exit做了3個工作,分別為設(shè)置線程返回值、釋放pool中資源和調(diào)用pthread_exit退出。

? ? ? ? ? ? 3.apr_thread_join和apr_thread_detach

? ? ? ? ? ? ? 進程有waitpid,線程有join。線程在調(diào)用apr_thread_exit后,只是其執(zhí)行停止了,其占有的"資源"并不一定釋放,這里的"資源"我想就是"另種觀點"中的"線程上下文",線程有兩種方式來釋放該"資源",這主要由現(xiàn)成的"可分離"屬性決定的。如果線程是"可分離的",當線程退出后會自動釋放其"資源",如果線程為"非可分離的",則必須由"對等線程"調(diào)用join接口來釋放其資源。apr_thread_detach用來將其調(diào)用線程轉(zhuǎn)化為"可分離"線程,而apr_thread_join用來等待某個線程結(jié)束并釋放其資源。

? 5.11 APR分析-網(wǎng)絡(luò)IO篇

? ? ? ? ?APR網(wǎng)絡(luò)I/O的源代碼的位置在$(APR_HOME)/network_io目錄下,本篇blog著重分析unix子目錄下的各.c文件內(nèi)容,其相應(yīng)頭文件為$(APR_HOME)/include/apr_network_io.h。

? ? ? ?一、IP地址 -- 主機通信

? ? ? ? ? ?我們熟知的并且每天工作于其上的因特網(wǎng)是一個世界范圍的主機的集合,這個主機結(jié)合被映射為一個32位(目前)或者64位(將來)IP地址;而IP地址又被映射為一組因特網(wǎng)域名;一個網(wǎng)絡(luò)中的主機上的進程能通過一個連接(connection)和任何其他網(wǎng)絡(luò)中的主機上的進程通信。

? ? ? ? ? 1.IP地址存儲

? ? ? ? ? ? ?在如今的IPv4協(xié)議中我們一般使用一個unsigned int來存儲IP地址,在UNIX平臺下,使用如下結(jié)構(gòu)來存儲一個IP地址的值:

/* Internet address structure */ struct in_addr {unsigned int s_addr; /* network byte order(big-endian) */ };

? ? ? ? ? ? ?這里值得一提的是APR關(guān)于IP地址存儲的做法,看如下代碼:

#if (!APR_HAVE_IN_ADDR) /*We need to make sure we always have an in_addr type, so APR will just define it ourselves, if the platform doesn't provide it. */ struct in_addr {apr_uint32_t s_addr; }; #endif

? ? ? ? ? ? ? APR保證了其所在平臺上in_addr的存在。在in_addr中,s_addr是以網(wǎng)絡(luò)字節(jié)序存儲的。如果你的IP地址不符合條件,可通過調(diào)用一些輔助接口來做轉(zhuǎn)換,這些接口包括:

htonl: host to network long; htons: host to network short; ntohl: network to host long; ntohs: network to host short.

? ? ? ? ? ? ? 2.IP地址表示

? ? ? ? ? ? ? ?我們平時看到的IP地址都是類似"xxx.xxx.xxx.xxx"這樣的點分十進制的。上面說過IP地址使用的是一個unsigned int整形數(shù)來表示。這樣就存在著一個IP地址表示和IP地址存儲之間的一個轉(zhuǎn)換過程。APR提供這一轉(zhuǎn)換支持,我們用一個例子來說明:

#include <apr.h> #include <apr_general.h> #include "apr_network_io.h" #include "apr_arch_networkio.h" int main(int argc, const char *const *argv, const char *const *env) {apr_app_initialize(&argc, &argv, &env);char presentation[100];int networkfmt;memset(presentation, 0, sizeof(presentation));apr_inet_pton(AF_INET, "255.255.255.255", &networkfmt);printf("0x%x/n", networkfmt);apr_inet_ntop(AF_INET, &networkfmt, presentation, sizeof(presentation));printf("presentation is %s/n", presentation);apr_terminate();return 0; }

? ? ? ? ? ? ? ?APR提供apr_inet_pton將我們熟悉的點分十進制形式轉(zhuǎn)換成一個整形數(shù)存儲的IP地址;而apr_inet_ntop則將一個存整形數(shù)存儲的IP地址轉(zhuǎn)換為我們可讀的點分十進制形式。這兩個接口的功能類似于系統(tǒng)調(diào)用inet_pton和inet_ntop,至于使用哪個就看你的喜好了。

? ? ? ?二、SOCKET --進程通信

? ? ? ? ? ?1.SOCKET描述符

? ? ? ? ? ? ? 獲取SOCKET描述符:

int socket(int domain, int type, int protocol);

? ? ? ? ? ? ? 從Unix程序的角度來看,SOCKET就是一個有相應(yīng)描述符的打開的文件。在APR中我們可以通過調(diào)用apr_socket_create來創(chuàng)建一個APR自定義的SOCKET對象,該SOCKET結(jié)構(gòu)如下:

/* apr_arch_networkio.h*/ struct apr_socket_t {apr_pool_t *cntxt;int socketdes;int type;int protocol;apr_sockaddr_t *local_addr;apr_sockaddr_t *remote_addr;apr_interval_time_t timeout; #ifdef HAVE_POLLint connected; #endifint local_port_unknown;int local_interface_unknown;int remote_addr_unknown;apr_int32_t options;apr_int32_t inherit;sock_userdata_t *userdata; #ifndef WAITIO_USES_POLL/* if there is a timeout set, then this pollset is used */apr_pollset_t *pollset; #endif };

? ? ? ? ? ? ? ? ? ? ? ?該結(jié)構(gòu)中的socketdes字段其實是真實存儲由socket函數(shù)返回的SOCKET描述符的,其他字段都是為APR自己所使用的,這些字段在Bind、Connect等過程中使用。我們?nèi)绻伙@示將SOCKET描述符綁定到某SOCKET地址上,系統(tǒng)內(nèi)核就會自動為該SOCKET描述符分配一個SOCKET地址。

? ? ? ? ? ? 2.SOCKET屬性

? ? ? ? ? ? ? ? 還是與文件對比,在文件系統(tǒng)調(diào)用中有一個fcntl接口可以從來獲取或設(shè)置已分配的文件描述符的屬性,如是否Block、是否Buffer等。SOCKET也提供類似的接口調(diào)用setsockopt和getsockopt。在APR中等價于該功能的接口時apr_socket_opt_set和apr_socket_opt_get。APR在apr_network_io.h中提供如下SOCKET的參數(shù)屬性:

#define APR_SO_LINGER 1 /*Linger*/ #define APR_SO_KEEPALIVE 2 /*Keepalive*/ #define APR_SO_DEBUG 4 /*Debug*/ #define APR_SO_NONBLOCK 8 /*Non-blocking IO*/ #define APR_SO_REUSEADDR 16 /*Reuse addresses*/ #define APR_SO_SNDBUF 64 /*Send buffer*/ #define APR_SO_DISCONNECTED 256 /*Disconnected*/ ......

? ? ? ? ? ?另外從上面這些屬性值(都是2的n次方)可以看出SOCKET也是使用一個屬性控制字段中的"位"來控制SOCKET屬性的。

? ? ? ? ? ?再有APR提供一個宏apr_is_option_set來判斷一個SOCKET是否擁有某個屬性。

? ? ? ? ? ? 3.Connect、Bind、Listen、Accept-- 建立連接

? ? ? ? ? ? ? (1) apr_socket_connect

? ? ? ? ? ? ? ?客戶端連接服務(wù)器端的唯一調(diào)用就是connect,connect試圖建立一個客戶端進程與服務(wù)器端進程的連接。apr_socket_connect的參數(shù)分別為客戶端已經(jīng)打開的一個SOCKET以及指定的服務(wù)器端的SOCKET地址(IP ADDR:PORT)。apr_socket_connect內(nèi)部實現(xiàn)的流程大致如下:

apr_socket_connect {do {rc = connect(sock->socketdes,(const struct sockaddr *)&sa->sa.sin,sa->salen);} while (rc == -1 && errno == EINTR); ---(a)if ((rc == -1) && (errno == EINPROGRESS || errno == EALREADY)&& (sock->timeout > 0)) {rc = apr_wait_for_io_or_timeout(NULL, sock, 0); ----(b)if (rc != APR_SUCCESS) {return rc;}if (rc == -1 && errno != EISCONN) {return errno; ----(c)}初始化sock->remote_addr;... ... }

? ? ? ? ? ? ? ? ? 對上述代碼進行若干說明:

? ? ? ? ? ? ? (a) 執(zhí)行系統(tǒng)調(diào)用connect連接服務(wù)器端,注意這里做了防止信號中斷的處理。

? ? ? ? ? ? ? (b) 如果系統(tǒng)操作正在進行中,調(diào)用apr_wait_for_io_or_timeout進行超時等待;

? ? ? ? ? ? ? (c) 錯誤返回,前提errno不是表示已連接上。

? ? ? ? ? ? ? 一旦apr_socket_connect成功返回,我們就已經(jīng)成功建立一個SOCKET對,即一個連接。

? ? ? ? ?(2) apr_socket_bind

? ? ? ? ? ? ? Bind、Listen和Accept這三個過程是服務(wù)器端用于接收"連接"的必經(jīng)之路。其中Bind就是告訴操作系統(tǒng)內(nèi)核顯示地位該SOCKET描述符分配一個SOCKET地址,這個SOCKET地址就不能被其他SOCKET描述符占用了。

? ? ? ? ?(3)apr_socket_listen

? ? ? ? ? ? ?SOCKET描述符在初始分配時都處于"主動連接"狀態(tài),Listen過程將該SOCKET描述符從"主動連接"轉(zhuǎn)換為"被動狀態(tài)",并告訴內(nèi)核接受該SOCKET描述符的連接請求。apr_socket_listen的背后直接就是listen接口調(diào)用。

? ? ? ? ? (4) apr_socket_accept

? ? ? ? ? ? ? Accept過程在"被動狀態(tài)"SOCKET描述符上接受一個客戶端的連接,這時系統(tǒng)內(nèi)核會自動分配一個新的SOCKET描述符,內(nèi)核為該描述符自動分配一個SOCKET地址,來代表這條連接的服務(wù)器端。注意在SOCKET編程接口中除了socket函數(shù)能分配新的SOCKET描述符之外,accept也是另外的一個也是唯一的一個能分配新的SOCKET描述符的系統(tǒng)調(diào)用了。apr_socket_accept首先在pool中分配一個新的apr_socket_t結(jié)構(gòu)變量,然后調(diào)用accept,并設(shè)置新變量的各個字段。

? ? ? ? ? ? 4.Send/Recv --數(shù)據(jù)傳輸

? ? ? ? ? ? ? 網(wǎng)絡(luò)通信最重要的還是數(shù)據(jù)傳輸,在SOCKET編程接口中最常見的兩個接口就是recv和send。在APR中分別有apr_socket_recv和apr_socket_send與前面二者對應(yīng)。下面逐一分析。

? ? ? ? ? ? ? ?(1) apr_socket_recv

? ? ? ? ? ? ? ? ?首先來看看apr_socket_recv的實現(xiàn)過程:

? ? ? ? ? ? ? ? ?

apr_socket_recv {if (上次調(diào)用apr_socket_recv沒有讀完所要求的的字節(jié)數(shù)) { ----------(a)設(shè)置sock->options;goto do_select;}do {rv = read(sock->socketdes, buf, (*len)); ------(b)} while (rv == -1 && errno == EINTR);if ((rv == -1) && (errno == EAGAIN || errno == EWOULDBLOCK) && (sock->timeout > 0)) { do_select:arv = apr_wait_for_io_or_timeout(NULL, sock, 1);if (arv != APR_SUCCESS) {*len = 0;return arv;}else {do {rv = rad(sock-》socketdes,buf, (*len));} while(rv == -1 && errno == EINTR);}} ---------(c)設(shè)置(*len)和sock->options; -----(d)... ... }

? ? ? ? ? ? ? ? ? ? ? ?針對上面代碼進行說明:

? ? ? ? ? ? ? ? ? ? ? ?(a) 一次apr_socket_recv調(diào)用完全有可能沒有讀完所要求的字節(jié)數(shù),這里做個判斷以決定是否繼續(xù)讀完剩下的數(shù)據(jù);

? ? ? ? ? ? ? ? ? ? ? ? (b) 調(diào)用read讀取SOCKET緩沖區(qū)數(shù)據(jù),注意這里做了防止信號中斷的處理。

? ? ? ? ? ? ? ? ? ? ? ? (c) 如果SOCKET操作正忙,我們調(diào)用apr_wait_for_io_or_timeout等待,直到SOCKET可用。

? ? ? ? ? ? ? ? ? ? ? ? (d) 將(*len)設(shè)置為實際從SOCKET Buffer中讀取的字節(jié)數(shù),并根據(jù)這一實際數(shù)據(jù)與要求數(shù)據(jù)作比較來設(shè)置sock->options.

? ? ? ? ? ? ? (2) apr_socket_send

? ? ? ? ? ? ? ? ? ? apr_socket_send負責(zé)發(fā)送數(shù)據(jù)到SOCKET Buffer,其實現(xiàn)的方式與apr_socket_recv大同小異。

? ? ? ? ? ? ? ? ?

總結(jié)

以上是生活随笔為你收集整理的参考资料学习APR库的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

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

国产又粗又猛又色又黄网站 | 久久夜色精品国产欧美一区麻豆 | 国产精品久久久久久一二三四五 | 免费看黄色大全 | 日韩精品专区在线影院重磅 | 91片黄在线观 | www.福利| 黄色小说视频网站 | 男女激情免费网站 | 国产伦理剧 | 在线黄色av | 久久在线免费视频 | 精品一区二区在线免费观看 | 9999激情 | 欧美另类人妖 | 精品视频国产 | 亚洲 精品在线视频 | 日韩免费在线观看网站 | 91私密保健| 天天干夜夜操视频 | 人人看人人艹 | 九七视频在线观看 | 天天射色综合 | 成 人 黄 色 视频免费播放 | 色香天天 | www黄| 五月天九九 | 国产伦精品一区二区三区高清 | 久久人人爽 | 久久久精品二区 | 久久情网 | 国产拍揄自揄精品视频麻豆 | 在线看成人av | 99视频在线免费播放 | 27xxoo无遮挡动态视频 | a视频免费在线观看 | 天天操天天透 | 国产在线a | 友田真希x88av | 国产精品2018 | 五月天中文字幕 | 天天色天天射天天综合网 | 亚洲精品美女久久17c | 999电影免费在线观看2020 | 国产中文字幕国产 | 日韩在线免费高清视频 | 国产精品系列在线观看 | 久久国产一区二区三区 | 国产在线一区二区三区播放 | 国产精品成久久久久 | 99久久这里只有精品 | a级国产乱理伦片在线播放 久久久久国产精品一区 | 国产精品theporn | 亚洲黄色免费在线看 | 国产免费观看久久 | 亚洲人成在线观看 | 夜色资源站国产www在线视频 | 亚洲日本色 | 少妇自拍av | 8090yy亚洲精品久久 | 成人在线超碰 | 黄色网中文字幕 | 激情导航 | 成人在线观看av | 天天色 天天 | 久久不射电影网 | 国内精品中文字幕 | 久久99网站 | 色a网 | 99亚洲精品在线 | 日本在线观看视频一区 | 毛片3| 狠狠狠狠狠色综合 | 免费高清在线一区 | 天天草天天操 | 天天干天天射天天爽 | 九九九电影免费看 | 日韩丝袜在线 | 久久嗨 | 精品视频中文字幕 | 五月黄色 | 狠狠干五月天 | 一区二区三区高清不卡 | 一区中文字幕电影 | 国产伦精品一区二区三区在线 | 麻豆视频成人 | 在线电影中文字幕 | 91免费高清在线观看 | 国产成人免费观看久久久 | 国产伦精品一区二区三区四区视频 | 久久只有精品 | 久久激情视频 久久 | 91激情视频在线 | 欧美成人亚洲成人 | 亚洲免费在线观看视频 | 国内精品久久久久 | 成年人视频免费在线 | 亚洲精品视频在线播放 | 最新国产在线视频 | 久久毛片高清国产 | av不卡中文 | 91精选| 人人天天夜夜 | 一区精品久久 | 国产视频一区在线 | 6080yy午夜一二三区久久 | 香蕉91视频 | 18国产精品福利片久久婷 | 狠狠色香婷婷久久亚洲精品 | 日本精品一区二区三区在线播放视频 | 亚洲丁香久久久 | 亚洲国产午夜视频 | 天堂v中文| 色婷婷www | 国产在线观看一 | 久久手机视频 | 日本不卡123 | 97色婷婷 | 国产精品久久久久久久久久久久午夜 | 国产一二区视频 | 久草爱| 久久免费的精品国产v∧ | 久久首页 | 日韩在线观看视频一区二区三区 | 精品国产一区二区三区久久久 | 色婷婷激情网 | 中文字幕av有码 | 色视频网站免费观看 | 国产美女精彩久久 | 免费亚洲成人 | 日韩免费视频 | 开心婷婷色| www.午夜 | 色.www| 久久久噜噜噜久久久 | av丁香 | 国产精品自拍在线 | 日韩欧美电影在线观看 | www色| 久久99亚洲热视 | 亚洲成成品网站 | 国产精品亚 | 亚洲午夜久久久久久久久 | 免费99| 最新国产精品久久精品 | 久久国际影院 | 日韩黄色在线观看 | 中文字幕免费观看全部电影 | 激情综合一区 | 草久久久 | 久久久久久久国产精品 | 久热超碰| 婷婷国产在线观看 | 免费日韩电影 | 久草久热 | av电影中文| 久久婷亚洲五月一区天天躁 | 国内精品久久天天躁人人爽 | 超碰在线观看av.com | 午夜av剧场 | 亚洲成人精品 | 久久精品伊人 | 91香蕉视频720p | 中文字幕一区二 | 免费无遮挡动漫网站 | 激情久久伊人 | 狠狠狠的干| 久久人人97超碰国产公开结果 | 亚洲婷婷伊人 | 欧美日韩一区二区三区免费视频 | 国产精品v欧美精品v日韩 | 91九色国产视频 | www狠狠 | 在线韩国电影免费观影完整版 | 色综合亚洲精品激情狠狠 | 亚洲免费永久精品国产 | 免费在线| 国产精品永久在线 | 国产精品入口久久 | av成人免费在线观看 | 激情五月亚洲 | 日韩免费视频观看 | 久草久草久草久草 | 色婷婷综合久久久中文字幕 | 日韩精品免费一区二区 | 亚洲天天综合网 | 成人av网址大全 | 国产欧美精品一区二区三区四区 | 97精品久久 | 九七人人干 | av天天澡天天爽天天av | 成人h在线播放 | 国产日产精品一区二区三区四区的观看方式 | 欧美亚洲久久 | 亚洲一区二区视频在线播放 | 精品国产一区二区三区久久 | 国产精品美女免费 | 国产亚洲资源 | 91九色视频国产 | 一区二区久久久久 | 日韩网站在线免费观看 | 色亚洲激情 | 国产精品视频专区 | 高清av在线 | 国产精品高潮呻吟久久久久 | 国产精品99精品久久免费 | 国产精品精品久久久久久 | 国产欧美精品在线观看 | 国产一级视频 | 色天天综合久久久久综合片 | 毛片888| 亚洲欧美日韩精品久久久 | 91成人精品一区在线播放 | 国产在线更新 | 国产男女无遮挡猛进猛出在线观看 | 免费观看www小视频的软件 | 亚洲va韩国va欧美va精四季 | 激情婷婷在线观看 | 成人一级片视频 | 天堂av免费看| 人人爽人人爽 | 精品一区二区在线看 | 毛片美女网站 | 亚洲国产日韩一区 | 狠狠干狠狠艹 | 久久草草热国产精品直播 | 97av在线视频| 97在线影视 | 日韩欧在线 | 狠狠色丁香婷婷综合基地 | 丁香婷婷激情网 | 欧美日韩不卡一区二区 | 久免费| 成年人黄色在线观看 | 色九九影院| 精品国内自产拍在线观看视频 | 国产一区二区成人 | 波多野结衣视频一区二区 | 99精品国自产在线 | 亚洲婷婷免费 | 成年人在线观看免费视频 | 四虎亚洲精品 | 久久香蕉国产精品麻豆粉嫩av | 久久久这里有精品 | 久久99久久99久久 | 国产一区二区三区久久久 | 天天综合视频在线观看 | 久久午夜网 | 欧美国产一区二区 | 91在线视频观看免费 | 欧美性色黄 | 久久视频在线观看免费 | 在线观看黄网 | 香蕉色综合 | 日韩av一区二区在线 | www久久精品 | 91av在线免费 | 激情av一区二区 | 国产成人一二片 | 亚洲天天做 | 日本视频高清 | 超碰在线日本 | 操久在线 | 国产美女免费 | 婷婷精品国产一区二区三区日韩 | 精品美女在线视频 | 天天躁日日躁狠狠 | 成人精品国产免费网站 | 91日本在线播放 | 久久久国产精品视频 | 青青草国产精品 | 五月婷在线观看 | 狠狠操狠狠干天天操 | 在线观看av片 | 99精品国自产在线 | 日韩一区二区免费视频 | 国产青草视频在线观看 | 日本在线观看一区二区 | 欧美a√大片 | 欧美在线一二 | 欧美爽爽爽 | 天堂网一区 | 国产午夜三级一二三区 | 91av在线精品 | 国产精品a级 | 免费看黄在线看 | 81国产精品久久久久久久久久 | 天天操天天舔天天干 | 午夜久草| 久久综合影音 | 久久久久亚洲天堂 | 狠狠色丁香婷婷综合久小说久 | 久久婷婷一区二区三区 | 国内外成人免费在线视频 | 国产高清av | 91麻豆精品国产自产 | 美女视频国产 | 粉嫩av一区二区三区四区五区 | 国产精品久久久久久五月尺 | 国产精品资源在线观看 | 国产首页 | 狠狠操影视 | 99热国产在线中文 | 免费色黄| 99久久久国产免费 | 欧美aa在线| 99精品国产在热久久下载 | 精品久久久国产 | 四虎国产精品免费观看视频优播 | 国产精品乱码一区二区视频 | 国内外成人免费在线视频 | 2019久久精品 | 视频在线91 | 天天爱综合| 欧美日韩中文在线观看 | av在线直接看 | 欧美一级视频在线观看 | 欧美日韩在线视频一区 | 久久电影色 | 亚洲国产中文字幕在线观看 | 国产1区2区3区精品美女 | 天天艹天天 | 欧美一区三区四区 | 91亚色视频在线观看 | 青草视频免费观看 | 日韩色一区二区三区 | 日韩欧美一二三 | 97免费在线观看视频 | 成人精品视频 | 操久久免费视频 | 粉嫩aⅴ一区二区三区 | 久久影院中文字幕 | 国产a级片免费观看 | 91成人欧美 | 色无五月 | 午夜久久久精品 | 久久免费视频网 | 国产免费一区二区三区最新6 | 久草在线最新免费 | 国产视频手机在线 | 日韩高清www | 精品综合久久久 | 亚洲国产福利视频 | 狠狠色丁香婷婷综合久小说久 | 色综合色综合久久综合频道88 | 91色吧| 日韩69av| 国产精品久久久久久一区二区三区 | 狠狠网亚洲精品 | 亚洲精品xxxx | 成人97人人超碰人人99 | 91豆麻精品91久久久久久 | 免费情缘 | 欧美日韩一区二区三区在线免费观看 | 国产视频一区精品 | 国产亚洲久一区二区 | 亚洲国产成人高清精品 | 四季av综合网站 | a黄色影院| av在线网站大全 | 天天伊人网 | 久久综合中文字幕 | 欧美精品一区二区三区一线天视频 | 久久精品亚洲精品国产欧美 | 91av超碰| 国产成人亚洲在线观看 | 日韩视频在线一区 | 免费观看成年人视频 | 91成人看片 | 欧美福利视频 | 久久久免费精品 | 日韩在线视频线视频免费网站 | 亚洲精品国产精品国自产观看 | 欧美色图视频一区 | 992tv在线| 亚洲精品乱码久久久久久久久久 | 久操97 | 成人avav| 丁香六月在线观看 | 亚洲午夜久久久久久久久电影网 | 亚洲六月丁香色婷婷综合久久 | 国产在线中文 | 午夜影视剧场 | 亚洲春色成人 | 国产精品一区二区三区四 | a视频在线播放 | 亚洲欧美日本A∨在线观看 青青河边草观看完整版高清 | 黄色大片视频网站 | 日韩视频一区二区三区在线播放免费观看 | 亚洲精品456在线播放乱码 | 亚洲一级电影在线观看 | 日韩精品一区二区三区第95 | 97超碰伊人| 亚洲91av | 在线91观看 | 亚洲精品久久久久久久不卡四虎 | 一区二区不卡视频在线观看 | 国产精品一区二区美女视频免费看 | 四川bbb搡bbb爽爽视频 | 91av免费观看 | 国产成人精品日本亚洲999 | 在线探花 | 黄色av网站在线免费观看 | 91九色在线视频 | 日本三级吹潮在线 | 国产免费xvideos视频入口 | 日韩欧美视频在线播放 | 欧美极品少妇xbxb性爽爽视频 | 五月天中文字幕mv在线 | 久久国产精品一区二区 | 丁香久久综合 | 国产日韩精品一区二区在线观看播放 | 国产69精品久久久久9999apgf | 久久精品91视频 | 一区免费视频 | 蜜臀一区二区三区精品免费视频 | 久久国产精品系列 | 日批视频 | 久久字幕精品一区 | 色av网站 | 久久午夜网 | 中文字幕在线一区观看 | 久久免费视频观看 | 国产不卡毛片 | 日本久久久久 | 国产精品综合久久久久久 | 又黄又刺激视频 | 久久99精品久久久久久久久久久久 | 99视频在线精品国自产拍免费观看 | 欧美少妇bbwhd | 在线观看一区二区精品 | 狠狠色噜噜狠狠狠狠2021天天 | 天堂在线免费视频 | 在线看国产视频 | 国产精品初高中精品久久 | 成年性视频| 蜜臀久久99精品久久久久久网站 | 激情文学丁香 | 色欧美88888久久久久久影院 | 国产精品久久久久久久久久久久 | 开心激情五月网 | 精品国产免费人成在线观看 | 国产一区二区三区免费观看视频 | 99 久久久久 | 午夜av免费看 | 国产福利精品在线观看 | 综合激情av | 欧美精品国产综合久久 | 九九综合九九综合 | 亚洲国产精品成人av | 亚洲免费在线播放视频 | 国产精品嫩草影视久久久 | 探花视频在线观看+在线播放 | 日韩一区正在播放 | 久久精品成人欧美大片古装 | 91av网站在线观看 | 免费日韩 | 成片免费 | 在线成人观看 | 在线观看国产v片 | 久久艹99 | 中文一区二区三区在线观看 | 免费在线播放视频 | 成年人黄色在线观看 | 中文字幕视频一区二区 | av理论电影 | 亚州性色| 很黄很黄的网站免费的 | 热久久免费视频精品 | 免费看成人av | 欧美激情综合五月色丁香 | 中文字幕 国产精品 | 久久九九影视 | 天天干人人干 | 13日本xxxxxⅹxxx20 | 制服丝袜成人在线 | 久久爱综合| 91黄色在线观看 | 久久久亚洲影院 | 成人动态视频 | 成人午夜电影网站 | 亚洲天堂精品视频在线观看 | 91视频在线播放视频 | 久久国产视频网 | 7777精品伊人久久久大香线蕉 | 91视频免费网址 | 国产精品黑丝在线观看 | 91影视成人 | 狠狠躁日日躁狂躁夜夜躁av | 婷婷在线观看视频 | 美女黄久久 | 午夜av在线电影 | 91精品国产99久久久久久久 | 国产精品一区专区欧美日韩 | 成人免费av电影 | 国产亚洲精品久 | 欧美精品久久久久久久久久丰满 | 人人爽久久久噜噜噜电影 | av中文字幕在线免费观看 | 国产精品成人一区二区 | 美女精品在线观看 | 91视频91色 | 色婷婷88av视频一二三区 | 91成人免费看片 | 97在线观看免费高清完整版在线观看 | 欧美人牲| 99视频在线观看视频 | 美女网站在线看 | 久久久影片 | 免费av大全 | 国产又黄又猛又粗 | 亚洲精品综合一二三区在线观看 | 国产一级不卡视频 | 超碰97中文 | 天天色天天艹 | 久久久久久久久久久久国产精品 | 中文乱幕日产无线码1区 | av天天草| 天天干夜夜擦 | 国产成人在线综合 | 91av看片 | www.伊人网.com | 激情网站免费观看 | 激情伊人五月天 | 亚洲精品美女免费 | 国产理论免费 | 久久精视频 | 99免费看片 | 99这里都是精品 | 久久丁香 | 人人爽久久涩噜噜噜网站 | 日韩电影中文字幕在线 | 精品国产午夜 | 91九色综合 | 色91在线| 国产在线观看网站 | 成人一区不卡 | 人人添人人澡人人澡人人人爽 | 久久不卡国产精品一区二区 | 欧美日本啪啪无遮挡网站 | 亚洲人成人天堂h久久 | 久久精品毛片 | 国产在线视频一区二区 | 亚洲国产日韩欧美在线 | 操高跟美女 | 久久免费成人精品视频 | 国产视频在线一区二区 | www.亚洲精品视频 | 国产麻豆视频免费观看 | 99在线热播| 蜜臀av.com | 九九热精品在线 | 91激情视频在线 | 免费在线国产精品 | 日韩精品一区二区免费视频 | 久久精品视频在线播放 | 高潮毛片无遮挡高清免费 | 国产高清av在线播放 | 在线视频中文字幕一区 | 亚洲精品www| 超碰免费在线公开 | 久久在视频 | 久久久久久国产精品 | 中文字幕一区二区三区精华液 | 黄色国产区 | 中文字幕在线观看完整版 | 国产不卡在线视频 | 日韩av一区二区在线影视 | 深爱激情五月婷婷 | 日韩在线观看视频一区二区三区 | 亚洲国产剧情av | 在线视频一区二区 | 日韩欧美国产精品 | 久久有精品 | 色视频在线 | 中文字幕av电影下载 | 亚洲极色 | 狠狠久久综合 | 性色在线视频 | 国产91精品一区二区绿帽 | 亚洲精品黄色在线观看 | 99热在线观看免费 | 黄在线| 久久激五月天综合精品 | 国产在线视频一区 | 五月婷婷精品 | 丝袜制服综合网 | 天天射天天做 | 免费高清在线观看成人 | 91精品欧美一区二区三区 | 日韩欧美大片免费观看 | 精品国产一区二区三区蜜臀 | 国产午夜一区二区 | 99色 | 久久久久久国产精品亚洲78 | 亚洲激情 | 奇米导航 | 玖玖爱国产在线 | 人人爽久久久噜噜噜电影 | 亚洲精品视频在线观看免费视频 | 超碰在线色 | 奇米影视四色8888 | 在线观看黄色的网站 | 国产不卡毛片 | 99久久精品国产免费看不卡 | 国产精品视频app | av一级免费 | 成人av电影在线播放 | 美女黄视频免费看 | 四虎成人av| 狠狠干狠狠久久 | 久久久国产一区二区三区四区小说 | 免费麻豆网站 | 在线观看免费观看在线91 | 日本久久片 | 欧美日韩精品免费观看 | 欧美精品一区在线 | 日韩免费电影一区二区 | 青草视频在线看 | 麻豆视频在线 | 91av在| 国产乱对白刺激视频在线观看女王 | 久草在线国产 | 天天躁天天操 | 色播99| 九九在线精品视频 | 三级在线视频观看 | 99re国产| 国产福利免费看 | 久久艹免费 | 天天综合网久久 | 久久久精品综合 | 91在线免费观看国产 | 久久国产经典视频 | 欧美精品资源 | 韩国av电影在线观看 | 麻豆网站免费观看 | 免费在线观看成人 | 日韩精品欧美专区 | 91免费在线视频 | 一区二区三区在线观看 | 亚洲精品tv久久久久久久久久 | 亚洲做受高潮欧美裸体 | 黄色在线成人 | 欧美精品v国产精品v日韩精品 | 久久精品人 | 精品亚洲午夜久久久久91 | 久草在线观看视频免费 | 久久综合婷婷综合 | 亚洲欧美乱综合图片区小说区 | 久草五月 | 久久久国产精品视频 | 激情欧美一区二区三区免费看 | 亚洲精品美女久久17c | 99久热在线精品视频成人一区 | 日本少妇久久久 | 久久黄色片 | 一级精品视频在线观看宜春院 | 免费看一及片 | 六月丁香在线观看 | www.天天干| 韩国av一区二区 | 伊人永久 | 手机av电影在线观看 | 婷婷电影在线观看 | 天天干,天天干 | 免费在线观看污网站 | 综合铜03| 白丝av在线 | 91 中文字幕 | 久久精品毛片 | 国产精品完整版 | 国内外激情视频 | 99热在线观看免费 | 精品视频在线播放 | av不卡中文| 亚洲精品视频在线观看网站 | 久久夜夜爽 | 久久五月婷婷丁香 | 日韩精品免费在线观看 | 国产精品一区电影 | 国产精品久久一区二区无卡 | 亚洲国产精品推荐 | 国产网红在线观看 | 91九色视频网站 | 91最新在线视频 | 色婷婷狠狠五月综合天色拍 | 在线亚洲天堂网 | 97成人精品区在线播放 | 91精品影视 | 亚洲精品乱码久久久一二三 | 欧美精品乱码99久久影院 | 国产精品一区二区av影院萌芽 | 人人澡视频| 九九在线播放 | 日本久久91| 久久精品国产精品 | 成人午夜影院 | 91黄色在线视频 | 99情趣网视频 | 99精品视频精品精品视频 | 在线免费黄色毛片 | 国产精品高潮呻吟久久av无 | 国产又黄又硬又爽 | 精品久久网站 | 亚洲干 | 久久久精品欧美一区二区免费 | 亚洲精品中文在线资源 | 亚洲国产精品久久久久 | 天天草天天爽 | 国产精品一区二区在线观看免费 | 日日麻批40分钟视频免费观看 | 国产精品正在播放 | 在线观看91视频 | 在线观看视频在线观看 | 91手机视频在线 | 在线免费观看羞羞视频 | 99视频这里有精品 | 五月天伊人| 成人av电影免费观看 | 色综合久久66| 久久99久久99精品免视看婷婷 | 成人av直播 | 国产91精品欧美 | 成人免费在线观看av | 成人动漫一区二区三区 | 日韩视频免费观看高清 | 一级黄色毛片 | 国产日本在线播放 | 国产在线精品一区二区不卡了 | 91九色九色 | av在线直接看| 国产96在线视频 | 免费观看成人av | 亚洲日本欧美 | 免费色网| 国产热re99久久6国产精品 | 免费看三级黄色片 | 久久久久久久久久影视 | 精品亚洲一区二区 | 在线观看国产日韩 | 天天草天天 | 国产第一福利 | 国产精品完整版 | 五月激情视频 | 999日韩| 日韩av不卡在线 | 手机看片福利 | 中文字幕在线观看完整版 | 国产黄色免费看 | 少妇高潮流白浆在线观看 | 六月丁香激情网 | 天天鲁天天干天天射 | 九九国产精品视频 | 久久精品久久久久久久 | 在线观看中文字幕一区二区 | 国产一级高清视频 | 69国产精品视频免费观看 | 99视频+国产日韩欧美 | 日韩精品中文字幕有码 | 99这里精品| 亚洲色图激情文学 | 欧美日韩一区二区三区在线观看视频 | 97视频免费在线看 | 伊人五月婷 | 亚洲精品视频在线观看免费视频 | av资源网在线播放 | 狂野欧美激情性xxxx | 日韩理论在线观看 | 亚洲精品国产精品国自 | 不卡的av在线 | 国产精品免费大片视频 | 天天激情站 | 国产亚洲精品久久久久秋 | 五月av在线 | 国产中文字幕三区 | 久久国内免费视频 | 特级片免费看 | 五月婷婷在线观看 | 日一日操一操 | 色综合久久久久久久久五月 | 欧美精品一二 | 中文字幕专区高清在线观看 | 在线激情av电影 | 在线你懂的视频 | 91桃色在线观看视频 | 亚洲综合成人在线 | 99超碰在线观看 | 91免费看黄 | 六月丁香激情综合 | 蜜臀久久99精品久久久无需会员 | 亚洲成av人片在线观看 | 2019精品手机国产品在线 | 国产视频久久 | 狠狠色丁香久久综合网 | 粉嫩av一区二区三区免费 | 四虎国产永久在线精品 | 日韩大片在线播放 | 91爱爱免费观看 | 9热精品| 国产免费一区二区三区最新 | 国产性xxxx| 麻豆传媒一区二区 | 色综合久久88色综合天天 | 91视频啊啊啊 | 麻豆一区二区 | 麻豆国产精品永久免费视频 | 九九久久精品视频 | 超碰国产在线 | 欧美99精品 | av观看免费在线 | 午夜久久久久久久久久久 | 欧美aa在线观看 | 91成年人视频| 在线观看国产日韩欧美 | 日韩精品中文字幕在线 | 亚洲午夜精品福利 | 成人黄色大片 | 欧美精品在线视频 | 日韩美一区二区三区 | 久久久久99精品成人片三人毛片 | 98久久| 狠狠操狠狠干天天操 | 99视频这里有精品 | 日韩欧美国产免费播放 | 国内精品久久久久影院优 | 午夜精品久久久久久久99 | 精品国产1区2区 | 国产免费国产 | 欧美做受69 | 久久视频精品在线 | 91成人看片 | 日韩激情久久 | 在线视频18在线视频4k | 不卡的av中文字幕 | 91人人揉日日捏人人看 | 99精品影视| 国产精品成人一区二区三区吃奶 | 国产精品1区2区 | 国产精品亚洲片夜色在线 | 亚洲精品国精品久久99热一 | 337p西西人体大胆瓣开下部 | 三级av在线免费观看 | 一本—道久久a久久精品蜜桃 | 亚洲激情在线播放 | 99国产一区二区三精品乱码 | 久久综合亚洲鲁鲁五月久久 | 一区二区三区中文字幕在线 | 免费观看视频的网站 | 日韩特级毛片 | 久精品在线观看 | www狠狠操| 亚洲va欧美 | 日本狠狠色 | 中文字幕一二三区 | 久久久久久久久久久久久久av | 韩日av一区二区 | 一区二区三区中文字幕在线 | 国产理论免费 | 亚洲人成人天堂h久久 | 国产一区二三区好的 | 国产在线色站 | 91高清视频 | 三级黄色片子 | 国产精品专区一 | 免费亚洲黄色 | 在线观看不卡视频 | 亚洲欧洲中文日韩久久av乱码 | 深夜福利视频在线观看 | 国产五月色婷婷六月丁香视频 | 欧日韩在线视频 | 久久小视频 | 久久久久久久毛片 | 深爱激情综合网 | 麻豆va一区二区三区久久浪 | 中文字幕在线视频精品 | 午夜免费电影院 | 黄污视频大全 | 日韩最新av | 欧美大片aaa| 日韩精品一区二区三区外面 | 国产精品久久久久一区二区三区 | 国产精品一区二区免费在线观看 | 中文在线资源 | 久久免费美女视频 | 精品不卡视频 | 韩国av在线| 91在线中字 | 国产女人18毛片水真多18精品 | 国产精品日韩欧美 | 久精品在线 | 免费在线色电影 | 91av综合| 在线久热 | 亚洲欧美日韩一区二区三区在线观看 | 免费在线看成人av | 欧美精品在线一区二区 | 国产精品麻豆果冻传媒在线播放 | 亚洲免费av片 | 成人三级黄色 | 黄色电影小说 | 亚洲综合在线一区二区三区 | 亚洲aⅴ久久精品 | 99精品国产一区二区三区麻豆 | 国产呻吟在线 | 97香蕉久久国产在线观看 | 亚洲精品免费观看视频 | 五月开心色 | 五月花婷婷 | 一区二区电影在线观看 | 最近中文字幕高清字幕在线视频 | 成人黄色电影在线观看 | 久久久精品影视 | 国产精品av在线免费观看 | 99久免费精品视频在线观看 | 狠狠色狠狠色合久久伊人 | 91资源在线观看 | 亚洲国产偷 | 日韩毛片在线免费观看 | 久久最新网址 | a黄色大片 | 黄色的视频网站 | 在线观看中文字幕一区 | 在线看黄色的网站 | 狠狠操狠狠干2017 | 国产一区二区在线免费 | 国产毛片在线 | 国产视频91在线 | 日韩在线观看中文字幕 | 婷婷色吧 | 黄色三级网站 | 五月天精品视频 | 久久精品欧美一区二区三区麻豆 | 久久久久久看片 | 午夜av影院 | 在线成人免费电影 | 国产精国产精品 | 在线看免费 | 亚洲少妇xxxx | 丁香花在线视频观看免费 | 欧美精品亚洲精品日韩精品 | 蜜臀av性久久久久蜜臀aⅴ涩爱 | 精品美女在线视频 | 激情网色| 91福利视频一区 | 操操操影院 | 蜜桃麻豆www久久囤产精品 | av网站免费线看精品 | 国产在线精品国自产拍影院 | 最近2019中文免费高清视频观看www99 | 超碰av在线播放 | 三级黄色免费片 | 天天天天爱天天躁 | 波多野结衣在线中文字幕 | 久久亚洲私人国产精品va | 日韩免费一级电影 | 久久视频这里有精品 | 97国产精品亚洲精品 | 国产69精品久久99的直播节目 | 欧美精品乱码久久久久久按摩 | 亚洲精品乱码久久久久久蜜桃动漫 | 黄色国产大片 | 国产黄色片一级 | 色诱亚洲精品久久久久久 | 国产精品久久久久影院日本 | 久久成人午夜视频 | 91免费国产在线观看 | 国产91精品久久久久久 | 亚洲综合欧美精品电影 | 天天爽网站 | 色综合久久久久综合体 | a爱爱视频| 五月婷婷色 | 97超碰在线免费 | 波多野结依在线观看 | 视频成人免费 | 久久久久成人免费 | 成人性生爱a∨ | 美女精品网站 | 一本色道久久精品 | 久久综合狠狠综合 | 国产传媒一区在线 | 亚洲女在线| 久久精品婷婷 | 六月婷婷久香在线视频 | 国产91亚洲精品 | 色www精品视频在线观看 | 国产一级片免费视频 | av片子在线观看 | av中文字幕网站 | 九色最新网址 | 精品成人网 | 亚州视频在线 | 日日夜夜精品免费观看 | 久久理论片 | 91麻豆国产 | 亚洲第一成网站 | 亚洲小视频在线 |