精华内容
下载资源
问答
  • 存量规划管控技术体系中的情景分析框架研究,戴锏,吕飞,当前,我国城市建设进入存量更新阶段,空间再开发过程中受到的影响因素越发复杂化,使原有以
  • 以未来经济社会可持续发展为目标,研究了社会发展、经济增长、经济结构变化、技术进步、产品替代和主要煤炭消费部门活动水平等煤炭需求的影响因素,建立了煤炭需求情景分析模型,对2010年和2020年的煤炭需求进行了情景...
  • Android系统源码情景分析pdf 1.源码驱动开发技巧 2.Android 源码查看技巧 3.Android高级技术学习
  • windows内核情景分析

    千次阅读 2014-04-21 18:01:01
    windows内核情景分析 --APC,有需要的朋友可以参考下。 APC:异步过程调用。这是一种常见的技术。前面进程启动的初始过程就是:主线程在内核构造好运行环境后,从KiThreadStartup开始运行,然后调用...

    http://www.aichengxu.com/article/%E7%B3%BB%E7%BB%9F%E4%BC%98%E5%8C%96/17531_12.html

    windows内核情景分析 --APC,有需要的朋友可以参考下。


    APC:异步过程调用。这是一种常见的技术。前面进程启动的初始过程就是:主线程在内核构造好运行环境后,从KiThreadStartup开始运行,然后调用PspUserThreadStartup,在该线程的apc队列中插入一个APC:LdrInitializeThunk,这样,当PspUserThreadStartup返回后,正式退回用户空间的总入口BaseProcessStartThunk前,会执行中途插入的那个apc,完成进程的用户空间初始化工作(链接dll的加载等)

    可见:APC的执行时机之一就是从内核空间返回用户空间的前夕。也即在返回用户空间前,会“中断”那么一下。因此,APC就是一种软中断。

    除了这种APC用途外,应用程序中也经常使用APC。如Win32APIReadFileEx就可以使用APC机制来实现异步读写文件的功能。

    BOOL//源码
    ReadFileEx(INHANDLEhFile,
    INLPVOIDlpBuffer,
    INDWORDnNumberOfBytesToReadOPTIONAL,
    INLPOVERLAPPEDlpOverlapped,//完成结果
    INLPOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine)//预置APC将调用的完成例程
    {
    LARGE_INTEGEROffset;
    NTSTATUSStatus;
    Offset.u.LowPart=lpOverlapped->Offset;
    Offset.u.HighPart=lpOverlapped->OffsetHigh;
    lpOverlapped->Internal=STATUS_PENDING;
    Status=NtReadFile(hFile,
    NULL,//Event=NULL
    ApcRoutine,//这个是内部预置的APC例程
    lpCompletionRoutine,//APC的Context
    (PIO_STATUS_BLOCK)lpOverlapped,
    lpBuffer,
    nNumberOfBytesToRead,
    &Offset,
    NULL);//Key=NULL
    if(!NT_SUCCESS(Status))
    {
    SetLastErrorByStatus(Status);//
    returnFALSE;
    }
    returnTRUE;
    }
    VOIDApcRoutine(PVOIDApcContext,//指向用户提供的完成例程
    _IO_STATUS_BLOCK*IoStatusBlock,//完成结果
    ULONGReserved)
    {
    LPOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine=ApcContext;
    DWORDdwErrorCode=RtlNtStatusToDosError(IoStatusBlock->Status);
    //调用用户提供的完成例程
    lpCompletionRoutine(dwErrorCode,
    IoStatusBlock->Information,
    (LPOVERLAPPED)IoStatusBlock);
    }


    因此,应用层的用户提供的完成例程实际上是作为APC函数进行的,它运行在APC_LEVELirql

    NTSTATUS
    NtReadFile(INHANDLEFileHandle,
    INHANDLEEventOPTIONAL,
    INPIO_APC_ROUTINEApcRoutineOPTIONAL,//内置的APC
    INPVOIDApcContextOPTIONAL,//应用程序中用户提供的完成例程
    OUTPIO_STATUS_BLOCKIoStatusBlock,
    OUTPVOIDBuffer,
    INULONGLength,
    INPLARGE_INTEGERByteOffsetOPTIONAL,
    INPULONGKeyOPTIONAL)
    {
    …
    Irp=IoAllocateIrp(DeviceObject->StackSize,FALSE);//分配一个irp
    Irp->Overlay.AsynchronousParameters.UserApcRoutine=ApcRoutine;//记录
    Irp->Overlay.AsynchronousParameters.UserApcContext=ApcContext;//记录
    …
    Status=IoCallDriver(DeviceObject,Irp);//把这个构造的irp发给底层驱动
    …
    }
    

    当底层驱动完成这个irp后,会调用IoCompleteRequest完成掉这个irp,这个IoCompleteRequest实际上内部最终调用IopCompleteRequest来做一些完成时的工作

    VOID
    IopCompleteRequest(INPKAPCApc,
    INPKNORMAL_ROUTINE*NormalRoutine,
    INPVOID*NormalContext,
    INPVOID*SystemArgument1,
    INPVOID*SystemArgument2)
    {
    …
    if(Irp->Overlay.AsynchronousParameters.UserApcRoutine)//上面传入的APC
    {
    //构造一个APC
    KeInitializeApc(&Irp->Tail.Apc,KeGetCurrentThread(),CurrentApcEnvironment,
    IopFreeIrpKernelApc,
    IopAbortIrpKernelApc,
    (PKNORMAL_ROUTINE)Irp->Overlay.AsynchronousParameters.UserApcRoutine,
    Irp->RequestorMode,
    Irp->Overlay.AsynchronousParameters.UserApcContext);//应用层的完成例程
    //插入到APC队列
    KeInsertQueueApc(&Irp->Tail.Apc,Irp->UserIosb,NULL,2);
    }//endif
    …
    }
    

    如上,ReadFileEx函数的异步APC机制是:在这个请求完成后,IO管理器会将一个APC插入队列中,然后

    在返回用户空间前夕调用那个内置APC,最终调用应用层用户提供的完成例程。

    明白了APC大致原理后,现在详细看一下APC的工作原理。

    APC分两种,用户APC、内核APC。前者指在用户空间执行的APC,后者指在内核空间执行的APC。

    先看一下内核为支持APC机制提供的一些基础结构设施。

    Typedefstruct_KTHREAD
    {
    …
    KAPC_STATEApcState;//表示本线程当前使用的APC状态(即apc队列的状态)
    KAPC_STATESavedApcState;//表示保存的原apc状态,备份用
    KAPC_STATE*ApcStatePointer[2];//状态数组,包含两个指向APC状态的指针
    UCHARApcStateIndex;//0或1,指当前的ApcState在ApcStatePointer数组中的索引位置
    UCHARApcQueueable;//指本线程的APC队列是否可插入apc
    ULONGKernelApcDisable;//禁用标志
    //专用于挂起操作的APC(这个函数在线程一得到调度就重新进入等待态,等待挂起计数减到0)
    KAPCSuspendApc;
    …
    }KTHREAD;
    Typedefstruct_KAPC_STATE//APC队列的状态描述符
    {
    LIST_EBTRYApcListHead[2];//每个线程有两个apc队列
    PKPROCESSProcess;//当前线程所在的进程
    BOOLKernelApcInProgress;//指示本线程是否当前正在内核apc
    BOOLKernelApcPending;//表示内核apc队列中是否有apc
    BOOLUserApcPending;//表示用户apc队列中是否apc
    }
    Typedefenum_KAPC_ENVIRONMENT
    {
    OriginalApcEnvironment,//0,状态数组索引
    AttachedApcEnvironment;//1,状态数组索引
    CurrentApcEnvironment;//2,表示使用当前apc状态
    CurrentApcEnvironment;//3,表示使用插入apc时那时的线程的apc状态
    }
    

    一个线程可以挂靠到其他进程的地址空间中,因此,一个线程的状态分两种:常态、挂靠态。

    常态下,状态数组中0号元素指向ApcState(即当前apc状态),1号元素指向SavedApcState(非当前apc状态);挂靠态下,两个元素的指向刚好相反。但无论如何,KTHREAD结构中的ApcStateIndex总是指当前状态的位置,ApcState则总是表示线程当前使用的apc状态。

    于是有:

    #define PsGetCurrentProcess IoGetCurrentProces
    PEPROCESSIoGetCurrentProces()
    {
    Return PsGetCurrentThread()->Tcb.ApcState.Process;//ApcState中的进程字段总是表示当前进程
    }
    

    不管当前线程是处于常态还是挂靠态下,它都有两个apc队列,一个内核,一个用户。把apc插入对应的队列后就可以在恰当的时机得到执行。注意:每当一个线程挂靠到其他进程时,挂靠初期,两个apc队列都会变空。下面看下每个apc本身的结构

    typedefstruct_KAPC
    {
    UCHARType;//结构体的类型
    UCHARSize;//结构体的大小
    struct_KTHREAD*Thread;//目标线程
    LIST_ENTRYApcListEntry;//用来挂入目标apc队列
    PKKERNEL_ROUTINEKernelRoutine;//该apc的内核总入口
    PKRUNDOWN_ROUTINERundownRoutine;
    PKNORMAL_ROUTINENormalRoutine;//该apc的用户空间总入口或者用户真正的内核apc函数
    PVOIDNormalContext;//真正用户提供的用户空间apc函数或者用户真正的内核apc函数的context*
    PVOIDSystemArgument1;//挂入时的附加参数1。真正用户apc的context*
    PVOIDSystemArgument2;//挂入时的附加参数2
    CCHARApcStateIndex;//指要挂入目标线程的哪个状态时的apc队列
    KPROCESSOR_MODEApcMode;//指要挂入用户apc队列还是内核apc队列
    BOOLEANInserted;//表示本apc是否已挂入队列
    }KAPC,*PKAPC;
    

    注意:

    若这个apc是内核apc,那么NormalRoutine表示用户自己提供的内核apc函数,NormalContext则是该apc函数的context*,SystemArgument1与SystemArgument2表示插入队列时的附加参数

    若这个apc是用户apc,那么NormalRoutine表示该apc的用户空间总apc函数,NormalContext才是真正用户自己提供的用户空间apc函数,SystemArgument1则表示该真正apc的context*。(一切错位了)

    //下面这个Win32API可以用来手动插入一个apc到指定线程的用户apc队列中
    DWORD
    QueueUserAPC(PAPCFUNCpfnAPC,HANDLEhThread,ULONG_PTRdwData)
    {
    NTSTATUSStatus;
    //调用对应的系统服务
    Status=NtQueueApcThread(hThread,//目标线程
    IntCallUserApc,//用户空间中的总apc入口
    pfnAPC,//用户自己真正提供的apc函数
    (PVOID)dwData,//SysArg1=context*
    NULL);//SysArg2=NULL
    if(!NT_SUCCESS(Status))
    {
    SetLastErrorByStatus(Status);
    return0;
    }
    return1;
    }
    NTSTATUS
    NtQueueApcThread(INHANDLEThreadHandle,//目标线程
    INPKNORMAL_ROUTINEApcRoutine,//用户空间中的总apc
    INPVOIDNormalContext,//用户自己真正的apc函数
    INPVOIDSystemArgument1,//用户自己apc的context*
    INPVOIDSystemArgument2)//其它
    {
    PKAPCApc;
    PETHREADThread;
    NTSTATUSStatus=STATUS_SUCCESS;
    Status=ObReferenceObjectByHandle(ThreadHandle,THREAD_SET_CONTEXT,PsThreadType,
    ExGetPreviousMode(),(PVOID)&Thread,NULL);
    //分配一个apc结构,这个结构最终在PspQueueApcSpecialApc中释放
    Apc=ExAllocatePoolWithTag(NonPagedPool|POOL_QUOTA_FAIL_INSTEAD_OF_RAISE,
    sizeof(KAPC),TAG_PS_APC);
    //构造一个apc
    KeInitializeApc(Apc,
    &Thread->Tcb,//目标线程
    OriginalApcEnvironment,//目标apc状态(此服务固定为OriginalApcEnvironment)
    PspQueueApcSpecialApc,//内核apc总入口
    NULL,//RundownRounine=NULL
    ApcRoutine,//用户空间的总apc
    UserMode,//此系统服务固定插入到用户apc队列
    NormalContext);//用户自己真正的apc函数
    //插入到目标线程的用户apc队列
    KeInsertQueueApc(Apc,
    SystemArgument1,//插入时的附加参数1,此处为用户自己apc的context*
    SystemArgument2,//插入时的附加参数2
    IO_NO_INCREMENT)//表示不予调整目标线程的调度优先级
    returnStatus;
    }
    //这个函数用来构造一个要插入指定目标队列的apc对象
    VOID
    KeInitializeApc(INPKAPCApc,
    INPKTHREADThread,//目标线程
    INKAPC_ENVIRONMENTTargetEnvironment,//目标线程的目标apc状态
    INPKKERNEL_ROUTINEKernelRoutine,//内核apc总入口
    INPKRUNDOWN_ROUTINERundownRoutineOPTIONAL,
    INPKNORMAL_ROUTINENormalRoutine,//用户空间的总apc
    INKPROCESSOR_MODEMode,//要插入用户apc队列还是内核apc队列
    INPVOIDContext)//用户自己真正的apc函数
    {
    Apc->Type=ApcObject;
    Apc->Size=sizeof(KAPC);
    if(TargetEnvironment==CurrentApcEnvironment)//CurrentApcEnvironment表示使用当前apc状态
    Apc->ApcStateIndex=Thread->ApcStateIndex;
    else
    Apc->ApcStateIndex=TargetEnvironment;
    Apc->Thread=Thread;
    Apc->KernelRoutine=KernelRoutine;
    Apc->RundownRoutine=RundownRoutine;
    Apc->NormalRoutine=NormalRoutine;
    if(NormalRoutine)//if提供了用户空间总apc入口
    {
    Apc->ApcMode=Mode;
    Apc->NormalContext=Context;
    }
    Else//若没提供,肯定是内核模式
    {
    Apc->ApcMode=KernelMode;
    Apc->NormalContext=NULL;
    }
    Apc->Inserted=FALSE;//表示初始构造后,尚未挂入apc队列
    }
    BOOLEAN
    KeInsertQueueApc(INPKAPCApc,INPVOIDSystemArgument1,INPVOIDSystemArgument2,
    INKPRIORITYPriorityBoost)
    {
    PKTHREADThread=Apc->Thread;
    KLOCK_QUEUE_HANDLEApcLock;
    BOOLEANState=TRUE;
    KiAcquireApcLock(Thread,&ApcLock);//插入过程需要独占队列
    if(!(Thread->ApcQueueable)||(Apc->Inserted))//检查队列是否可以插入apc
    State=FALSE;
    else
    {
    Apc->SystemArgument1=SystemArgument1;//记录该apc的附加插入时的参数
    Apc->SystemArgument2=SystemArgument2;//记录该apc的附加插入时的参数
    Apc->Inserted=TRUE;//标记为已插入队列
    //插入目标线程的目标apc队列(如果目标线程正处于睡眠状态,可能会唤醒它)
    KiInsertQueueApc(Apc,PriorityBoost);
    }
    KiReleaseApcLockFromDpcLevel(&ApcLock);
    KiExitDispatcher(ApcLock.OldIrql);//可能引发一次线程切换,以立即切换到目标线程执行apc
    returnState;
    }
    VOIDFASTCALL
    KiInsertQueueApc(INPKAPCApc,INKPRIORITYPriorityBoost)//唤醒目标线程后的优先级增量
    {
    PKTHREADThread=Apc->Thread;
    BOOLEANRequestInterrupt=FALSE;
    if(Apc->ApcStateIndex==InsertApcEnvironment)//if要动态插入到当前的apc状态队列
    Apc->ApcStateIndex=Thread->ApcStateIndex;
    ApcState=Thread->ApcStatePointer[(UCHAR)Apc->ApcStateIndex];//目标状态
    ApcMode=Apc->ApcMode;
    //先插入apc到指定位置
    /*插入位置的确定:分三种情形
    *1)KernelAPCwithNormalRoutineorUserAPC:PutitattheendoftheList
    *2)UserAPCwhichisPsExitSpecialApc:PutitatthefrontoftheList
    *3)KernelAPCwithoutNormalRoutine:PutitattheendoftheNo-NormalRoutineKernelAPClist
    */
    if(Apc->NormalRoutine)//有NormalRoutine的APC都插入尾部(用户模式发来的线程终止APC除外)
    {
    if((ApcMode==UserMode)&&(Apc->KernelRoutine==PsExitSpecialApc))
    {
    Thread->ApcState.UserApcPending=TRUE;
    InsertHeadList(&ApcState->ApcListHead[ApcMode],&Apc->ApcListEntry);
    }
    else
    InsertTailList(&ApcState->ApcListHead[ApcMode],&Apc->ApcListEntry);
    }
    Else//无NormalRoutine的特殊类APC(内核APC),少见
    {
    ListHead=&ApcState->ApcListHead[ApcMode];
    NextEntry=ListHead->Blink;
    while(NextEntry!=ListHead)
    {
    QueuedApc=CONTAINING_RECORD(NextEntry,KAPC,ApcListEntry);
    if(!QueuedApc->NormalRoutine)break;
    NextEntry=NextEntry->Blink;
    }
    InsertHeadList(NextEntry,&Apc->ApcListEntry);//插在这儿
    }
    //插入到相应的位置后,下面检查Apc状态是否匹配
    if(Thread->ApcStateIndex==Apc->ApcStateIndex)//if插到了当前apc状态的apc队列中
    {
    if(Thread==KeGetCurrentThread())//if就是给当前线程发送的apc
    {
    ASSERT(Thread->State==Running);//当前线程肯定没有睡眠,这不废话吗?
    if(ApcMode==KernelMode)
    {
    Thread->ApcState.KernelApcPending=TRUE;
    if(!Thread->SpecialApcDisable)//发出一个apc中断,待下次降低irql时将执行apc
    HalRequestSoftwareInterrupt(APC_LEVEL);//关键
    }
    }
    Else//给其他线程发送的内核apc
    {
    KiAcquireDispatcherLock();
    if(ApcMode==KernelMode)
    {
    Thread->ApcState.KernelApcPending=TRUE;
    if(Thread->State==Running)
    RequestInterrupt=TRUE;//需要给它发出一个apc中断
    elseif((Thread->State==Waiting)&&(Thread->WaitIrql==PASSIVE_LEVEL)&&
    !(Thread->SpecialApcDisable)&&(!(Apc->NormalRoutine)||
    (!(Thread->KernelApcDisable)&&
    !(Thread->ApcState.KernelApcInProgress))))
    {
    Status=STATUS_KERNEL_APC;
    KiUnwaitThread(Thread,Status,PriorityBoost);//临时唤醒目标线程执行apc
    }
    elseif(Thread->State==GateWait)…
    }
    elseif((Thread->State==Waiting)&&(Thread->WaitMode==UserMode)&&
    ((Thread->Alertable)||(Thread->ApcState.UserApcPending)))
    {
    Thread->ApcState.UserApcPending=TRUE;
    Status=STATUS_USER_APC;
    KiUnwaitThread(Thread,Status,PriorityBoost);//强制唤醒目标线程
    }
    KiReleaseDispatcherLockFromDpcLevel();
    KiRequestApcInterrupt(RequestInterrupt,Thread->NextProcessor);
    }
    }
    }
    

    如上,这个函数既可以给当前线程发送apc,也可以给目标线程发送apc。若给当前线程发送内核apc时,会立即请求发出一个apc中断。若给其他线程发送apc时,可能会唤醒目标线程。

    APC函数的执行时机:

    回顾一下从内核返回用户时的流程:

    KiSystemService()//int2e的isr,内核服务函数总入口,注意这个函数可以嵌套、递归!!!
    {
    SaveTrap();//保存trap现场
    Sti//开中断
    ---------------上面保存完寄存器等现场后,开始查SST表调用系统服务------------------
    FindTableCall();
    ---------------------------------调用完系统服务函数后------------------------------
    Moveesp,kthread.TrapFrame;//将栈顶回到trap帧结构体处
    Cli//关中断
    If(上次模式==UserMode)
    {
    CallKiDeliverApc//遍历执行本线程的内核APC和用户APC队列中的所有APC函数
    清理Trap帧,恢复寄存器现场
    Iret//返回用户空间
    }
    Else
    {
    返回到原call处后面的那条指令处
    }
    }
    不光是从系统调用返回用户空间要扫描执行apc,从异常和中断返回用户空间也同样需要扫描执行。

    现在我们只看从系统调用返回时apc的执行过程。

    上面是伪代码,实际的从Cli后面的代码,是下面这样的。

    Test dword ptr[ebp+KTRAP_FRAME_EFLAGS],EFLAGS_V86_MASK//检查eflags是否标志运行在V86模式
    Jnz 1//若运行在V86模式,那么上次模式肯定是从用户空间进入内核的,跳过下面的检查
    Test byte ptr[ebp+KTRAP_FRAME_CS],1
    Je 2//若上次模式不是用户模式,跳过下面的流程,不予扫描apc
    1:
    Movebx,PCR[KPCR_CURRENT_THREAD]//ebx=KTHREAD*(当前线程对象的地址)
    Movbyteptr[ebx+KTHREAD_ALERTED],0//kthread.Alert修改为不可提醒
    Cmpbyteptr[ebx+KTHREAD_PENDING_USER_APC],0
    Je2//如果当前线程的用户apc队列为空,直接跳过
    Move bx,ebp//ebx=TrapFrame帧的地址
    Mov [ebx,KTRAP_FRAME_EAX],eax//保存
    Mov ecx,APC_LEVEL
    Call KfRaiseIrql//callKfRaiseIrql(APC_LEVEL)
    Push eax//保存提升irql之前的irql
    Sti
    Push ebx//TrapFrame帧的地址
    Push NULL
    Push UserMode
    Call KiDeliverApc//callKiDeliverApc(UserMode,NULL,TrapFrame*)
    Pop ecx//ecx=之前的irql
    Call KfLowerIrql//callKfLowerIrql(之前的irql)
    Mov eax,[ebx,KTRAP_FRAME_EAX]//恢复eax
    Cli
    Jmp1//再次跳回1处循环,扫描apc队列
    …
    

    关键的函数是KiDeliverApc,这个函数用来真正扫描apc队列执行所有apc,我们看:

    VOID
    KiDeliverApc(INKPROCESSOR_MODEDeliveryMode,//指要执行哪个apc队列中的函数
    INPKEXCEPTION_FRAMEExceptionFrame,//传入的是NULL
    INPKTRAP_FRAMETrapFrame)//即将返回用户空间前的Trap现场帧
    {
    PKTHREADThread=KeGetCurrentThread();
    PKPROCESSProcess=Thread->ApcState.Process;
    OldTrapFrame=Thread->TrapFrame;
    Thread->TrapFrame=TrapFrame;
    Thread->ApcState.KernelApcPending=FALSE;
    if(Thread->SpecialApcDisable)gotoQuickie;
    //先固定执行掉内核apc队列中的所有apc函数
    while(!IsListEmpty(&Thread->ApcState.ApcListHead[KernelMode]))
    {
    KiAcquireApcLockAtApcLevel(Thread,&ApcLock);//锁定apc队列
    ApcListEntry=Thread->ApcState.ApcListHead[KernelMode].Flink;//队列头部中的apc
    Apc=CONTAINING_RECORD(ApcListEntry,KAPC,ApcListEntry);
    KernelRoutine=Apc->KernelRoutine;//内核总apc函数
    NormalRoutine=Apc->NormalRoutine;//用户自己真正的内核apc函数
    NormalContext=Apc->NormalContext;//真正内核apc函数的context*
    SystemArgument1=Apc->SystemArgument1;
    SystemArgument2=Apc->SystemArgument2;
    if(NormalRoutine==NULL)//称为SpecialApc,少见
    {
    RemoveEntryList(ApcListEntry);//关键,移除队列
    Apc->Inserted=FALSE;
    KiReleaseApcLock(&ApcLock);
    //执行内核中的总apc函数
    KernelRoutine(Apc,&NormalRoutine,&NormalContext,
    &SystemArgument1,&SystemArgument2);
    }
    Else//典型,一般程序员都会提供一个自己的内核apc函数
    {
    if((Thread->ApcState.KernelApcInProgress)||(Thread->KernelApcDisable))
    {
    KiReleaseApcLock(&ApcLock);
    gotoQuickie;
    }
    RemoveEntryList(ApcListEntry);//关键,移除队列
    Apc->Inserted=FALSE;
    KiReleaseApcLock(&ApcLock);
    //执行内核中的总apc函数
    KernelRoutine(Apc,
    &NormalRoutine,//注意,内核中的总apc可能会在内部修改NormalRoutine
    &NormalContext,
    &SystemArgument1,
    &SystemArgument2);
    if(NormalRoutine)//如果内核总apc没有修改NormalRoutine成NULL
    {
    Thread->ApcState.KernelApcInProgress=TRUE;//标记当前线程正在执行内核apc
    KeLowerIrql(PASSIVE_LEVEL);
    //直接调用用户提供的真正内核apc函数
    NormalRoutine(NormalContext,SystemArgument1,SystemArgument2);
    KeRaiseIrql(APC_LEVEL,&ApcLock.OldIrql);
    }
    Thread->ApcState.KernelApcInProgress=FALSE;
    }
    }
    //上面的循环,执行掉所有内核apc函数后,下面开始执行用户apc队列中的第一个apc
    if((DeliveryMode==UserMode)&&
    !(IsListEmpty(&Thread->ApcState.ApcListHead[UserMode]))&&
    (Thread->ApcState.UserApcPending))
    {
    KiAcquireApcLockAtApcLevel(Thread,&ApcLock);//锁定apc队列
    Thread->ApcState.UserApcPending=FALSE;
    ApcListEntry=Thread->ApcState.ApcListHead[UserMode].Flink;//队列头
    Apc=CONTAINING_RECORD(ApcListEntry,KAPC,ApcListEntry);
    KernelRoutine=Apc->KernelRoutine;//内核总apc函数
    NormalRoutine=Apc->NormalRoutine;//用户空间的总apc函数
    NormalContext=Apc->NormalContext;//用户真正的用户空间apc函数
    SystemArgument1=Apc->SystemArgument1;//真正apc的context*
    SystemArgument2=Apc->SystemArgument2;
    RemoveEntryList(ApcListEntry);//关键,移除队列
    Apc->Inserted=FALSE;
    KiReleaseApcLock(&ApcLock);
    KernelRoutine(Apc,
    &NormalRoutine,//注意,内核中的总apc可能会在内部修改NormalRoutine
    &NormalContext,
    &SystemArgument1,
    &SystemArgument2);
    if(!NormalRoutine)
    KeTestAlertThread(UserMode);
    Else//典型,准备提前回到用户空间调用用户空间的总apc函数
    {
    KiInitializeUserApc(ExceptionFrame,//NULL
    TrapFrame,//Trap帧的地址
    NormalRoutine,//用户空间的总apc函数
    NormalContext,//用户真正的用户空间apc函数
    SystemArgument1,//真正apc的context*
    SystemArgument2);
    }
    }
    Quickie:
    Thread->TrapFrame=OldTrapFrame;
    }
    

    如上,这个函数既可以用来投递处理内核apc函数,也可以用来投递处理用户apc队列中的函数。

    特别的,当要调用这个函数投递处理用户apc队列中的函数时,它每次只处理一个用户apc。

    由于正式回到用户空间前,会循环调用这个函数。因此,实际的处理顺序是:

    扫描执行内核apc队列所有apc->执行用户apc队列中一个apc->再次扫描执行内核apc队列所有apc->执行用户apc队列中下一个apc->再次扫描执行内核apc队列所有apc->再次执行用户apc队列中下一个apc如此循环,直到将用户apc队列中的所有apc都执行掉。

    执行用户apc队列中的apc函数与内核apc不同,因为用户apc队列中的apc函数自然是要在用户空间中执行的,而KiDeliverApc这个函数本身位于内核空间,因此,不能直接调用用户apc函数,需要‘提前’回到用户空间去执行队列中的每个用户apc,然后重新返回内核,再次扫描整个内核apc队列,再执行用户apc队列中遗留的下一个用户apc。如此循环,直至执行完所有用户apc后,才‘正式’返回用户空间。

    下面的函数就是用来为执行用户apc做准备的。

    VOID
    KiInitializeUserApc(INPKEXCEPTION_FRAMEExceptionFrame,
    INPKTRAP_FRAMETrapFrame,//原真正的断点现场帧
    INPKNORMAL_ROUTINENormalRoutine,
    INPVOIDNormalContext,
    INPVOIDSystemArgument1,
    INPVOIDSystemArgument2)
    {
    Context.ContextFlags=CONTEXT_FULL|CONTEXT_DEBUG_REGISTERS;
    //将原真正的Trap帧打包保存在一个Context结构中
    KeTrapFrameToContext(TrapFrame,ExceptionFrame,&Context);
    _SEH2_TRY
    {
    AlignedEsp=Context.Esp&~3;//对齐4B
    //为用户空间中KiUserApcDisatcher函数的参数腾出空间(4个参数+CONTEXT+8B的seh节点)
    ContextLength=CONTEXT_ALIGNED_SIZE+(4*sizeof(ULONG_PTR));
    Stack=((AlignedEsp-8)&~3)-ContextLength;//8表示seh节点的大小
    //模拟压入KiUserApcDispatcher函数的4个参数
    *(PULONG_PTR)(Stack+0*sizeof(ULONG_PTR))=(ULONG_PTR)NormalRoutine;
    *(PULONG_PTR)(Stack+1*sizeof(ULONG_PTR))=(ULONG_PTR)NormalContext;
    *(PULONG_PTR)(Stack+2*sizeof(ULONG_PTR))=(ULONG_PTR)SystemArgument1;
    *(PULONG_PTR)(Stack+3*sizeof(ULONG_PTR))=(ULONG_PTR)SystemArgument2;
    //将原真正trap帧保存在用户栈的一个CONTEXT结构中,方便以后还原
    RtlCopyMemory((Stack+(4*sizeof(ULONG_PTR))),&Context,sizeof(CONTEXT));
    //强制修改当前Trap帧中的返回地址与用户栈地址(偏离原来的返回路线)
    TrapFrame->Eip=(ULONG)KeUserApcDispatcher;//关键,新的返回断点地址
    TrapFrame->HardwareEsp=Stack;//关键,新的用户栈顶
    TrapFrame->SegCs=Ke386SanitizeSeg(KGDT_R3_CODE,UserMode);
    TrapFrame->HardwareSegSs=Ke386SanitizeSeg(KGDT_R3_DATA,UserMode);
    TrapFrame->SegDs=Ke386SanitizeSeg(KGDT_R3_DATA,UserMode);
    TrapFrame->SegEs=Ke386SanitizeSeg(KGDT_R3_DATA,UserMode);
    TrapFrame->SegFs=Ke386SanitizeSeg(KGDT_R3_TEB,UserMode);
    TrapFrame->SegGs=0;
    TrapFrame->ErrCode=0;
    TrapFrame->EFlags=Ke386SanitizeFlags(Context.EFlags,UserMode);
    if(KeGetCurrentThread()->Iopl)TrapFrame->EFlags|=EFLAGS_IOPL;
    }
    _SEH2_EXCEPT((RtlCopyMemory(&SehExceptRecord,_SEH2_GetExceptionInformation()->ExceptionRecord,sizeof(EXCEPTION_RECORD)),EXCEPTION_EXECUTE_HANDLER))
    {
    SehExceptRecord.ExceptionAddress=(PVOID)TrapFrame->Eip;
    KiDispatchException(&SehExceptRecord,ExceptionFrame,TrapFrame,UserMode,TRUE);
    }
    _SEH2_END;
    }
    

    至于为什么要放在一个try块中保护,是因为用户空间中的栈地址,谁也无法保证会不会出现崩溃。

    如上,这个函数修改返回地址,回到用户空间中的KiUserApcDisatcher函数处去。然后把原trap帧保存在用户栈中。由于KiUserApcDisatcher这个函数有参数,所以需要模拟压入这个函数的参数,这样,当返回到用户空间时,就仿佛是在调用这个函数。看下那个函数的代码:

    KiUserApcDisatcher(NormalRoutine,
    NormalContext,
    SysArg1,
    SysArg2
    )
    {
    Lea eax,[esp+CONTEXT_ALIGNED_SIZE+16]//eax指向seh异常节点的地址
    Mov ecx,fs:[TEB_EXCEPTION_LIST]
    Mov edx,offsetKiUserApcExceptionHandler
    --------------------------------------------------------------------------------------
    Mov [eax],ecx//seh节点的next指针成员
    Mov [eax+4],edx//she节点的handler函数指针成员
    Mov fs:[TEB_EXCEPTION_LIST],eax
    --------------------上面三条指令在栈中构造一个8B的标准seh节点-----------------------
    Pop eax//eax=NormalRoutine(即IntCallUserApc这个总apc函数)
    Lea edi,[esp+12]//edi=栈中保存的CONTEXT结构的地址
    Call eax//相当于callIntCallUserApc(NormalContext,SysArg1,SysArg2)
    Mov ecx,[edi+CONTEXT_ALIGNED_SIZE]
    Mov fs:[TEB_EXCEPTION_LIST],ecx//撤销栈中的seh节点
    Push TRUE//表示回到内核后需要继续检测执行用户apc队列中的apc函数
    Push edi//传入原栈帧的CONTEXT结构的地址给这个函数,以做恢复工作
    Call NtContinue//调用这个函数重新进入内核(注意这个函数正常情况下是不会返回到下面的)
    ----------------------------------华丽的分割线-------------------------------------------
    Mov esi,eax
    Push esi
    Call RtlRaiseStatus//若ZwContinue返回了,那一定是内部出现了异常
    Jmp StatusRaiseApc
    Ret16
    }
    

    如上,每当要执行一个用户空间apc时,都会‘提前’偏离原来的路线返回用户空间的这个函数处去执行用户的apc。在执行这个函数前,会先构造一个seh节点,也即相当于把这个函数的调用放在try块中保护。这个函数内部会调用IntCallUserApc,执行完真正的用户apc函数后,调用ZwContinue重返内核。

    VoidCALLBACK//用户空间的总apc函数
    IntCallUserApc(void*RealApcFunc,void*SysArg1,void*SysArg2)
    {
    (*RealApcFunc)(SysArg1);//也即调用RealApcFunc(void*context)
    }
    NTSTATUSNtContinue(CONTEXT*Context,//原真正的TraFrame
    BOOLTestAlert//指示是否继续执行用户apc队列中的apc函数
    )
    {
    Push ebp//此时ebp=本系统服务自身的TrapFrame地址
    Mov ebx,PCR[KPCR_CURRENT_THREAD]//ebx=当前线程的KTHREAD对象地址
    Mov edx,[ebp+KTRAP_FRAME_EDX]//注意TrapFrame中的这个edx字段不是用来保存edx的
    Mov [ebx+KTHREAD_TRAP_FRAME],edx//将当前的TrapFrame改为上一个TrapFrame的地址
    Mov ebp,esp
    Mob eax,[ebp]//eax=本系统服务自身的TrapFrame地址
    Mov ecx,[ebp+8]/本函数的第一个参数,即Context
    Push eax
    Push NULL
    Push ecx
    Call KiContinue//callKiContinue(Context*,NULL,TrapFrame*)
    Or eax,eax
    Jnz error
    Cmp dword ptr [ebp+12],0//检查TestAlert参数的值
    Je DontTest
    Mov al,[ebx+KTHREAD_PREVIOUS_MODE]
    Push eax
    Call KeTestAlertThread//检测用户apc队列是否为空
    DontTest:
    Pop ebp
    Mov esp,ebp
    Jmp KiServiceExit2//返回用户空间(返回前,又会去扫描执行apc队列中的下一个用户apc)
    }
    NTSTATUS
    KiContinue(INPCONTEXTContext,//原来的断点现场
    INPKEXCEPTION_FRAMEExceptionFrame,
    INPKTRAP_FRAMETrapFrame)//NtContinue自身的TrapFrame地址
    {
    NTSTATUSStatus=STATUS_SUCCESS;
    KIRQLOldIrql=APC_LEVEL;
    KPROCESSOR_MODEPreviousMode=KeGetPreviousMode();
    if(KeGetCurrentIrql()<APC_LEVEL)
    KeRaiseIrql(APC_LEVEL,&OldIrql);
    _SEH2_TRY
    {
    if(PreviousMode!=KernelMode)
    KiContinuePreviousModeUser(Context,ExceptionFrame,TrapFrame);//恢复成原TrapFrame
    else
    {
    KeContextToTrapFrame(Context,ExceptionFrame,TrapFrame,Context->ContextFlags,
    KernelMode);//恢复成原TrapFrame
    }
    }
    _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
    {
    Status=_SEH2_GetExceptionCode();
    }
    _SEH2_END;
    if(OldIrql<APC_LEVEL)
    KeLowerIrql(OldIrql);
    returnStatus;
    }
    VOID
    KiContinuePreviousModeUser(INPCONTEXTContext,//原来的断点现场
    INPKEXCEPTION_FRAMEExceptionFrame,
    INPKTRAP_FRAMETrapFrame)//NtContinue自身的TrapFrame地址
    {
    CONTEXTLocalContext;
    ProbeForRead(Context,sizeof(CONTEXT),sizeof(ULONG));
    RtlCopyMemory(&LocalContext,Context,sizeof(CONTEXT));
    Context=&LocalContext;
    //看到没,将原Context中的成员填写到NtContinue系统服务的TrapFrame帧中(也即修改成原来的TrapFrame)
    KeContextToTrapFrame(&LocalContext,ExceptionFrame,TrapFrame,
    LocalContext.ContextFlags,UserMode);
    }
    

    如上,上面的函数,就把NtContinue的TrapFrame强制还原成原来的TrapFrame,以好‘正式’返回到用户空间的真正断点处(不过在返回用户空间前,又要去扫描用户apc队列,若仍有用户apc函数,就先执行掉内核apc队列中的所有apc函数,然后又偏离原来的返回路线,‘提前’返回到用户空间的KiUserApcDispatcher函数去执行用户apc,这是一个不断循环的过程。可见,NtContinue这个函数不仅含有继续回到原真正用户空间断点处的意思,还含有继续执行用户apc队列中下一个apc函数的意思)

    BOOLEANKeTestAlertThread(INKPROCESSOR_MODEAlertMode)
    {
    PKTHREADThread=KeGetCurrentThread();
    KiAcquireApcLock(Thread,&ApcLock);
    OldState=Thread->Alerted[AlertMode];
    if(OldState)
    Thread->Alerted[AlertMode]=FALSE;
    elseif((AlertMode!=KernelMode)&&
    (!IsListEmpty(&Thread->ApcState.ApcListHead[UserMode])))
    {
    Thread->ApcState.UserApcPending=TRUE;//关键。又标记为不空,从而又去执行用户apc
    }
    KiReleaseApcLock(&ApcLock);
    returnOldState;
    }
    

    上面这个函数的关键工作是检测到用户apc队列不为空,就又将UserApcPending标志置于TRUE。

    前面我们看到的是用户apc队列的执行机制与时机,那是用户apc唯一的执行时机。内核apc队列中的apc执行时机是不相同的,而且有很多执行时机。

    内核apc的执行时机主要有:

    1、每次返回用户空间前,每执行一个用户apc前,就会扫描执行整个内核apc队列

    2、每当调用KeLowerIrql,从APC_LEVEL以上(不包括APC_LEVEL)降到APC_LEVEL以下(不包括APC_LEVEL)前,中途会检查是否有阻塞的apc中断请求,若有就扫描执行内核apc队列

    3、每当线程重新得到调度,开始运行前,会扫描执行内核apc队列或者发出apc中断请求

    内核apc的执行时机:【调度、返、降】apc

    KeLowerIrql实质上是下面的函数:

    VOIDFASTCALL
    KfLowerIrql(INKIRQLOldIrql)
    {
    ULONGEFlags;
    ULONGPendingIrql,PendingIrqlMask;
    PKPCRPcr=KeGetPcr();
    PIC_MASKMask;
    EFlags=__readeflags();//保存原eflags
    _disable();//关中断
    Pcr->Irql=OldIrql;//降到目标irql
    //检测是否有高于目标irql的阻塞中的软中断
    PendingIrqlMask=Pcr->IRR&FindHigherIrqlMask[OldIrql];
    if(PendingIrqlMask)//若有
    {
    BitScanReverse(&PendingIrql,PendingIrqlMask);//找到最高级别的软中断
    if(PendingIrql>DISPATCH_LEVEL)
    {
    Mask.Both=Pcr->IDR;
    __outbyte(PIC1_DATA_PORT,Mask.Master);
    __outbyte(PIC2_DATA_PORT,Mask.Slave);
    Pcr->IRR^=(1<<PendingIrql);
    }
    SWInterruptHandlerTable[PendingIrql]();//处理阻塞的软中断(即扫描执行队列中的函数)
    }
    __writeeflags(EFlags);//恢复原eflags
    }
    

    这个函数在从当前irql降到目标irql时,会按irql高低顺序执行各个软中断的isr。

    软中断是用来模拟硬件中断的一种中断。

    #define PASSIVE_LEVEL 0
    #define APC_LEVEL 1
    #define DISPATCH_LEVEL 2
    #define CMCI_LEVEL 5
    

    比如,当调用KfLowerIrql要将cpu的irql从CMCI_LEVEL降低到PASSIVE_LEVEL时,这个函数中途会先看看当前cpu是否收到了CMCI_LEVEL级的软中断,若有,就调用那个软中断的isr处理之。然后,再检查是否收到有DISPATCH_LEVEL级的软中断,若有,调用那个软中断的isr处理之,然后,检查是否有APC中断,若有,同样处理之。最后,降到目标irql,即PASSIVE_LEVEL。

    换句话说,在irql的降低过程中会一路检查、处理中途的软中断。Cpu数据结构中有一个IRR字段,即表示当前cpu累积收到了哪些级别的软中断。

    下面的函数可用于模拟硬件,向cpu发出任意irql级别的软中断,请求cpu处理执行那种中断。

    VOIDFASTCALL
    HalRequestSoftwareInterrupt(INKIRQLIrql)//Irql一般是APC_LEVEL/DPC_LEVEL
    {
    ULONGEFlags;
    PKPCRPcr=KeGetPcr();
    KIRQLPendingIrql;
    EFlags=__readeflags();//保存老的eflags寄存器
    _disable();//关中断
    Pcr->IRR|=(1<<Irql);//关键。标志向cpu发出了一个对应irql级的软中断
    PendingIrql=SWInterruptLookUpTable[Pcr->IRR&3];//IRR后两位表示是否有阻塞的apc中断
    //若有阻塞的apc中断,并且当前irql是PASSIVE_LEVEL,立即执行apc。也即在PASSIVE_LEVEL级时发出任意软中断后,会立即检查执行现有的apc中断。
    if(PendingIrql>Pcr->Irql)
    SWInterruptHandlerTable[PendingIrql]();//调用执行apc中断的isr,处理apc中断
    __writeeflags(EFlags);//恢复原eflags寄存器
    }
    

    那么什么时候,系统会调用这个函数,向cpu发出apc中断呢?

    典型的情形1:

    在切换线程时,若将线程的WaitIrql置为APC_LEVEL,将导致KiSwapContextInternal函数内部在重新切回来后,立即自动发出一个apc中断,以在下次降低irql到PASSIVE_LEVEL时处理执行队列中那些阻塞的apc。反之,若将线程的WaitIrql置为PASSIVE_LEVEL,将导致KiSwapContextInternal函数内部在重新切回来后,不会发出apc中断,然后系统会自行显式调用KiDeliverApc给予扫描执行

    典型情形2:

    在给自身线程发送一个内核apc时,在apc进队的同时,会发出apc中断,以请求cpu在下次降低irql时,扫描执行apc。

    Apc是一种软中断,既然是中断,他也有类似的isr。Apc中断的isr最终进入HalpApcInterruptHandler

    VOIDFASTCALL
    HalpApcInterruptHandler(INPKTRAP_FRAMETrapFrame)
    {
    //模拟硬件中断压入保存的寄存器
    TrapFrame->EFlags=__readeflags();
    TrapFrame->SegCs=KGDT_R0_CODE;
    TrapFrame->Eip=TrapFrame->Eax;
    KiEnterInterruptTrap(TrapFrame);//构造Trap现场帧
    扫描执行当前线程的内核apc队列,略…
    KiEoiHelper(TrapFrame);
    }


    展开全文
  • 情景分析是政策和规划领域中的重要研究工具,但将其应用在生态环境规划中还处于起步阶段,且相对简单。 情景是对一些有合理性和不确定性的事件在未来一段时间内可能呈现的态势的一种假定,情景分析是预测这些态势的...

     

    情景分析是政策和规划领域中的重要研究工具,但将其应用在生态环境规划中还处于起步阶段,且相对简单。

    情景是对一些有合理性和不确定性的事件在未来一段时间内可能呈现的态势的一种假定,情景分析是预测这些态势的产生并比较分析可能产生影响的整个过程,其结果包括:对发展态势的确认,各态势的特性、发生的可能性描述, 并对其发展路径进行分析。与传统的趋势外推法相比,它在对随机因素的影响和决策者意愿的处理上具有更大的灵活性和实用性;Delphi 法相比,它更强调专家之间的观点差异,并试图解释这种不一致性,这也使得它更复杂和更完善。

    情景分析法的最基本的观点是未来充满不确定性,但未来有部分内容是可以预测的。情景分析包括以下几个步骤(SRI):

    1)明确决策焦点。

    明确要决策的内容项目,已经聚情景发展的焦点。所谓决策焦点,是指为达成企业使命在经营领域必须做的决策。焦点应当具备两个特点:重要性和不确定性。

    在规划中就理解成规划对象、焦点问题及关键决策识别。生态规划情景的设计要面对一定的对象群体,并鉴别这些对象所关心的焦点问题(如污染和生态破坏) 和相关的重要决策。

    2)识别关键因素。

    确认所有影响决策成功的关键因素,即直接影响决策的外在环境因素,如市场需求,企业生产能力和政府管制力量等。

    在生态规划中指的核心要素是在情景设计时需要重点考虑的影响因子,如规划区域的经济因素、环境管理因素等。

    3)分析外在驱动力量。

    确认重要的外在驱动力量,包括政治经济社会技术各层面,以决定关键决策因素的未来状态。

    生态规划中值得是驱动因子列举。驱动因子主要是由情景设计小组以研讨会或其他方式邀请一些专家和对象群体代表,对未来区域内生态环境可能出现的一些情况和预期达到的目标进行展望,以“头脑风暴”的形式产生。这一过程分3 : ①根据所识别的核心要素将邀请到的人员分组,使他们就某些问题发表自己的看法,从而得到一系列的观点清单,在这一阶段不审核哪些观点是不重要或是不切合实际的; ②对观点进行归纳整理,去掉一些含混不清的和重复的观点; ③根据观点涵盖范围的不同进行分类。还要对驱动因子的重要性和不确定性排序。在情景设计中需要加以重点考虑。

    4)发展情景逻辑

    选定两三个情景,这些情景包括了所有的焦点。对情景进行细节描绘,把情景完善为“剧本”。可以选取一定的指标,在分析现状发展的基础上,利用模型对设计的情景进行量化,给出不同情景下的详尽资料。

    5)分析情景的内容

    可以通过角色试演的方法检验情景的一致性,管理者可以看到在未来环境中各角色可能做出的反应,最后认定各情景在管理决策上的含义。

    即分析预测的情景结果,结合实现这些目标的假定条件,给出未来规划区域内环境变化趋势以及环境技术上、经济上和政策上的建议。

    总的来说,将情景分析法用于城市规划是一种可行的方法,即在不同发展情景的分析基础之上,对不同情景下的环境保护策略或措施进行有的放矢的规划,并在对不同情景下的规划方案进行比较后提供决策和科学依据,

    例如以通州区土地利用规划为案例。可采用情景分析法提出高中低三种替代方案,三种替代方案具有不同的土地利用的布局和数量对比,有的注重经济发展,建设用地面积多一些,有的注重生态保护,耕地和林地多一些,有的则是生态和经济发展并重,介于二者之间。每个方案又分为三个阶段进行分析和预测,即近期、远期、远景。不同的方案有不同的侧重,越是到远期,这种差异和侧重就越明显,按照注重经济还是注重环保会有完全不同的方案和发展预测。这些方案中具体的土地利用的数量和类型对比都是根据发展战略侧重和其他相关的规划期望预测出来的。下面需要对这三个方案进行筛选。从生态足迹的角度来衡量这三个方案,找到最合理的方案。

    这就是情景分析法的一个应用,即根据不同的情景预测出不同的方案,在对方案进行比对。

    转载于:https://www.cnblogs.com/luspa/archive/2008/08/19/1271189.html

    展开全文
  • 区块链(Blockchain)是分布式数据存储、点对点传输、共识机制、加密算法等计算机技术的新型应用模式。所谓共识机制是区块链系统中实现不同节点之间建立信任、获取权益的数学算法。
  • 毛德操的 Windows内核情景分析 下册,学习Windos核心开发,安全技术学习必读
  • 毛德操的 Windows内核情景分析 上册,学习Windos核心开发,安全技术学习必读
  • 6. 测试的主程序 规则: 加粗体的黑色代码,表示将作深入分析 try { Directory directory = new RAMDirectory(); Analyzer analyzer = new SimpleAnalyzer(); IndexWriter writer = new IndexWriter(directory, ...

    6. 测试的主程序

    规则:

    加粗体的黑色代码,表示将作深入分析

    try {

    Directory directory = new RAMDirectory();

    Analyzer analyzer = new SimpleAnalyzer();

    IndexWriter writer = new IndexWriter(directory, analyzer, true);

    String[] docs = {

    "a b c d e",

    "a b c d e a b c d e",

    "a b c d e f g h i j",

    "a c e",

    "e c a",

    "a c e a c e",

    "a c e a b c"

    };

    for (int j = 0; j < docs.length; j++) {

    Document d = new Document();

    d.add(Field.Text("contents", docs[j]));

    writer.addDocument(d);

    }

    writer.close();

    以上代码是准备工作,生成索引

    Searcher searcher = new IndexSearcher(directory);

    以上代码,初始化查询,分析编号 1 。 1

    String[] queries = {"/"a c e/"",

    };

    Hits hits = null;

    QueryParser parser = new QueryParser("contents", analyzer);

    parser.setPhraseSlop(0);

    for (int j = 0; j < queries.length; j++) {

    Query query = parser.parse(queries[j]);

    该 Query = PhraseQuery

    System.out.println("Query: " + query.toString("contents"));

    hits = searcher.search(query);

    以上代码,初始化查询,分析编号 1 。 2

    System.out.println(hits.length() + " total results");

    for (int i = 0 ; i < hits.length() && i < 10; i++) {

    Document d = hits.doc(i);

    System.out.println(i + " " + hits.score(i)

    // + " " + DateField.stringToDate(d.get("modified"))

    + " " + d.get("contents"));

    }

    }

    searcher.close();

    } catch (Exception e) {

    System.out.println(" caught a " + e.getClass() +

    "/n with message: " + e.getMessage());

    }

     

    查询结果:

    Query: "a c e"

    3 total results

    0 1.0 a c e a c e

    1 0.9428091 a c e

    2 0.7071068 a c e a b c

     

    1.1. Searcher searcher = new IndexSearcher(directory)

    1.1.1. 初始化

    通过目录,创建一个索引搜索器,

    调用类

    IndexSearcher :: public IndexSearcher(Directory directory) throws IOException {

    this( IndexReader.open(directory) , true);

    }

    调用

    private IndexSearcher(IndexReader r, boolean closeReader) {

    reader = r;

    this.closeReader = closeReader;

    }

    调用

    private static IndexReader open(final Directory directory, final boolean closeDirectory) throws IOException {

    synchronized (directory) { // in- & inter-process sync

    return (IndexReader)new Lock.With(

    directory.makeLock(IndexWriter.COMMIT_LOCK_NAME),

    IndexWriter.COMMIT_LOCK_TIMEOUT) {

    public Object doBody() throws IOException {

    SegmentInfos infos = new SegmentInfos();

    从目录中读取 SegmentInfos

    infos.read(directory);

    if (infos.size() == 1) { // index is optimized

    return new SegmentReader(infos, infos.info(0), closeDirectory);

    } else {

    IndexReader[] readers = new IndexReader[infos.size()];

    for (int i = 0; i < infos.size(); i++)

    readers[i] = new SegmentReader(infos.info(i));

    return new MultiReader(directory, infos, closeDirectory, readers);

    }

    }

    }.run();

    }

    }

    代码到这里,已经读取了文件 segments 文件,获得段信息,该测试只有一个段,所以执行了 return new SegmentReader(infos, infos.info(0), closeDirectory); ,记住 IndexReader = SegmentReader

    infos.read(directory):

    /** 读取输入参数的目录,下的 segments 文件

    * 代码分析:

    * 1 。读取格式,小于 0 表示该文件有隐含的格式信息,小于- 1 就表示该格式是未知的,因为最小的格式是- 1

    * 2 。小于 0 时,再读取版本信息以及段的计数

    * 3 。大于 0 ,表示 segments 文件开头部分没有版本信息,只有段的计数

    * 4 。读取段的数量

    * 5 。循环读取段信息,然后构建段信息对象,最后把这些对象都加入到段集合中

    * 6 。大于 0 时,判断是否文件最后有版本信息,有的话就赋值 version ,没有的话, version = 0 */ , 该段代码比较简单,读者可以从看 src 中代码

    return new SegmentReader(infos, infos.info(0), closeDirectory);

    SegmentReader(SegmentInfos sis, SegmentInfo si, boolean closeDir)

    throws IOException {

    super(si.dir, sis, closeDir);

    initialize(si);

    }

    super(si.dir, sis, closeDir);

    IndexReader :: IndexReader(Directory directory, SegmentInfos segmentInfos, boolean closeDirectory) {

    this.directory = directory;

    this.segmentInfos = segmentInfos;

    directoryOwner = true;

    this.closeDirectory = closeDirectory;

    stale = false;

    hasChanges = false;

    writeLock = null;

    }

    SegmentReader :: initialize(si);

    /** 初始化这个段信息

    该段代码是初始化了

    * 1 。读入域信息,只有域的名字

    * 2. 打开保存域、保存域索引的文件

    */

    private void initialize(SegmentInfo si) throws IOException

    {

    segment = si.name;

    // Use compound file directory for some files, if it exists

    Directory cfsDir = directory();// 就是保存该段的目录

    // CompoundFileReader (组合文件读取器)也是 ( 目录 ) 的子类

    if (directory().fileExists(segment + ".cfs")) {

    cfsReader = new CompoundFileReader(directory(), segment + ".cfs");

    cfsDir = cfsReader;

    }

    // 1 。读入域信息,只有域的名字

    fieldInfos = new FieldInfos(cfsDir, segment + ".fnm"); // 这个过程读入所有的域信息了

    // 2 。打开保存域、保存域索引的文件

    fieldsReader = new FieldsReader(cfsDir, segment, fieldInfos);

    tis = new TermInfosReader(cfsDir, segment, fieldInfos);

    if (hasDeletions(si))

    deletedDocs = new BitVector(directory(), segment + ".del");// 读入删除表

    freqStream = cfsDir.openFile(segment + ".frq");// 读入频率文件

    proxStream = cfsDir.openFile(segment + ".prx");// 读入 位置文件

    openNorms(cfsDir);// 读入文件 segment.f1,segment.f2 ……,建立 hashtable

    if (fieldInfos.hasVectors()) { // open term vector files only as needed

    termVectorsReader = new TermVectorsReader(cfsDir, segment, fieldInfos);

    }

    }

    1.2. hits = searcher.search(query);

    这时, searcher = IndexSearcher , 对该代码的跟踪如下:

    调用: return search(query, (Filter)null)

    调用: return new Hits(this, query, filter);

    调用: Hit :: Hits(Searcher s, Query q, Filter f) throws IOException {

    query = q;

    searcher = s;

    filter = f;

    getMoreDocs(50); // retrieve 100 initially

    }

    getMoreDocs(int min) 调用:: TopDocs topDocs = searcher.search(query, filter, n)

    searcher.search(query, filter, n) 调用 Scorer scorer = query.weight(this).scorer(reader);

    IndexSearcher :: public TopDocs search(Query query, Filter filter, final int nDocs)

    throws IOException {

    Scorer scorer = query.weight(this).scorer(reader);

    if (scorer == null)

    return new TopDocs(0, new ScoreDoc[0]);

    final BitSet bits = filter != null ? filter.bits(reader) : null;

    final HitQueue hq = new HitQueue(nDocs);

    final int[] totalHits = new int[1];

    scorer.score(new HitCollector() {

    private float minScore = 0.0f ;

    public final void collect(int doc, float score) {

    if (score > 0.0f && // ignore zeroed buckets

    (bits==null || bits.get(doc))) { // skip docs not in bits

    totalHits[0]++;

    if (hq.size() < nDocs || score >= minScore) {

    hq.insert(new ScoreDoc(doc, score));

    minScore = ((ScoreDoc)hq.top()).score; // maintain minScore

    }

    }

    }

    });

    ScoreDoc[] scoreDocs = new ScoreDoc[hq.size()];

    for (int i = hq.size()-1; i >= 0; i--) // put docs in array

    scoreDocs[i] = (ScoreDoc)hq.pop();

    return new TopDocs(totalHits[0], scoreDocs);

    }

    1.2.1. Scorer scorer = query.weight(this).scorer(reader);

    参数分析: query = PhraseQuery (该参数由主测试程序中的 Query query = parser.parse(queries[j]); 初始化)

    this = IndexSearcher (该参数初始化,已经初始化了主要的文件,具体可参考 1.1 )

    由代码

    1 PhraseQuery ::

    protected Weight createWeight(Searcher searcher) {

    if (terms.size() == 1) { // optimize one-term case

    Term term = (Term)terms.elementAt(0);

    Query termQuery = new TermQuery(term);

    termQuery.setBoost(getBoost());

    return termQuery.createWeight(searcher);

    }

    return new PhraseWeight(searcher);

    }

     

    query.weight(this) 创建了 PhraseWeight(searcher)

    Scorer scorer = query.weight(this).scorer(reader) 就相当于 PhraseWeight(searcher). .scorer(reader), 即调用以下代码:

    2 PhraseQuery ::

    public Scorer scorer(IndexReader reader) throws IOException {

    if (terms.size() == 0) // optimize zero-term case

    return null;

    // 读取项的 位置信息

    TermPositions[] tps = new TermPositions[terms.size()];

    for (int i = 0; i < terms.size(); i++) {

    TermPositions p = reader.termPositions((Term)terms.elementAt(i));

    if (p == null)

    return null;

    tps[i] = p;

    }

    得到所有项的项信息, TermPositions[ ] = SegmentTermPositions[ ]

    if (slop == 0) // optimize exact case

    return new ExactPhraseScorer(this, tps, getPositions(), getSimilarity(searcher) ,

    reader.norms(field));

    }

     

    ü TermPositions p = reader.termPositions((Term)terms.elementAt(i));

    这时 Term 文本为查询里的项

    public TermPositions termPositions(Term term) throws IOException {

    TermPositions termPositions = termPositions();

    termPositions.seek(term);

    return termPositions;

    }

    termPositions() ::

    SegmentReader :: public final TermPositions termPositions() throws IOException {

    return new SegmentTermPositions(this);

    }

    parent = SegmentReader, 即刚才的段读取器

    tis = new TermInfosReader(cfsDir, segment, fieldInfos); 即项信息读取器

    SegmentTermPositions(this) ::

    SegmentTermPositions :: SegmentTermPositions(SegmentReader p) throws IOException {

    super(p);

    this.proxStream = (InputStream)parent.proxStream.clone();

    }

    super(p) ::

    SegmentTermDocs(SegmentReader parent)

    throws IOException {

    this.parent = parent;

    this.freqStream = (InputStream) parent.freqStream.clone();

    this.deletedDocs = parent.deletedDocs;

    this.skipInterval = parent.tis.getSkipInterval();

    }

    termPositions.seek(term);

    public void seek(Term term) throws IOException {

    根据项,从项信息读取器中读取对应的项信息,该方法是线程安全的

    TermInfo ti = parent.tis.get(term);

    seek(ti);

    }

    seek(TermInfo ti)

    SegmentTermDocs 的项信息转变为现在读入的项的信息

    void seek(TermInfo ti) throws IOException {

    count = 0;

    if (ti == null) {

    df = 0;

    } else {

    df = ti.docFreq;

    doc = 0;

    skipDoc = 0;

    skipCount = 0;

    numSkips = df / skipInterval;

    freqPointer = ti.freqPointer;

    proxPointer = ti.proxPointer;

    skipPointer = freqPointer + ti.skipOffset;

    freqStream.seek(freqPointer);

    haveSkipped = false;

    }

    }

    new ExactPhraseScorer(this, tps, getPositions(), getSimilarity(searcher) , reader.norms(field));

    调用构造器

    ExactPhraseScorer(Weight weight, TermPositions[] tps, int[] positions, Similarity similarity,

    byte[] norms) throws IOException {

    super(weight, tps, positions, similarity, norms);

    调用超类构造器,获得短语位置的频繁度信息和位置信息,并构造一个优先队列

    PhraseScorer(Weight weight, TermPositions[] tps, int[] positions, Similarity similarity,

    byte[] norms) {

    super(similarity);

    this.norms = norms;

    this.weight = weight;

    this.value = weight.getValue();

    // convert tps to a list

    // 把 PhrasePositions 放在一个一般的队列里面(以链表形式)

    for (int i = 0; i < tps.length; i++) {

    PhrasePositions pp = new PhrasePositions(tps[i], positions[i]);

    if (last != null) { // add next to end of list

    last.next = pp;

    } else

    first = pp;

    last = pp;

    }

    pq = new PhraseQueue(tps.length); // construct empty pq

    }

    使用该记分器记分,并收集

    scorer.score(new HitCollector()

    public void score(HitCollector hc) throws IOException {

    while (next()) {

    hc.collect(doc(), score());

    }

    }

    hc.collect(doc(), score());

    score() 调用, value 为权值

    PhraseScorer :: public float score() throws IOException {

    //System.out.println("scoring " + first.doc);

    float raw = getSimilarity().tf(freq) * value; // raw score

    return raw * Similarity.decodeNorm(norms[first.doc]); // normalize

    }

    把各个位置的文档和得分收集

    public final void collect(int doc, float score) {

    if (score > 0.0f && // ignore zeroed buckets

    (bits==null || bits.get(doc))) { // skip docs not in bits

    totalHits[0]++;

    if (hq.size() < nDocs || score >= minScore) {

    hq.insert(new ScoreDoc(doc, score));

    minScore = ((ScoreDoc)hq.top()).score; // maintain minScore

    }

    }

    }

    到这里就出来了查询的文档和分数,并且这些文档和分数经过了指定的排序和过滤

     

    展开全文
  • 通过情景分析,定量计算了不同污染物控制方案下中国未来煤炭利用中的污染物排放量趋势。研究结果表明,在不采取污染物控制措施的基准情景下,2030年我国煤炭利用产生的NOx和SOx排放量将大大超出环境容量。而通过在主要...
  • 在计算机技术的发展史上,Unix操作系统的出现是一个重要的里程碑。
  • 《Android系统源代码情景分析

    千次下载 热门讨论 2012-12-09 12:16:26
    《Android系统源代码情景分析》随书光盘内容(源代码) 目录如下: 第1篇 初识Android系统 第1章 准备知识 1.1 Linux内核参考书籍 1.2 Android应用程序参考书籍 1.3 下载、编译和运行Android源代码 ...
  • 本书沿用作者独特而广受欢迎的情景分析方法和风格,深入浅出直白易懂,可以作为大数据系统高级课程的教材,也可用作计算机软件专业和其他相关专业大学本科高年级学生和研究生深入学习大数据系统的参考书。...
  • 本文实例讲述了C#线程同步的三类情景,分享给大家供大家参考。具体分析如下: C# 已经提供了我们几种非常好用的类库如 BackgroundWorker、Thread、Task等,借助它们,我们就能够分分钟编写出一个多线程的应用程序。 ...
  • 利用LEAP模型对我国煤炭需求进行情景分析及预测,得出三种不同经济及环境情境下,我国未来的煤炭需求,对于煤炭行业去产能,煤炭行业技术发展方向及煤化工提出了政策建议,有助于煤炭行业正确认识目前及未来煤炭发展形式,...
  • 从不同经济结构、发展路径及技术条件等角度对江苏省2010年和2020年的能源消耗进行了情景分析,利用能源消费总量、结构与碳排放量之间的关系式即单位能耗碳排放系数的表达式,测算了江苏省未来碳排放总量情况。
  • 资源名称:大数据处理系统:Hadoop源代码情景分析内容简介:Hadoop是目前重要的一种开源的大数据处理平台,读懂Hadoop的源代码,深入理解其各种机理,对于掌握大数据处理的技术有着显而易见的重要性。 本书从大数据...
  • 本书写法独特,论述精辟,不回避代码分析中的难点,可以作为操作系统高级课程的教材,也可以作为计算机软件专业和其他相关专业大学本科高年级学生和研究生深入学习操作系统以至软件核心技术的重要参考书。...
  • 为评估中国电力行业CO2减排潜力,建立了LEAP China模型,应用自底向上的方法模拟分析了3种不同政策情景下行业2000—2030年的CO2排放情况。通过评估计算CO2减排成本及减排贡献率识别了行业重点减排技术。结果表明:...
  • 促进中小燃煤工业锅炉的清洁高效发展,从清洁燃料替代、集中供热替代、采用先进工业锅炉技术、提高用煤质量4个减排途径对中小燃煤工业锅炉节能减排效果进行了分析,研究了4个节能减排途径适用区域以及政策情景下节能...
  • 北京火龙果软件工程技术中心内容摘要:Jive作为学习设计模式的好教材,难度自然不低,我们就以Jive(J道版)为例程,带着大家来一次“面向过程式”的逐步阅读源代码,好像一次游览一般。本次浏览forum.jsp。作者:蔡...
  • 北京火龙果软件工程技术中心内容摘要:Jive作为学习设计模式的好教材,难度自然不低,我们就以Jive(J道版)为例程,带着大家来一次“面向过程式”的逐步阅读源代码,好像一次游览一般。本次浏览过滤器。作者:蔡永航...
  • 北京火龙果软件工程技术中心内容摘要:Jive作为学习设计模式的好教材,难度自然不低,我们就以Jive(J道版)为例程,带着大家来一次“面向过程式”的逐步阅读源代码,好像一次游览一般。本次浏览只限于匿名登陆(即为...
  • 在内容上,本书结合使用情景,全面、深入、细致地分析Android系统的源代码,涉及到Linux内核层、硬件抽象层(HAL)、运行时库层(Runtime)、应用程序框架层(Application Framework)以及应用程序层(Application)。...
  • Android系统源代码情景分析:基础知识 作者 罗升阳 发布于 2011年12月14日 领域  语言 & 开发  主题  移动 ,  Android系统源代码情景分析  标签  源代码 ,  Android ,  专栏 分享...

    Android系统源代码情景分析:基础知识

    作者 罗升阳 发布于 2011年12月14日

    领域 
    语言 & 开发 
    主题 
    移动 , 
    Android系统源代码情景分析 
    标签 
    源代码 , 
    Android , 
    专栏

    【编者按】移动开发领域已经成为技术社区的一大热点,InfoQ中文站也在密切关注。本专栏旨在帮助Android应用开发人员更深入地了解该系统的底层架构、源码实现,以便在实际开发过程中更有效地利用Android系统的功能特性,达到“庖丁解牛”的境界。


    目前,互联网行业正在朝着移动互联网方向强劲地发展,而移动互联网的发展离不开背后的移动平台的支撑。众所周知,如今在移动平台市场上,苹果的iOS、谷歌的Android和微软的Windows Phone系统已经形成了三足鼎立的形势,而Android系统的市场占有率是最高的。Android系统之所以能够在市场上占据着第一的位置,一来是因为它依托着谷歌的品德效应和技术实力,二来是因为它是开放的,任何人都可以得到它的源代码,并且能够自由地使用它。既然Android系统是开放的,作为一个移动平台开发人员来说,当然希望能够深入地去分析和研究它的源代码了,然而,Android系统的源代码非常庞大,我们需要循序渐进地去学习。

    工欲善其事,必先利其器。为了全面、深入地理解Android系统的源代码,在正式进入Android系统源代码的世界前,我们手头上需要准备好一些参考资料以及实验环境,此外,还需要了解Android系统的架构知识。

    参考资料

    Android系统的源代码非常庞大和复杂,我们不能贸然进入,否则很容易在里面迷入方向,进而失去研究它的信心。我们应该在分析它的源代码之前学习好一些理论知识,下面就介绍一些与Android系统相关的资料。

    我们知道,Android系统是基于Linux内核来开发的,在分析它在运行时库层的源代码时,我们会经常碰到诸如管道(pipe)、套接字(socket)和虚拟文件系统(VFS)等知识。此外,Android系统还在Linux内核中增加了一些专用的驱动程序,例如用于日志系统的Logger驱动程序、用于进程间通信的Binder驱动程序和用于辅助内存管理的匿名共享内存Ashmem驱动程序。在分析这些Android专用驱动程序的时候,也会碰到Linux内核中与进程、内存管理相关的数据结构。因此,我们有必要掌握一些Linux内核的基础知识,下面就介绍四本典经的Linux内核书籍。

    1. Linux Kernel Development.

      这本书的作者是Robert Love,目前最新的版本是第3版。这本书对Linux内核的设计和实现提供了一个总览视图,从概念上对Linux内核的各个子系统的设计目标和实现思路进行了清晰的描述,非常适合初学者阅读。如果从软件工程的角度来看,这本书就相当于是Linux内核的概要设计文档。

    2. Understanding the Linux Kernel.

      这本书的作者是Daniel P. Bovet和Marco Cesati,目前最新的版本是第3版。这本书对Linux内核的实现提供了更多的细节,详细地描述了内核开发中用到的重要数据结构、算法以及编程技巧,非常适合中高级读者阅读。如果从软件工程的角度来看,这本书就相当于是Linux内核的详细设计文档。

    3. Linux Device Drivers.

      这本书的作者是Jonathan Corbet, Alessandro Rubini和Greg Kroah-Hartman,目前最新的版本是第3版。这本书更加注重实际操作,它详细地讲解了Linux内核驱动程序的实现原理和实现方法,读者可以跟着它来实际地编写出自己的Linux驱动程序。阅读了这本书之后,对我们后续去分析Android的专用驱动程序是有非常大的帮助的。

    4. Linux内核源代码情景分析

      这本书的作者是毛德操和胡希明,是中国人自己编写的一本经典的Linux内核书籍。这本书最大的特点是从使用情景出发,对Linux内核的源代码作了详细的分析,帮助读者把枯燥无味的源代码给理顺了。

    掌握了Linux内核的基础知识之后,还不宜马上就去分析Android系统的源代码,因为这样做是漫无目的的,我们应该带着问题或者目标去分析Android系统的源代码。要把问题或者目标挖掘出来,最好的方法就莫过于是在Android平台上编写自己的应用程序了。通过编写应用程序,我们可以知道Android平台都提供了哪些功能,进而我们就会想去了解这些功能是怎么实现的,这样就可以达到带着问题或者目标去分析Android系统的源代码了。这里介绍两个Android应用程序开发教程的书籍:

    1. Professional Android 2 Application Development.
    2. Google Android SDK开发范例大全.

    这两本书都使用了大量的例子来说明如何使用Android SDK来开发Android应用程序。读者可以根据实际情况来练习一下,主要掌握Android应用程序四大组件(Activity、Service、Broadcast Receiver和Content Provider)的用法,因为Android系统的整个架构和实现就是为了向开发者提供这四大组件来实现各种各样的应用程序的。在学习的过程中,如果遇到其它问题,还可以参考官方文档,其网址为:http://developer.android.com/index.html

    环境搭建

    开发Android应用程序可以在两种环境下进行,一是在Android SDK环境下进行,一般是集成在Eclipse里面进行开发,二是在Android源代码工程环境下进行,在这种环境进行开发的好处是可以使用一些在SDK中不公开的接口。但是如果我们要修改Android系统的源代码,或者为Android系统增加新的功能接口,那么就只能在Android源代码工程环境下进行了。由于我们的目的是对Android系统源代码进行分析,因此,我们在开发Android应用程序时,也在Android源代码环境下进行。这样,我们就需要搭建一套Android源代码工程环境了。

    目前,Android源代码工程环境只能在Linux平台上使用,而Linux系统的发行版本比较多,这里我们推荐Ubuntu系统。Ubuntu系统是免费的,而且非常易于使用,安装和更新应用程序也非常方便,它的官方下载地址为:http://www.ubuntu.com/

    安装好Ubuntu系统之后,我们就可以在上面下载、编译和安装Android源代码了,具体方法和步骤可以参考下面这篇文章:在Ubuntu上下载、编译和安装Android最新源代码

    Android系统的源代码工程默认是不包含Linux内核源代码的,如果我们需要修改Android系统的内核或者在里面增加新的模块,那么就要把Android内核源代码一起下载、编译和安装了,具体方法和步骤可以参考下面这篇文章:在Ubuntu上下载、编译和安装Android最新内核源代码(Linux Kernel)

    Android源代码工程环境搭建好了之后,我们就可以在里面开发新的应用程序或者修改系统代码了。增加了新的应用程序或者修改了系统的代码之后,不需要重新编译整个源代码工程,只要单独编译有改动的模块就可以了,具体方法可以参考下面这篇文章:如何单独编译Android源代码中的模块

    对于已经开发好的应用程序或者系统功能,如果想把当作Demo展示给客户来体验时,我们既可以在真机上面运行,也可以在模拟器(Android源代码工程环境或者Android SDK环境都集成了模拟器)上面运行。当我们手头上没有真机,而且我们又不想把整个Android源代码工程环境或者Android SDK环境带去展示我们的Demo时,就可以考虑把模拟器这两个环境中独立出来了,具体方法可以参考下面这篇文章:制作可独立分发的Android模拟器

    系统架构

    Android系统是按层次、分模块来设计的。在我们着手对Android系统的源代码进行分析前,需要对Android系统的架构有一个总体的认识,这样我们就能够快速地知道哪些代码位于哪个层次上的哪个模块中,节省搜索代码的时间,把更多的精力投入在源代码的分析上去。

    整个系统划分内核空间和用户空间两部分。内核空间包含了进程管理、内存管理以及设备驱动程序模块等,其中Android专用驱动Binder、Logger和Ashmem就是在内核空间实现的。用户空间包含了硬件抽象层(HAL)、外部库和运行时库层(External Libraries & Android Runtime)、应用程序框架层(Application Framework)和应用程序层(Applications)四个层次。我们应该如何去掌握这个层次结构呢?最好的方法就是从学习Android的硬件抽象层作为切入点了。

    可能读者会觉得比较奇怪,为什么要把Android系统的硬件抽象层作为学习Android系统架构的切入点呢?这个层次因为涉及到硬件,看起来这是一个比较复杂和深奥的知识点。其实不然,Android系统的硬件抽象层在实现和使用上,层次都是非常清晰的,它从上到下涵盖了Android系统的用户空间和内核空间。内核空间主要就是涉及到硬件驱动程序,而用户空间就涉及到了Android系统应用程序层、应用程序框架层和系统运行时库层的相关知识。因此,学习Android系统的硬件抽象层,可以使读者快速地认识整个Android系统,从而对Android系统得到一个感性的认识,为后面深入分析Android系统的源代码打下良好的基础。

    Android硬件抽象层的学习可以参考下面的一系列文章:

    学会了编写基本的Android应用程序并且对Android系统的整体架构有一个大概的了解之后,我们就可以去分析Android系统的源代码了。

    在分析Android源代码的过程中,我们经常进入到应用程序框架层去分析它的源代码,而在应用程序框架层中,有一部分代码是使用C++来实现的,这时候就会经常碰到智能指针,因此,我们把Android系统中的智能指针也作为一个基础知识点来学习。相信使用过C++语言来做开发的读者对智能指针不会感到陌生。用C++来写代码最容易出错的地方就是指针了,一旦使用不当,轻则造成内存泄漏,重则造成系统崩溃,因此,系统为我们提供了智能指针,避免出现上述问题。

    在Android系统中,提供了三种类型的智能指针,分别是轻量级指针、强指针和弱指针,它们都是基于对象引用计数技术来实现的。轻量级指针的计数技术比较简单,只要对象的引用计数值为0,它就会被释放。强指针和弱指针的计数技术相对比较复杂,一个对象可以同时被强指针和弱指针引用,但是这个对象的生命周期一般只受强指针的控制,即当这个对象的强引用计数为0的时候,这个对象就被释放了,即使这时候这个对象的弱引用计数不为0。引进强指针和弱指针这种复杂的引用计数技术是为了解决垃圾收集(Garbage Collection)问题而提出的。考虑这样的一个场景,系统中有两个对象A和B,在对象A的内部引用了对象B,而在对象B的内部也引用了对象A。当两个对象A和B都不再使用时,垃圾收集系统会发现无法回收这两个对象的所占据的内存的,因为系统一次只能收集一个对象,而无论系统决定要收回对象A还是要收回对象B时,都会发现这个对象被其它的对象所引用,因而就都回收不了,这样就造成了内存泄漏。如果采用强指针和弱指针技术,这个问题就迎刃而解了,即A和B都用弱指针来引用对方。Android智能指针的学习,可以参考下面这篇文章: Android系统的智能指针(轻量级指针、强指针和弱指针)的实现原理分析

    掌握了本文所介绍的这些基础知识后,我们就可以正式开始分析Android系统的源代码了。

    关于作者

    罗升阳,先后就读于浙江大学(学士)、上海交通大学(硕士)计算机专业。现在供职于国内一家互联网公司,从事软件开发工作,专注于移动平台开发。


    感谢崔康对本文的审校。

    给InfoQ中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家加入到InfoQ中文站用户讨论组中与我们的编辑和其他读者朋友交流。

    不错 发表人 Zon Charlie 发表于 14/12/2011 04:55
    有些资料过期了 发表人 高 德翔 发表于 14/12/2011 08:56
    Re: 有些资料过期了 发表人 Luo Shengyang 发表于 14/12/2011 10:36
    三足鼎立? 发表人 qiu yi 发表于 15/12/2011 11:43
    1. 返回顶部

      不错

      14/12/2011 04:55 发表人 Zon Charlie

      给出这样一个指导性的学习方向和方法挺好的 :)
      只是如果不了解linux内核的话,学习周期好像有点长。

    2. 返回顶部

      有些资料过期了

      14/12/2011 08:56 发表人 高 德翔

      比如:源码下载方式,已经变了。

    3. 返回顶部

      Re: 有些资料过期了

      14/12/2011 10:36 发表人 Luo Shengyang

      文章提到的两篇下载源码的文章有更新的,注意看一下文章后面的评论,有最新的下载方式。

    4. 返回顶部

      三足鼎立?

      15/12/2011 11:43 发表人 qiu yi

      苹果的iOS、谷歌的Android和微软的Windows Phone系统已经形成了三足鼎立?

      Windows Phone现在啥都不是,不管国内还是国外。


    展开全文
  • Android系统源代码情景分析光盘资料 目录 第1篇初识Android系统 第1章 准备知识................................................................ 2 1.1 Linux内核参考书籍......................................

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 437
精华内容 174
关键字:

情景分析技术