2004年08月29日

/**

 * file: message.c

 * author: <muddog@21cn.com>

 * date: 2004/8/24

 *

 */

#include “litegui.h”

#include “message.h”

#include “wnd.h”

 

 

/**

 * some util function

 * 这些函数是用来根据窗口句柄获得窗口消息队列及回调的

 * 窗口句柄在这里只是简单的用窗口结构的地址来表示,不象

 * windows那样的资源表

 */

static LPMSG_QUEUE GetMsgQueue(HWND hWnd)

{

    PMAIN_WND pMainWnd;

    if(hWnd == HWND_INVALID)

    return NULL;

    if(hWnd == HWND_DESKTOP)

       return g_deskTop.pMsgQueue;

   

    pMainWnd = (PMAIN_WND)hWnd;

    return pMainWnd->pMsgQueue;

}

 

static WNDPROC GetWndProc(HWND hWnd)

{

    PMAIN_WND pMainWin = (PMAIN_WND)hWnd;

 

    if(hWnd == HWND_DESKTOP)

       return g_deskTop.DesktopWinProc;

      

    return pMainWin->MainWndProc;

}

 

/**

* 以下结构很重要,一个是消息结构池,存放使用及未使用的消息结构

* free_list则是一个未使用的消息结构的连表,我们从free_list连表中

* 分配新的消息结构给需要消息传递的进程。

* 这里的消息结构的分配释放有n中方法,比如:miniGUI中的读者写者的方式,就是

* 利用一个静态的循环静态数组,指定读位置和写位置,发送者修改写位置,收取者

* 修改读位置,这个不难。还有就是连表,就想linuxwait_list一样。我们采用

* 后者,当然我在原始消息队列中采用了前者(和驱动打交道的进程)。

* 结构池也可以是静态的或动态的,动态需要malloc,但liteGUI是个小型系统,

* 没有必要为malloc牺牲性能,但有可能在后面的窗口资源管理上,会用上动态的

* 结构分配。

*/

/* Message struct Pool */

/* message pool */

MSG msg_pool[TOTAL_MSG_POOL];

/* list header of free msg in pool */

LPMSG msg_free_list;

 

 

/**

 *  MSG list on Message Queue

 * Operation function

 *  这两个inline实现的是向队列中插入消息结构

 * 和取下一个消息结构。插入在队列尾,取在队列头。

 *  这里有个OS_ENTER_CRITIALOS_EXIT_CRITIAL

 *  这个是临界段的进入和离开,当然也就是竞态的代码

 *  ucOS提供的API本质上就是关中断,开中断

 **/

/* remove a msg struct from the msgHeader in queue*/

inline void DEL_FROM_LIST(

                  LPMSG msgHead,

                  LPMSG msgTail,

                  LPMSG lpMsg) 

{                

    LPMSG rmMsg;

    /* remove from msg queue list */  

    rmMsg = msgHead;               

    ASSERT(rmMsg);                    

    msgHead = msgHead->next;

    /* enter critial section */

    OS_ENTER_CRITICAL();    

    /* add to free list */            

    rmMsg->next = msg_free_list;      

    msg_free_list = rmMsg;         

    /* refresh tail pointer */ 

    if(!msgHead)

       msgTail = NULL;

    *lpMsg = *rmMsg;

    /* leave critical section */

    OS_EXIT_CRITICAL();        

}

 

/**

 *  add a msg struct to the msgHeader in queue

 * return:

 *      if success TRUE

 *      else there’s no more free msg struct FALSE

 *

 * */

inline BOOL ADD_TO_LIST(

                  LPMSG msgHead,

                  LPMSG msgTail,

                  LPMSG lpMsg)

{

    LPMSG newMsg;

 

    /*************************/

    /* enter critial section */

    OS_ENTER_CRITICAL();

   

    /* get a free msg struct from msg pool */

    if( !(newMsg = msg_free_list) ){

       /*************************/

       /* leave critial section */

       OS_EXIT_CRITICAL(); 

       return FALSE;

    }

 

    msg_free_list = msg_free_list->next;

 

    /*************************/

    /* leave critial section */

    OS_EXIT_CRITICAL(); 

 

    *newMsg = *lpMsg;

    /* add to tail */

    if(msgTail)

       msgTail->next = newMsg;

    else

       msgTail = msgHead = newMsg;

    newMsg->next = NULL;

    return TRUE;

}

 

 

 

/**

 * Message Pool operation Function

 */

void InitMsgPool()

{

    msg_free_list = &msg_pool[0];

    for(int i=0; i<TOTAL_MSG_POOL-1; )

       msg_pool[i].next = msg_pool[++i];

    msg_pool[TOTAL_MSG_POOL-1].next = NULL;

}

 

/**

 * Message Function declaration

 * GetMessage比较复杂。

 * WIN32的机制来看,首先他要处理邮寄消息POSTMSG

 * 再处理QUIT消息,接下来是INPUT用户输入消息,PAINT重绘消息

 * 最后TIMER消息。

 * 这种消息优先模式,我到现在还是搞的不很清楚,但PAINTTIMER肯定是延后的

 * QUITINPUT之前也好理解,毕竟结束进程后,应该停止任何用户响应。

 * POST消息在所有消息之前,我分析是因为系统的消息都是POST消息,优先及应该

 * 高于用户输入。控制权当然应在系统手里。

 */

 

/* GetMessage */

BOOL GetMessage(

    LPMSG lpMsg,     

    HWND hWnd ,          

    UINT wMsgFilterMin,  /* default 0 */

    UINT wMsgFilterMax)   /* default 0 */

{

    LPMSG_QUEUE pMsgQueue;

    int timer_slot = 0;

    INT8U err;

    /* check if the hWnd is valid */

    if( !(pMsgQueue = GetMsgQueue(hWnd)) )

       return FALSE;

   

checkagain:  

 

    /* wait for new message to come */

    OSSemPend(pMsgQueue->msgSem,0,&err); /* 阻塞等待消息到来 */

   

    /**

     *  first check for POSTMSG state

     * this state infos that there are common post

     *  messages in the msg queue

     * */

    if(pMsgQueue->dwState & QS_POSTMSG) {

   

        if(pMsgQueue->firstPostMsg){

        DEL_FROM_LIST(pMsgQueue->firstPostMsg,

                    pMsgQueue->lastPostMsg,

                    lpMsg);

        ASSERT(lpMsg);

   

             if(pMsgQueue->firstPostMsg == NULL)

                pMsgQueue->dwState &= ~QS_POSTMSG;

       

        return TRUE;

        } else {

           pMsgQueue->dwState &= ~QS_POSTMSG;

        }

    }

    /**

     *  second check for QUIT state

     * this state infos that there is a post message

     *

     **/

    if (pMsgQueue->dwState & QS_QUIT) {

        lpMsg->hWnd = hWnd;

        lpMsg->uMsg = WM_QUIT;

        lpMsg->wParam = 0;

        lpMsg->lParam = 0;

 

        pMsgQueue->dwState &= ~QS_QUIT;

       

        return FALSE;

    }

    /**

     *  third check for INPUT state

     * this state infos that there is a input message

     *

     **/

    if (pMsgQueue->dwState & QS_INPUT) {

      

        if (pMsgQueue->firstInputMsg) {

             DEL_FROM_LIST(pMsgQueue->firstInputMsg,

                    pMsgQueue->lastInputMsg,

                    lpMsg);

       

             if(pMsgQueue->firstInputMsg == NULL)

                pMsgQueue->dwState &= ~QS_INPUT;

           

            return TRUE;

        } else {

            pMsgQueue->dwState &= ~QS_INPUT;

        }

       

    }

 

    /**

     *  fourth check for PAINT state

     * this state infos that there is a paint message

     *

     **/

    if (pMsgQueue->dwState & QS_PAINT) {

        /**

* PAINT消息比较复杂,研究中

*/

        pMsgQueue->dwState &= ~QS_PAINT;

    }

   

    if (pMsgQueue->dwState & QS_DESKTIMER) {

        lpMsg->hwnd = HWND_DESKTOP;

        lpMsg->message = WM_TIMER;

        lpMsg->wParam = 0;

        lpMsg->lParam = 0;

 

        pMsgQueue->dwState &= ~QS_DESKTIMER;

        return TRUE;

    }

   

    /**

     *  sixth check for TIMER state

     * this state infos that there is a timer expired

     *

     **/

    if (pMsgQueue->dwState & QS_TIMER) {

        for (timer_slot = 0; timer_slot < DEF_NR_TIMERS;

         timer_slot++) {

            if (pMsgQueue->timerMask & (0×01 << slot))

                break;

        }

 

        if (timer_slot == DEF_NR_TIMERS) {

        /**

         * here the timer has all

          * been executed

         */

            pMsgQueue->dwState &= ~QS_TIMER;

        }

        else {

            lpMsg->hWnd = pMsgQueue->timerOwner[timer_slot];

            lpMsg->uMsg = WM_TIMER;

            lpMsg->wParam = pMsgQueue->timerID[timer_slot];

            lpMsg->lParam = 0;

           /* refresh the state of queue */

            pMsgQueue->dwState &= ~(0×01 << slot);

            return TRUE;

        }

    }

 

    /* no message, back to wait again. */

    goto checkagain;

    /* we never reached here */

    return TRUE;

}

 

/**

 * TranslateMessage Function

 * Function: translate keyboard message

 *

 * */

BOOL TranslateMessage(const MSG *lpMsg)

{

    /* do nothing */

}

 

/**

 * DispatchMessage Function

 * Function: dispath message which get from

 *           GetMessage.

 * */

LONG DispatchMessage(const MSG *lpMsg)

{

    WNDPROC WndProc;

 

    if (lpMsg->hWnd == HWND_INVALID)

        return HWND_INVALID;

 

    WndProc = GetWndProc(lpMsg->hWnd);

    return (*WndProc)(lpMsg->hWnd, lpMsg->uMsg,

        lpMsg->wParam, lpMsg->lParam);

}

 

/**

 * SendMessage Function

 * Function: we just directly call the wndProc

 *          to realize the message send.

 * 发送消息函数很简单,就是直接调用对方窗口的回调函数

 * 因为这里不存在进程间的保护,所以地址空间都是可见的

 * 直接阻塞的调用就ok

 * */

LRESULT SendMessage(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam)

{

    WNDPROC WndProc;

 

    if (hWnd == HWND_INVALID) return -1;

    WndProc = GetWndProc(hWnd);

    return (*WndProc)(hWnd, uMsg, wParam, lParam);

}

 

/**

 * PostMessage Function

 * Function: we just post POST message or PAINT message

 *          

 * */  

BOOL PostMessage(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam)

{

    LPMSG_QUEUE pMsgQueue;

 

    if( !(pMsgQueue = GetMsgQueue(hWnd)) ) return FALSE;

   

    if(uMsg == WM_PAINT){

    pMsgQueue->dwState |= QS_PAINT;

    goto ret_ok;

    }

    /* construct a new msg struct */

    MSG newMsg;

    newMsg.hWnd = hWnd;

    newMsg.uMsg = uMsg;

    newMsg.wParam = wParam;

    newMsg.lParam = lParam;

    /* add this msg to queue */

    if(!ADD_TO_LIST(pMsgQueue->firstPostMsg,

               pMsgQueue->lastPostMsg,

               &newMsg))

/**

 * file: message.c

 * author: <muddog@21cn.com>

 * date: 2004/8/24

 *

 */

#include “litegui.h”

#include “message.h”

#include “wnd.h”

 

 

/**

 * some util function

 * 这些函数是用来根据窗口句柄获得窗口消息队列及回调的

 * 窗口句柄在这里只是简单的用窗口结构的地址来表示,不象

 * windows那样的资源表

 */

static LPMSG_QUEUE GetMsgQueue(HWND hWnd)

{

    PMAIN_WND pMainWnd;

    if(hWnd == HWND_INVALID)

    return NULL;

    if(hWnd == HWND_DESKTOP)

       return g_deskTop.pMsgQueue;

   

    pMainWnd = (PMAIN_WND)hWnd;

    return pMainWnd->pMsgQueue;

}

 

static WNDPROC GetWndProc(HWND hWnd)

{

    PMAIN_WND pMainWin = (PMAIN_WND)hWnd;

 

    if(hWnd == HWND_DESKTOP)

       return g_deskTop.DesktopWinProc;

      

    return pMainWin->MainWndProc;

}

 

/**

* 以下结构很重要,一个是消息结构池,存放使用及未使用的消息结构

* free_list则是一个未使用的消息结构的连表,我们从free_list连表中

* 分配新的消息结构给需要消息传递的进程。

* 这里的消息结构的分配释放有n中方法,比如:miniGUI中的读者写者的方式,就是

* 利用一个静态的循环静态数组,指定读位置和写位置,发送者修改写位置,收取者

* 修改读位置,这个不难。还有就是连表,就想linuxwait_list一样。我们采用

* 后者,当然我在原始消息队列中采用了前者(和驱动打交道的进程)。

* 结构池也可以是静态的或动态的,动态需要malloc,但liteGUI是个小型系统,

* 没有必要为malloc牺牲性能,但有可能在后面的窗口资源管理上,会用上动态的

* 结构分配。

*/

/* Message struct Pool */

/* message pool */

MSG msg_pool[TOTAL_MSG_POOL];

/* list header of free msg in pool */

LPMSG msg_free_list;

 

 

/**

 *  MSG list on Message Queue

 * Operation function

 *  这两个inline实现的是向队列中插入消息结构

 * 和取下一个消息结构。插入在队列尾,取在队列头。

 *  这里有个OS_ENTER_CRITIALOS_EXIT_CRITIAL

 *  这个是临界段的进入和离开,当然也就是竞态的代码

 *  ucOS提供的API本质上就是关中断,开中断

 **/

/* remove a msg struct from the msgHeader in queue*/

inline void DEL_FROM_LIST(

                  LPMSG msgHead,

                  LPMSG msgTail,

                  LPMSG lpMsg) 

{                

    LPMSG rmMsg;

    /* remove from msg queue list */  

    rmMsg = msgHead;               

    ASSERT(rmMsg);                    

    msgHead = msgHead->next;

    /* enter critial section */

    OS_ENTER_CRITICAL();    

    /* add to free list */            

    rmMsg->next = msg_free_list;      

    msg_free_list = rmMsg;         

    /* refresh tail pointer */ 

    if(!msgHead)

       msgTail = NULL;

    *lpMsg = *rmMsg;

    /* leave critical section */

    OS_EXIT_CRITICAL();        

}

 

/**

 *  add a msg struct to the msgHeader in queue

 * return:

 *      if success TRUE

 *      else there’s no more free msg struct FALSE

 *

 * */

inline BOOL ADD_TO_LIST(

                  LPMSG msgHead,

                  LPMSG msgTail,

                  LPMSG lpMsg)

{

    LPMSG newMsg;

 

    /*************************/

    /* enter critial section */

    OS_ENTER_CRITICAL();

   

    /* get a free msg struct from msg pool */

    if( !(newMsg = msg_free_list) ){

       /*************************/

       /* leave critial section */

       OS_EXIT_CRITICAL(); 

       return FALSE;

    }

 

    msg_free_list = msg_free_list->next;

 

    /*************************/

    /* leave critial section */

    OS_EXIT_CRITICAL(); 

 

    *newMsg = *lpMsg;

    /* add to tail */

    if(msgTail)

       msgTail->next = newMsg;

    else

       msgTail = msgHead = newMsg;

    newMsg->next = NULL;

    return TRUE;

}

 

 

 

/**

 * Message Pool operation Function

 */

void InitMsgPool()

{

    msg_free_list = &msg_pool[0];

    for(int i=0; i<TOTAL_MSG_POOL-1; )

       msg_pool[i].next = msg_pool[++i];

    msg_pool[TOTAL_MSG_POOL-1].next = NULL;

}

 

/**

 * Message Function declaration

 * GetMessage比较复杂。

 * WIN32的机制来看,首先他要处理邮寄消息POSTMSG

 * 再处理QUIT消息,接下来是INPUT用户输入消息,PAINT重绘消息

 * 最后TIMER消息。

 * 这种消息优先模式,我到现在还是搞的不很清楚,但PAINTTIMER肯定是延后的

 * QUITINPUT之前也好理解,毕竟结束进程后,应该停止任何用户响应。

 * POST消息在所有消息之前,我分析是因为系统的消息都是POST消息,优先及应该

 * 高于用户输入。控制权当然应在系统手里。

 */

 

/* GetMessage */

BOOL GetMessage(

    LPMSG lpMsg,     

    HWND hWnd ,          

    UINT wMsgFilterMin,  /* default 0 */

    UINT wMsgFilterMax)   /* default 0 */

{

    LPMSG_QUEUE pMsgQueue;

    int timer_slot = 0;

    INT8U err;

    /* check if the hWnd is valid */

    if( !(pMsgQueue = GetMsgQueue(hWnd)) )

       return FALSE;

   

checkagain:  

 

    归类于: 未分类 — muddog @ 3:43 pm 评论关闭

/**

 *  file: message.h

 *  author: <muddog@21cn.com>

 *  data: 2004/8/24

 */

 

#ifndef _LGUI_MESSAGE_H

#define _LGUI_MESSAGE_H

 

#include “type.h”

 

/**

* 世界上所有邮局能够积压的最大包裹数量,

* 当然就是liteGUI消息系统中消息结构的同时最大使用量

* 这个值还没验证呢,要看ARM7的性能和我们的系统反映能力了

* 等整个系统构件好了,测试后再改

*/

#define TOTAL_MSG_POOL   50

 

/**

 *  define message struct

 *  externel for linkage

 */

typedef struct _msg {

   

    HWND   hWnd;     // handle to destination window

    UINT   uMsg;     // message

    WPARAM wParam;   // first message parameter

    LPARAM lParam;   // second message parameter

    struct _msg* next;       // next msg in msg link

 

} MSG;

/**

* 以上是消息结构,很普通,但我增加了一个连表成员

* 就是放入消息队列中排队用的

*/

typedef MSG* LPMSG;

 

/**

 *  Message queue struct defination

 *  which is a member of the main window info struct

 */

typedef struct _msg_queue {

   

    DWORD  dwState;       /* queue state */

    /**

     *  post message list

     *  firstPostMsg refer to the earliest msg

     *  lastPostMsg refer to the newset msg

     **/

    LPMSG  firstPostMsg; 

    LPMSG  lastPostMsg;

    /**

     *  input message list

     *  firstInputMsg refer to the earliest msg

     *  lastInputMsg refer to the newset msg

     **/

    LPMSG  firstInputMsg;

    LPMSG  lastInputMsg; 

    /**

     * semaphore for msg

     * the sempahore count refers to

     * how many messages are in queue now

     * */

    OS_EVENT *msgSem; // 这个是ucOS的信号量,标示着该消息队列中有多少未处理消息

    /**

     *  timer members

     **/

    HWND   timerOwner[DEF_NR_TIMERS]; /* 定义定时器的窗口,主或者控件窗口 */

    int    timerID[DEF_NR_TIMERS];   /* 定时器id */

    BYTE   timerMask; /* 定时器是否过期的掩码 */

 

} MSG_QUEUE;

typedef MSG_QUEUE* LPMSG_QUEUE;

/**

*  以上结构中的first/lastPostMsg first/lastInputMsg

*  是两个不同的消息连表,一个是为post message,一个是用户外部输入的消息

*  后面的message.c会谈到。Timer是窗口中建立定时器用的,一个主窗口

*  最多能定义DEF_NR_TIMERS个定时器

*/

 

/**

 * Message Pool operation Function

 */

void InitMsgPool();

 

/**

 * Message Function declaration

 *

 * 以下是消息API的原型声明,和WIN32没什么区别

 *

 */

 

/* GetMessage */

BOOL GetMessage(

    LPMSG lpMsg,      /* message to fill */

    HWND hWnd ,           /* Handle to the window whose messages

                      are to be retrieved. The window must

                      belong to the calling thread.  */

    UINT wMsgFilterMin = 0, /* reservered to be 0*/

    UINT wMsgFilterMax = 0);/* reservered to be 0*/

 

BOOL TranslateMessage(const MSG *lpMsg);

 

LONG DispatchMessage(const MSG *lpMsg);

 

LRESULT SendMessage(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam);

   

BOOL PostMessage(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam);

 

/**

 * Message Queues Function Declaration

 *

 */

void InitMsgQueue(LPMSG_QUEUE pMsgQueue);

void DestroyMsgQueue (LPMSG_QUEUE pMsgQueue);  

 

#endif /*_LGUI_MESSAGE_H*/

 


       消息机制是WIN32系统最为重要的部分。也是windows能够运转的核心驱动力。WIN32消息机制是进程间通讯的最主要的方式。当你需要两个进程互相通讯时,最便捷的方式便是在各自的进程中建立一个隐藏窗体,利用该窗体的句柄和回调,双方的通讯得以实现。这种机制已经被广泛的应用到了微内核的OS及大多数的GUI系统。对于我们现在开发的liteGUI来说,消息机制无疑也是值得思考和运用的。

       考虑到我们的GUI是在ucOS的内核上构件,并且是专用系统,无须考虑太多移植性,小巧并且需要高效,所以采用X-window引擎那样的C/S模式显然是不适合的,并且在嵌入式环境下这样的开销也是不值得的。从MINIX的微内核的设计思想及WIN32的设计中,我们找到了消息这样一个好东西。可以说liteGUI的消息机制更象是MINIX,因为MINIX的进程管理还处在一个比较原始的状态,内核空间和用户空间划分的并不明显,也就是说,MINIX的安全性能很差,但是它有是高效,消息的发送和接收无须切入内核空间,无须做繁复权限的检查。LiteGUI因为是构件在ucOS上的,从本质上来讲ucOS更象是个lib,提供比较完善的进程管理、同步、通讯的api,但没有复杂的保护和安全机制,没有FS,没有MM……因为在嵌入式环境特别是实时环境下,这些都是冗余烦琐的。所以在ucOS上构件系统,更象是在一个大的项目中引入ucOS的进程管理机制来更好的实现需求,ucOS本身并不是一个影象,而我们的系统才是个正真能够跑起来的影象,就想51单片机上的程序一样。所以我们的消息可以在这个系统的内部高效快速的传递,而不是在进程间的通讯。当然转化到ucOS的角度来说,这样的消息传递还是进程间的。这里只是一种概念上的和实际实现上的区别。

       下面来看看liteGUI的消息机制。参考了WIN32MINIXminiGUI等系统的消息机制,你会发现思想完全一样。消息,无非是一个可以容纳信息的结构或者数据。我们的目的就是将这些信息传递到指定的消息接受体上。每个消息都象一个包裹,我们定义他为MSG结构(参看message.h)。包裹里可以放入的是该消息的发送者,接受者及消息的内容,当然大家都应该比较熟悉win32APISendMessagePostMessage了,这里就不在重复消息这个结构的具体情况。日常生活中的包裹我们都是需要邮寄出去,或者到邮局去领的,我们的包裹传递的载体是邮政单位,或者邮局,那么liteGUI里对应的就应该是主窗口。GUI是 个和用户交互的系统,主窗口则承担了交互的前沿工作,正入邮局,我们接受到用户的动作后,主窗口做出响应,打包响应的消息给接受窗口,接收窗口在解包处 理。当然邮局也是每天定时的发送和接收批量的包裹的,每天至少一次(偶也不晓得,天知道一天几次,你的包裹肯定会在邮局积压一段时间,等到固定时间,工作 人员才处理这些包裹)。也就是说,寄送和接收包裹各自并不是同步的,而且两个动作也不是同步的。我们的系统也一样,发送消息时,系统会暂时将包裹放在邮局 (消息队列中),等到接收者收到邮局的通知单(这里利用的ucOS的 信号量)才会去邮局认领(消息队列中获取消息,回调)。好了,现在应该清楚了,我们的主窗口就是各自当地的邮局,主窗口中有个叫消息队列的,它是邮局的包 裹库,负责接收外来的包裹。主窗口的消息回调就象是邮局中的分检员,根据不用的包裹类型和包裹发送接收者等信息做相应的处理,如:接收者无效,则丢弃或者 退还包裹,如果接收者来认领则,验证身份,给认领者,等等。

       还有很多东西没讲,我想大家看了代码应该清楚了,反正注释写着。

PS:不知道这文章大家看了如何?不知道我说的清楚不清楚,具体实现内容,我放在原代码一起讲了。大家给点回应我啊,我这也是为毕业论文、为项目文档练练笔了。3x

       消息机制是WIN32系统最为重要的部分。也是windows能够运转的核心驱动力。WIN32消息机制是进程间通讯的最主要的方式。当你需要两个进程互相通讯时,最便捷的方式便是在各自的进程中建立一个隐藏窗体,利用该窗体的句柄和回调,双方的通讯得以实现。这种机制已经被广泛的应用到了微内核的OS及大多数的GUI系统。对于我们现在开发的liteGUI来说,消息机制无疑也是值得思考和运用的。

       考虑到我们的GUI是在ucOS的内核上构件,并且是专用系统,无须考虑太多移植性,小巧并且需要高效,所以采用X-window引擎那样的C/S模式显然是不适合的,并且在嵌入式环境下这样的开销也是不值得的。从MINIX的微内核的设计思想及WIN32的设计中,我们找到了消息这样一个好东西。可以说liteGUI的消息机制更象是MINIX,因为MINIX的进程管理还处在一个比较原始的状态,内核空间和用户空间划分的并不明显,也就是说,MINIX的安全性能很差,但是它有是高效,消息的发送和接收无须切入内核空间,无须做繁复权限的检查。LiteGUI因为是构件在ucOS上的,从本质上来讲ucOS更象是个lib,提供比较完善的进程管理、同步、通讯的api,但没有复杂的保护和安全机制,没有FS,没有MM……因为在嵌入式环境特别是实时环境下,这些都是冗余烦琐的。所以在ucOS上构件系统,更象是在一个大的项目中引入ucOS的进程管理机制来更好的实现需求,ucOS本身并不是一个影象,而我们的系统才是个正真能够跑起来的影象,就想51单片机上的程序一样。所以我们的消息可以在这个系统的内部高效快速的传递,而不是在进程间的通讯。当然转化到ucOS的角度来说,这样的消息传递还是进程间的。这里只是一种概念上的和实际实现上的区别。

       下面来看看liteGUI的消息机制。参考了WIN32MINIXminiGUI等系统的消息机制,你会发现思想完全一样。消息,无非是一个可以容纳信息的结构或者数据。我们的目的就是将这些信息传递到指定的消息接受体上。每个消息都象一个包裹,我们定义他为MSG结构(参看message.h)。包裹里可以放入的是该消息的发送者,接受者及消息的内容,当然大家都应该比较熟悉win32APISendMessagePostMessage了,这里就不在重复消息这个结构的具体情况。日常生活中的包裹我们都是需要邮寄出去,或者到邮局去领的,我们的包裹传递的载体是邮政单位,或者邮局,那么liteGUI里对应的就应该是主窗口。GUI是 个和用户交互的系统,主窗口则承担了交互的前沿工作,正入邮局,我们接受到用户的动作后,主窗口做出响应,打包响应的消息给接受窗口,接收窗口在解包处 理。当然邮局也是每天定时的发送和接收批量的包裹的,每天至少一次(偶也不晓得,天知道一天几次,你的包裹肯定会在邮局积压一段时间,等到固定时间,工作 人员才处理这些包裹)。也就是说,寄送和接收包裹各自并不是同步的,而且两个动作也不是同步的。我们的系统也一样,发送消息时,系统会暂时将包裹放在邮局 (消息队列中),等到接收者收到邮局的通知单(这里利用的ucOS的 信号量)才会去邮局认领(消息队列中获取消息,回调)。好了,现在应该清楚了,我们的主窗口就是各自当地的邮局,主窗口中有个叫消息队列的,它是邮局的包 裹库,负责接收外来的包裹。主窗口的消息回调就象是邮局中的分检员,根据不用的包裹类型和包裹发送接收者等信息做相应的处理,如:接收者无效,则丢弃或者 退还包裹,如果接收者来认领则,验证身份,给认领者,等等。

       还有很多东西没讲,我想大家看了代码应该清楚了,反正注释写着。

PS:不知道这文章大家看了如何?不知道我说的清楚不清楚,具体实现内容,我放在原代码一起讲了。大家给点回应我啊,我这也是为毕业论文、为项目文档练练笔了。3x

 

 

 

/**

 *  file: message.h

 *  author: <muddog@21cn.com>

 *  data: 2004/8/24

 */

 

#ifndef _LGUI_MESSAGE_H

#define _LGUI_MESSAGE_H

 

#include “type.h”

 

/**

* 世界上所有邮局能够积压的最大包裹数量,

* 当然就是liteGUI消息系统中消息结构的同时最大使用量

* 这个值还没验证呢,要看ARM7的性能和我们的系统反映能力了

* 等整个系统构件好了,测试后再改

*/

#define TOTAL_MSG_POOL   50

 

/**

 *  define message struct

 *  externel for linkage

 */

typedef struct _msg {

   

    HWND   hWnd;     // handle to destination window

    UINT   uMsg;     // message

    WPARAM wParam;   // first message parameter

    LPARAM lParam;   // second message parameter

    struct _msg* next;       // next msg in msg link

 

} MSG;

/**

* 以上是消息结构,很普通,但我增加了一个连表成员

* 就是放入消息队列中排队用的

*/

typedef MSG* LPMSG;

 

/**

 *  Message queue struct defination

 *  which is a member of the main window info struct

 */

typedef struct _msg_queue {

   

    DWORD  dwState;       /* queue state */

    /**

     *  post message list

     *  firstPostMsg refer to the earliest msg

     *  lastPostMsg refer to the newset msg

     **/

    LPMSG  firstPostMsg; 

    LPMSG  lastPostMsg;

    /**

     *  input message list

     *  firstInputMsg refer to the earliest msg

     *  lastInputMsg refer to the newset msg

     **/

    LPMSG  firstInputMsg;

    LPMSG  lastInputMsg; 

    /**

     * semaphore for msg

     * the sempahore count refers to

     * how many messages are in queue now

     * */

    OS_EVENT *msgSem; // 这个是ucOS的信号量,标示着该消息队列中有多少未处理消息

    /**

     *  timer members

     **/

    HWND   timerOwner[DEF_NR_TIMERS]; /* 定义定时器的窗口,主或者控件窗口 */

    int    timerID[DEF_NR_TIMERS];   /* 定时器id */

    BYTE   timerMask; /* 定时器是否过期的掩码 */

 

} MSG_QUEUE;

typedef MSG_QUEUE* LPMSG_QUEUE;

/**

*  以上结构中的first/lastPostMsg first/lastInputMsg

*  是两个不同的消息连表,一个是为post message,一个是用户外部输入的消息

*  后面的message.c会谈到。Timer是窗口中建立定时器用的,一个主窗口

*  最多能定义DEF_NR_TIMERS个定时器

*/

 

/**

 * Message Pool operation Function

 */

void InitMsgPool();

 

/**

 * Message Function declaration

 *

 * 以下是消息API的原型声明,和WIN32没什么区别

 *

 */

 

/* GetMessage */

BOOL GetMessage(

    LPMSG lpMsg,      /* message to fill */

    HWND hWnd ,           /* Handle to the window whose messages

                      are to be retrieved. The window must

                      belong to the calling thread.  */

    UINT wMsgFilterMin = 0, /* reservered to be 0*/

    UINT wMsgFilterMax = 0);/* reservered to be 0*/

 

BOOL TranslateMessage(const MSG *lpMsg);

 

LONG DispatchMessage(const MSG *lpMsg);

 

LRESULT SendMessage(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam);

  &nbs

       消息机制是WIN32系统最为重要的部分。也是windows能够运转的核心驱动力。WIN32消息机制是进程间通讯的最主要的方式。当你需要两个进程互相通讯时,最便捷的方式便是在各自的进程中建立一个隐藏窗体,利用该窗体的句柄和回调,双方的通讯得以实现。这种机制已经被广泛的应用到了微内核的OS及大多数的GUI系统。对于我们现在开发的liteGUI来说,消息机制无疑也是值得思考和运用的。

       考虑到我们的GUI是在ucOS的内核上构件,并且是专用系统,无须考虑太多移植性,小巧并且需要高效,所以采用X-window引擎那样的C/S模式显然是不适合的,并且在嵌入式环境下这样的开销也是不值得的。从MINIX的微内核的设计思想及WIN32的设计中,我们找到了消息这样一个好东西。可以说liteGUI的消息机制更象是MINIX,因为MINIX的进程管理还处在一个比较原始的状态,内核空间和用户空间划分的并不明显,也就是说,MINIX的安全性能很差,但是它有是高效,消息的发送和接收无须切入内核空间,无须做繁复权限的检查。LiteGUI因为是构件在ucOS上的,从本质上来讲ucOS更象是个lib,提供比较完善的进程管理、同步、通讯的api,但没有复杂的保护和安全机制,没有FS,没有MM……因为在嵌入式环境特别是实时环境下,这些都是冗余烦琐的。所以在ucOS上构件系统,更象是在一个大的项目中引入ucOS的进程管理机制来更好的实现需求,ucOS本身并不是一个影象,而我们的系统才是个正真能够跑起来的影象,就想51单片机上的程序一样。所以我们的消息可以在这个系统的内部高效快速的传递,而不是在进程间的通讯。当然转化到ucOS的角度来说,这样的消息传递还是进程间的。这里只是一种概念上的和实际实现上的区别。

       下面来看看liteGUI的消息机制。参考了WIN32MINIXminiGUI等系统的消息机制,你会发现思想完全一样。消息,无非是一个可以容纳信息的结构或者数据。我们的目的就是将这些信息传递到指定的消息接受体上。每个消息都象一个包裹,我们定义他为MSG结构(参看message.h)。包裹里可以放入的是该消息的发送者,接受者及消息的内容,当然大家都应该比较熟悉win32APISendMessagePostMessage了,这里就不在重复消息这个结构的具体情况。日常生活中的包裹我们都是需要邮寄出去,或者到邮局去领的,我们的包裹传递的载体是邮政单位,或者邮局,那么liteGUI里对应的就应该是主窗口。GUI是 个和用户交互的系统,主窗口则承担了交互的前沿工作,正入邮局,我们接受到用户的动作后,主窗口做出响应,打包响应的消息给接受窗口,接收窗口在解包处 理。当然邮局也是每天定时的发送和接收批量的包裹的,每天至少一次(偶也不晓得,天知道一天几次,你的包裹肯定会在邮局积压一段时间,等到固定时间,工作 人员才处理这些包裹)。也就是说,寄送和接收包裹各自并不是同步的,而且两个动作也不是同步的。我们的系统也一样,发送消息时,系统会暂时将包裹放在邮局 (消息队列中),等到接收者收到邮局的通知单(这里利用的ucOS的 信号量)才会去邮局认领(消息队列中获取消息,回调)。好了,现在应该清楚了,我们的主窗口就是各自当地的邮局,主窗口中有个叫消息队列的,它是邮局的包 裹库,负责接收外来的包裹。主窗口的消息回调就象是邮局中的分检员,根据不用的包裹类型和包裹发送接收者等信息做相应的处理,如:接收者无效,则丢弃或者 退还包裹,如果接收者来认领则,验证身份,给认领者,等等。

       还有很多东西没讲,我想大家看了代码应该清楚了,反正注释写着。

PS:不知道这文章大家看了如何?不知道我说的清楚不清楚,具体实现内容,我放在原代码一起讲了。大家给点回应我啊,我这也是为毕业论文、为项目文档练练笔了。3x

 

 

 

/**

 *  file: message.h

 *  author: <muddog@21cn.com>

 *  data: 2004/8/24

 */

 

#ifndef _LGUI_MESSAGE_H

#define _LGUI_MESSAGE_H

 

#include “type.h”

 

/**

* 世界上所有邮局能够积压的最大包裹数量,

* 当然就是liteGUI消息系统中消息结构的同时最大使用量

* 这个值还没验证呢,要看ARM7的性能和我们的系统反映能力了

* 等整个系统构件好了,测试后再改

*/

#define TOTAL_MSG_POOL   50

 

/**

 *  define message struct

 *  externel for linkage

 */

typedef struct _msg {

   

    HWND   hWnd;     // handle to destination window

    UINT   uMsg;     // message

    WPARAM wParam;   // first message parameter

    LPARAM lParam;   // second message parameter

    struct _msg* next;       // next msg in msg link

 

} MSG;

/**

* 以上是消息结构,很普通,但我增加了一个连表成员

* 就是放入消息队列中排队用的

*/

typedef MSG* LPMSG;

 

/**

 *  Message queue struct defination

 *  which is a member of the main window info struct

 */

typedef struct _msg_queue {

   

    DWORD  dwState;       /* queue state */

    /**

     *  post message list

     *  firstPostMsg refer to the earliest msg

     *  lastPostMsg refer to the newset msg

     **/

    LPMSG  firstPostMsg; 

    LPMSG  lastPostMsg;

    /**

     *  input message list

     *  firstInputMsg refer to the earliest msg

     *  lastInputMsg refer to the newset msg

     **/

    LPMSG  firstInputMsg;

    LPMSG  lastInputMsg; 

    /**

     * semaphore for msg

     * the sempahore count refers to

     * how many messages are in queue now

     * */

    OS_EVENT *msgSem; // 这个是ucOS的信号量,标示着该消息队列中有多少未处理消息

    /**

     *  timer members

     **/

    HWND   timerOwner[DEF_NR_TIMERS]; /* 定义定时器的窗口,主或者控件窗口 */

    int    timerID[DEF_NR_TIMERS];   /* 定时器id */

    BYTE   timerMask; /* 定时器是否过期的掩码 */

 

} MSG_QUEUE;

typedef MSG_QUEUE* LPMSG_QUEUE;

/**

*  以上结构中的first/lastPostMsg first/lastInputMsg

*  是两个不同的消息连表,一个是为post message,一个是用户外部输入的消息

*  后面的message.c会谈到。Timer是窗口中建立定时器用的,一个主窗口

*  最多能定义DEF_NR_TIMERS个定时器

*/

 

/**

 * Message Pool operation Function

 */

void InitMsgPool();

 

/**

 * Message Function declaration

 *

 * 以下是消息API的原型声明,和WIN32没什么区别

 *

 */

 

/* GetMessage */

BOOL GetMessage(

    LPMSG lpMsg,      /* message to fill */

    HWND hWnd ,           /* Handle to the window whose messages

                      are to be retrieved. The window must

                      belong to the calling thread.  */

    UINT wMsgFilterMin = 0, /* reservered to be 0*/

    UINT wMsgFilterMax = 0);/* reservered to be 0*/

 

BOOL TranslateMessage(const MSG *lpMsg);

 

LONG DispatchMessage(const MSG *lpMsg);

 

LRESULT SendMessage(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam);

   

BOOL PostMessage(

    HWND hWnd,

    UINT uMsg,

    WPARAM wParam,

    LPARAM lParam);

 

/**

 * Message Queues Function Declaration

 *

 */

void InitMsgQueue(LPMSG_QUEUE pMsgQueue);

void DestroyMsgQueue (LPMSG_QUEUE pMsgQueue);  

 

#endif /*_LGUI_MESSAGE_H*/

 

 

 

 

 

 

/**

 * file: message.c

 * author: <muddog@21cn.com>

 * date: 2004/8/24

 *

 */

#include “litegui.h”

#include “message.h”

#include “wnd.h”

 

 

/**

 * some util function

 * 这些函数是用来根据窗口句柄获得窗口消息队列及回调的

 * 窗口句柄在这里只是简单的用窗口结构的地址来表示,不象

 * windows那样的资源表

 */

static LPMSG_QUEUE GetMsgQueue(HWND hWnd)

{

    PMAIN_WND pMainWnd;

    if(hWnd == HWND_INVALID)

    return NULL;

    if(hWnd == HWND_DESKTOP)

       return g_deskTop.pMsgQueue;

   

    pMainWnd = (PMAIN_WND)hWnd;

    return pMainWnd->pMsgQueue;

}

 

static WNDPROC GetWndProc(HWND hWnd)

{

    PMAIN_WND pMainWin = (PMAIN_WND)hWnd;

 

    if(hWnd == HWND_DESKTOP)

       return g_deskTop.DesktopWinProc;

      

    return pMainWin->MainWndProc;

}

 

/**

* 以下结构很重要,一个是消息结构池,存放使用及未使用的消息结构

* free_list则是一个未使用的消息结构的连表,我们从free_list连表中

* 分配新的消息结构给需要消息传递的进程。

* 这里的消息结构的分配释放有n中方法,比如:miniGUI中的读者写者的方式,就是

* 利用一个静态的循环静态数组,指定读位置和写位置,发送者修改写位置,收取者

* 修改读位置,这个不难。还有就是连表,就想linuxwait_list一样。我们采用

* 后者,当然我在原始消息队列中采用了前者(和驱动打交道的进程)。

* 结构池也可以是静态的或动态的,动态需要malloc,但liteGUI是个小型系统,

* 没有必要为malloc牺牲性能,但有可能在后面的窗口资源管理上,会用上动态的

* 结构分配。

*/

/* Message struct Pool */

/* message pool */

MSG msg_pool[TOTAL_MSG_POOL];

/* list header of free msg in pool */

LPMSG msg_free_list;

 

 

/**

 *  MSG list on Message Queue

 * Operation function

 *  这两个inline实现的是向队列中插入消息结构

 * 和取下一个消息结构。插入在队列尾,取在队列头。

 *  这里有个OS_ENTER_CRITIALOS_EXIT_CRITIAL

 *  这个是临界段的进入和离开,当然也就是竞态的代码

 *  ucOS提供的API本质上就是关中断,开中断

 **/

/* remove a msg struct from the msgHeader in queue*/

inline void DEL_FROM_LIST(

                  LPMSG msgHead,

                  LPMSG msgTail,

                  LPMSG lpMsg) 

{                

    LPMSG rmMsg;

    /* remove from msg queue list */  

    rmMsg = msgHead;               

    ASSERT(rmMsg);                    

    msgHead = msgHead->next;

    /* enter critial section */

    OS_ENTER_CRITICAL();    

    /* add to free list */            

    rmMsg->next = msg_free_list;      

    msg_free_list = rmMsg;         

    /* refresh tail pointer */ 

    if(!msgHead)

       msgTail = NULL;

    *lpMsg = *rmMsg;

    /* leave critical section */

    OS_EXIT_CRITICAL();        

}

 

/**

 *  add a msg struct to the msgHeader in queue

 * return:

 *      if success TRUE

 *      else there’s no more free msg struct FALSE

 *

 * */

inline BOOL ADD_TO_LIST(

                  LPMSG msgHead,

                  LPMSG msgTail,

                  LPMSG lpMsg)

{

    LPMSG newMsg;

 

    /*************************/

    /* enter critial section */

    OS_ENTER_CRITICAL();

   

    /* get a free msg struct from msg pool */

    if( !(newMsg = msg_free_list) ){

       /*************************/

       /* leave critial section */

       OS_EXIT_CRITICAL(); 

       return FALSE;

    }

 

    msg_free_list = msg_free_list->next;

 

    /*************************/

    /* leave critial section */

    OS_EXIT_CRITICAL(); 

 

    *newMsg = *lpMsg;

    /* add to tail */

    if(msgTail)

       msgTail->next = newMsg;

    else

       msgTail = msgHead = newMsg;

    newMsg->next = NULL;

    return TRUE;

}

 

 

 

/**

 * Message Pool operation Function

 */

void InitMsgPool()

{

    msg_free_list = &msg_pool[0];

    for(int i=0; i<TOTAL_MSG_POOL-1; )

       msg_pool[i].next = msg_pool[++i];

    msg_pool[TOTAL_MSG_POOL-1].next = NULL;

}

 

/**

 * Message Function declaration

 * GetMessage比较复杂。

 * WIN32的机制来看,首先他要处理邮寄消息POSTMSG

 * 再处理QUIT消息,接下来是INPUT用户输入消息,PAINT重绘消息

 * 最后TIMER消息。

 * 这种消息优先模式,我到现在还是搞的不很清楚,但PAINTTIMER肯定是延后的

 * QUITINPUT之前也好理解,毕竟结束进程后,应该停止任何用户响应。

 * POST消息在所有消息之前,我分析是因为系统的消息都是POST消息,优先及应该

 * 高于用户输入。控制权当然应在系统手里。

 */

 

/* GetMessage */

BOOL GetMessage(

    LPMSG lpMsg,     

    HWND hWnd ,          

    UINT wMsgFilterMin,  /* default 0 */

    UINT wMsgFilterMax)   /* default 0 */

{

    LPMSG_QUEUE pMsgQueue;

    int timer_slot = 0;

    INT8U err;

    /* check if the hWnd is valid */

    if( !(pMsgQueue = GetMsgQueue(hWnd)) )

       return FALSE;

   

checkagain:  

 

    /* wait for new message to come */

    OSSemPend(pMsgQueue->msgSem,0,&err); /* 阻塞等待消息到来 */

   

    /**

     *  first check for POSTMSG state

     * this state infos that there are common post

     *  messages in the msg queue

     * */

    if(pMsgQueue->dwState & QS_POSTMSG) {

   

        if(pMsgQueue->firstPostMsg){

        DEL_FROM_LIST(pMsgQueue->firstPostMsg,

                    pMsgQueue->lastPostMsg,

                    lpMsg);

        ASSERT(lpMsg);

   

             if(pMsgQueue->firstPostMsg == NULL)

                pMsgQueue->dwState &= ~QS_POSTMSG;

       

        return TRUE;

        } else {

           pMsgQueue->dwState &= ~QS_POSTMSG;

        }

    }

    /**归类于: 未分类 — muddog @ 3:26 pm 评论关闭

2004年08月16日

Linux2.6内核驱动移植参考
作者:晏渭川
随着Linux2.6的发布,由于2.6内核做了教的改动,各个设备的驱动程序在不同程度上要
进行改写。为了方便各位Linux爱好者我把自己整理的这分文档share出来。该文当列举
了2.6内核同以前版本的绝大多数变化,可惜的是由于时间和精力有限没有详细列出各个
函数的用法。
特别声明:该文档中的内容来自http://lwn.net,该网也上也有各个函数的较为详细的
说明可供各位参考。如果需要该文档的word版的朋友, 请mail到weiriver@sohu.com索
取。

1、 使用新的入口
必须包含 <linux/init.h>
module_init(your_init_func);
module_exit(your_exit_func);
老版本:int init_module(void);
void cleanup_module(voi);
2.4中两种都可以用,对如后面的入口函数不必要显示包含任何头文件。
2、 GPL
MODULE_LICENSE(“Dual BSD/GPL”);
老版本:MODULE_LICENSE(“GPL”);
3、 模块参数
必须显式包含<linux/moduleparam.h>
module_param(name, type, perm);
module_param_named(name, value, type, perm);
参数定义
module_param_string(name, string, len, perm);
module_param_array(name, type, num, perm);
老版本:MODULE_PARM(variable,type);
MODULE_PARM_DESC(variable,type);
4、 模块别名
MODULE_ALIAS(“alias-name”);
这是新增的,在老版本中需在/etc/modules.conf配置,现在在代码中就可以实现。
5、 模块计数
int try_module_get(&module);
module_put();
老版本:MOD_INC_USE_COUNT 和 MOD_DEC_USE_COUNT
6、 符号导出
只有显示的导出符号才能被其他模块使用,默认不导出所有的符号,不必使用EXPORT_NO
_SYMBOLS
老板本:默认导出所有的符号,除非使用EXPORT_NO_SYMBOLS
7、 内核版本检查
需要在多个文件中包含<linux/module.h>时,不必定义__NO_VERSION__
老版本:在多个文件中包含<linux/module.h>时,除在主文件外的其他文件中必须定义_
_NO_VERSION__,防止版本重复定义。
8、 设备号
kdev_t被废除不可用,新的dev_t拓展到了32位,12位主设备号,20位次设备号。
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
老版本:8位主设备号,8位次设备号
int MAJOR(kdev_t dev);
int MINOR(kdev_t dev);
9、 内存分配头文件变更
所有的内存分配函数包含在头文件<linux/slab.h>,而原来的<linux/malloc.h>不存在
老版本:内存分配函数包含在头文件<linux/malloc.h>
10、 结构体的初试化
gcc开始采用ANSI C的struct结构体的初始化形式:
static struct some_structure = {
.field1 = value,
.field2 = value,
..
};
老版本:非标准的初试化形式
static struct some_structure = {
field1: value,
field2: value,
..
};
11、 用户模式帮助器
int call_usermodehelper(char *path, char **argv, char **envp,
int wait);
新增wait参数
12、 request_module()
request_module(“foo-device-%d”, number);
老版本:
char module_name[32];
printf(module_name, “foo-device-%d”, number);
request_module(module_name);
13、 dev_t引发的字符设备的变化
1、取主次设备号为
unsigned iminor(struct inode *inode);
unsigned imajor(struct inode *inode);
2、老的register_chrdev()用法没变,保持向后兼容,但不能访问设备号大于256的设备

3、新的接口为
a)注册字符设备范围
int register_chrdev_region(dev_t from, unsigned count, char *name);
b)动态申请主设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, char
*name);
看了这两个函数郁闷吧^_^!怎么和file_operations结构联系起来啊?别急!
c)包含 <linux/cdev.h>,利用struct cdev和file_operations连接
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *cdev, struct file_operations *fops);
int cdev_add(struct cdev *cdev, dev_t dev, unsigned count);
(分别为,申请cdev结构,和fops连接,将设备加入到系统中!好复杂啊!)
d)void cdev_del(struct cdev *cdev);
只有在cdev_add执行成功才可运行。
e)辅助函数
kobject_put(&cdev->kobj);
struct kobject *cdev_get(struct cdev *cdev);
void cdev_put(struct cdev *cdev);
这一部分变化和新增的/sys/dev有一定的关联。
14、 新增对/proc的访问操作
<linux/seq_file.h>
以前的/proc中只能得到string, seq_file操作能得到如long等多种数据。
相关函数:
static struct seq_operations 必须实现这个类似file_operations得数据中得各个成
员函数。
seq_printf();
int seq_putc(struct seq_file *m, char c);
int seq_puts(struct seq_file *m, const char *s);
int seq_escape(struct seq_file *m, const char *s, const char *esc);
int seq_path(struct seq_file *m, struct vfsmount *mnt,
struct dentry *dentry, char *esc);
seq_open(file, &ct_seq_ops);
等等
15、 底层内存分配
1、<linux/malloc.h>头文件改为<linux/slab.h>
2、分配标志GFP_BUFFER被取消,取而代之的是GFP_NOIO 和 GFP_NOFS
3、新增__GFP_REPEAT,__GFP_NOFAIL,__GFP_NORETRY分配标志
4、页面分配函数alloc_pages(),get_free_page()被包含在<linux/gfp.h>中
5、对NUMA系统新增了几个函数:
a) struct page *alloc_pages_node(int node_id,
unsigned int gfp_mask,
unsigned int order);
b) void free_hot_page(struct page *page);
c) void free_cold_page(struct page *page);
6、 新增Memory pools
<linux/mempool.h>
mempool_t *mempool_create(int min_nr,
mempool_alloc_t *alloc_fn,
mempool_free_t *free_fn,
void *pool_data);
void *mempool_alloc(mempool_t *pool, int gfp_mask);
void mempool_free(void *element, mempool_t *pool);
int mempool_resize(mempool_t *pool, int new_min_nr, int gfp_mask);
16、 per-CPU变量
get_cpu_var();
put_cpu_var();
void *alloc_percpu(type);
void free_percpu(const void *);
per_cpu_ptr(void *ptr, int cpu)
get_cpu_ptr(ptr)
put_cpu_ptr(ptr)
老版本使用
DEFINE_PER_CPU(type, name);
EXPORT_PER_CPU_SYMBOL(name);
EXPORT_PER_CPU_SYMBOL_GPL(name);
DECLARE_PER_CPU(type, name);
DEFINE_PER_CPU(int, mypcint);
2.6内核采用了可剥夺得调度方式这些宏都不安全。
17、 内核时间变化
1、现在的各个平台的HZ为
Alpha: 1024/1200; ARM: 100/128/200/1000; CRIS: 100; i386: 1000; IA-64:
1024; M68K: 100; M68K-nommu: 50-1000; MIPS: 100/128/1000; MIPS64: 100;
PA-RISC: 100/1000; PowerPC32: 100; PowerPC64: 1000; S/390: 100; SPARC32:
100; SPARC64: 100; SuperH: 100/1000; UML: 100; v850: 24-100; x86-64: 1000.
2、由于HZ的变化,原来的jiffies计数器很快就溢出了,引入了新的计数器jiffies_64
3、#include <linux/jiffies.h>
u64 my_time = get_jiffies_64();
4、新的时间结构增加了纳秒成员变量
struct timespec current_kernel_time(void);
5、他的timer函数没变,新增
void add_timer_on(struct timer_list *timer, int cpu);
6、新增纳秒级延时函数
ndelay();
7、POSIX clocks 参考kernel/posix-timers.c
18、 工作队列(workqueue)
1、任务队列(task queue )接口函数都被取消,新增了workqueue接口函数
struct workqueue_struct *create_workqueue(const char *name);
DECLARE_WORK(name, void (*function)(void *), void *data);
INIT_WORK(struct work_struct *work,
void (*function)(void *), void *data);
PREPARE_WORK(struct work_struct *work,
void (*function)(void *), void *data);
2、申明struct work_struct结构
int queue_work(struct workqueue_struct *queue,
struct work_struct *work);
int queue_delayed_work(struct workqueue_struct *queue,
struct work_struct *work,
unsigned long delay);
int cancel_delayed_work(struct work_struct *work);
void flush_workqueue(struct workqueue_struct *queue);
void destroy_workqueue(struct workqueue_struct *queue);
int schedule_work(struct work_struct *work);
int schedule_delayed_work(struct work_struct *work, unsigned long
delay);
19、 新增创建VFS的”libfs”
libfs给创建一个新的文件系统提供了大量的API.
主要是对struct file_system_type的实现。
参考源代码:
drivers/hotplug/pci_hotplug_core.c
drivers/usb/core/inode.c
drivers/oprofile/oprofilefs.c
fs/ramfs/inode.c
fs/nfsd/nfsctl.c (simple_fill_super() example)
20、 DMA的变化
未变化的有:
void *pci_alloc_consistent(struct pci_dev *dev, size_t size,
dma_addr_t *dma_handle);
void pci_free_consistent(struct pci_dev *dev, size_t size,
void *cpu_addr, dma_addr_t dma_handle);
变化的有:
1、 void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, int flag);
void dma_free_coherent(struct device *dev, size_t size,
void *cpu_addr, dma_addr_t dma_handle);
2、列举了映射方向:
enum dma_data_direction {
DMA_BIDIRECTIONAL = 0,
DMA_TO_DEVICE = 1,
DMA_FROM_DEVICE = 2,
DMA_NONE = 3,
};
3、单映射
dma_addr_t dma_map_single(struct device *dev, void *addr,
size_t size,
enum dma_data_direction direction);
void dma_unmap_single(struct device *dev, dma_addr_t dma_addr,
size_t size,
enum dma_data_direction direction);
4、页面映射
dma_addr_t dma_map_page(struct device *dev, struct page *page,
unsigned long offset, size_t size,
enum dma_data_direction direction);
void dma_unmap_page(struct device *dev, dma_addr_t dma_addr,
size_t size,
enum dma_data_direction direction);
5、有关scatter/gather的函数:
int dma_map_sg(struct device *dev, struct scatterlist *sg,
int nents, enum dma_data_direction direction);
void dma_unmap_sg(struct device *dev, struct scatterlist *sg,
int nhwentries, enum dma_data_direction direction);
6、非一致性映射(Noncoherent DMA mappings)
void *dma_alloc_noncoherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, int flag);
void dma_sync_single_range(struct device *dev, dma_addr_t dma_handle,
unsigned long offset, size_t size,
enum dma_data_direction direction);
void dma_free_noncoherent(struct device *dev, size_t size,
void *cpu_addr, dma_addr_t dma_handle);
7、DAC (double address cycle)
int pci_dac_set_dma_mask(struct pci_dev *dev, u64 mask);
void pci_dac_dma_sync_single(struct pci_dev *dev,
dma64_addr_t dma_addr,
size_t len, int direction);
21、 互斥
新增seqlock主要用于:
1、少量的数据保护
2、数据比较简单(没有指针),并且使用频率很高
3、对不产生任何副作用的数据的访问
4、访问时写者不被饿死
<linux/seqlock.h>
初始化
seqlock_t lock1 = SEQLOCK_UNLOCKED;
或seqlock_t lock2; seqlock_init(&lock2);
void write_seqlock(seqlock_t *sl);
void write_sequnlock(seqlock_t *sl);
int write_tryseqlock(seqlock_t *sl);
void write_seqlock_irqsave(seqlock_t *sl, long flags);
void write_sequnlock_irqrestore(seqlock_t *sl, long flags);
void write_seqlock_irq(seqlock_t *sl);
void write_sequnlock_irq(seqlock_t *sl);
void write_seqlock_bh(seqlock_t *sl);
void write_sequnlock_bh(seqlock_t *sl);
unsigned int read_seqbegin(seqlock_t *sl);
int read_seqretry(seqlock_t *sl, unsigned int iv);
unsigned int read_seqbegin_irqsave(seqlock_t *sl, long flags);
int read_seqretry_irqrestore(seqlock_t *sl, unsigned int iv, long
flags);
22、 内核可剥夺
<linux/preempt.h>
preempt_disable();
preempt_enable_no_resched();
preempt_enable_noresched();
preempt_check_resched();
23、 眠和唤醒
1、原来的函数可用,新增下列函数:
prepare_to_wait_exclusive();
prepare_to_wait();
2、等待队列的变化
typedef int (*wait_queue_func_t)(wait_queue_t *wait,
unsigned mode, int sync);
void init_waitqueue_func_entry(wait_queue_t *queue,
wait_queue_func_t func);
24、 新增完成事件(completion events)
<linux/completion.h>
init_completion(&my_comp);
void wait_for_completion(struct completion *comp);
void complete(struct completion *comp);
void complete_all(struct completion *comp);
25、 RCU(Read-copy-update)
rcu_read_lock();
void call_rcu(struct rcu_head *head, void (*func)(void *arg),
void *arg);
26、 中断处理
1、中断处理有返回值了。
IRQ_RETVAL(handled);
2、cli(), sti(), save_flags(), 和 restore_flags()不再有效,应该使用local_save
_flags() 或local_irq_disable()。
3、synchronize_irq()函数有改动
4、新增int can_request_irq(unsigned int irq, unsigned long flags);
5、 request_irq() 和free_irq() 从 <linux/sched.h>改到了 <linux/interrupt.h>
27、 异步I/O(AIO)
<linux/aio.h>
ssize_t (*aio_read) (struct kiocb *iocb, char __user *buffer,
size_t count, loff_t pos);
ssize_t (*aio_write) (struct kiocb *iocb, const char __user *buffer,
size_t count, loff_t pos);
int (*aio_fsync) (struct kiocb *, int datasync);
新增到了file_operation结构中。
is_sync_kiocb(struct kiocb *iocb);
int aio_complete(struct kiocb *iocb, long res, long res2);
28、 网络驱动
1、struct net_device *alloc_netdev(int sizeof_priv, const char *name,
void (*setup)(struct net_device *));
struct net_device *alloc_etherdev(int sizeof_priv);
2、新增NAPI(New API)
void netif_rx_schedule(struct net_device *dev);
void netif_rx_complete(struct net_device *dev);
int netif_rx_ni(struct sk_buff *skb);
(老版本为netif_rx())
29、 USB驱动
老版本struct usb_driver取消了,新的结构体为
struct usb_class_driver {
char *name;
struct file_operations *fops;
mode_t mode;
int minor_base;
};
int usb_submit_urb(struct urb *urb, int mem_flags);
int (*probe) (struct usb_interface *intf,
const struct usb_device_id *id);
30、 block I/O 层
这一部分做的改动最大。不祥叙。
31、 mmap()
int remap_page_range(struct vm_area_struct *vma, unsigned long from,
unsigned long to, unsigned long size,
pgprot_t prot);
int io_remap_page_range(struct vm_area_struct *vma, unsigned long from,
unsigned long to, unsigned long size,
pgprot_t prot);
struct page *(*nopage)(struct vm_area_struct *area,
unsigned long address,
int *type);
int (*populate)(struct vm_area_struct *area, unsigned long address,
unsigned long len, pgprot_t prot, unsigned long pgoff,
int nonblock);
int install_page(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long addr, struct page *page,
pgprot_t prot);
struct page *vmalloc_to_page(void *address);
32、 零拷贝块I/O(Zero-copy block I/O)
struct bio *bio_map_user(struct block_device *bdev,
unsigned long uaddr,
unsigned int len,
int write_to_vm);
void bio_unmap_user(struct bio *bio, int write_to_vm);
int get_user_pages(struct task_struct *task,
struct mm_struct *mm,
unsigned long start,
int len,
int write,
int force,
struct page **pages,
struct vm_area_struct **vmas);
33、 高端内存操作kmaps
void *kmap_atomic(struct page *page, enum km_type type);
void kunmap_atomic(void *address, enum km_type type);
struct page *kmap_atomic_to_page(void *address);
老版本:kmap() 和 kunmap()。
34、 驱动模型
主要用于设备管理。
1、 sysfs
2、 Kobjects

2004年08月11日

GDB Man Page

这里是GDB的一个例子:
        原文中是使用一个叫m4的程序。但很遗憾我找不到这个程序的原代码,
所以没有办法来按照原文来说明。不过反正是个例子,我就拿一个操作系统的
进程调度原码来说明把,原代码我会附在后面。
        首先这个程序叫os.c是一个模拟进程调度的原程序(也许是个老古董了:-))。
先说明一下如何取得包括原代码符号的可执行代码。大家有心的话可以去看一下gcc的
man文件(在shell下打man gcc)。gcc -g <原文件.c> -o <要生成的文件名>
-g 的意思是生成带原代码调试符号的可执行文件。
-o 的意思是指定可执行文件名。
(gcc 的命令行参数有一大堆,有兴趣可以自己去看看。)
(忍不住要加个注,现在应该用gcc -ggdb指定吧!因为有很多人都在问,因为除了gdb还有别的工具:-)
反正在linux下把os.c用以上方法编译连接以后就产生了可供gdb使用的可执行文件。
我用gcc -g os.c -o os,产生的可执行文档叫os.
然后打gdb os,就可进入gdb,屏幕提示:
     GDB is free software and you are welcome to distribute copies
      of it under certain conditions; type “show copying” to see
      the conditions.
     There is absolutely no warranty for GDB; type “show warranty”
      for details.

     GDB 4.16, Copyright 1995 Free Software Foundation, Inc…
 (gdb)
  (gdb)是提示符,在这提示符下可以输入命令,直到退出。(退出命令是q/Q)
为了尽量和原文档说明的命令相符,即使在本例子中没用的命令我也将演示。
首先我们可以设置gdb的屏幕大小。键入:
 (gdb)set width 70
就是把标准屏幕设为70列。
  然后让我们来设置断点。设置方法很简单:break或简单打b后面加行号或函数名
比如我们可以在main 函数上设断点:
 (gdb)break main
或(gdb)b main
 系统提示:Breakpoint 1 at 0×8049552: file os.c, line 455.
 然后我们可以运行这个程序,当程序运行到main函数时程序就会停止返回到gdb的
提示符下。运行的命令是run或r(gdb中有不少alias,可以看一下help,在gdb下打help)
run 后面可以跟参数,就是为程序指定命令行参数。
比如r abcd,则程序就会abcd以作为参数。(这里要说明的是可以用set args来指定参
数)。打入r或run后,程序就开始运行直到进入main的入口停止,显示:
Starting program: <路径>/os

Breakpoint 1, main () at os.c:455
455            Initial();
这里455 Initial();是将要执行的命令或函数。
gdb提供两种方式:1.单步进入,step into就是跟踪到函数内啦。命令是step或s
                 2.单步,next,就是简单的单步,不会进入函数。命令是next或n
这两个命令还有别的用法以后再说。
我们用n命令,键入:
(gdb)n
Success forking process# 1 ,pid is 31474

Success forking process# 2 ,pid is 31475

Success forking process# 3 ,pid is 31476

Success forking process# 4 ,pid is 31477

Success forking process# 5 ,pid is 31478

Success forking process# 6 ,pid is 31479

                Dispatching Algorithm : FIFO
********************************************************************************

            PCB#        PID     Priority        PC      State
            1           31474      24            0      WAITING
            2           31475      19            0      WAITING
            3           31476      16            0      WAITING
            4           31477      23            0      WAITING
            5           31478      22            0      WAITING
            6           31479      20            0      WAITING

******************************************************************************

CPU  :  NO process running
IO :  No process
Waiting CPU!!!  31474   31475   31476   31477   31478   31479
Waiting  IO    NONE
456            State=WAITING;
最后的一行就是下一句要执行的命令。我们现在在另一个函数上加断点。注意我们
可以用l/list命令来显示原代码。这里我们键入
(gdb)l
451     main()
452     {
453             int message;
454
455            Initial();
456            State=WAITING;
457            printf(“Use Control-C to halt \n”);
458            signal(SIGALRM,AlarmMessage);
459            signal(SIGINT,InteruptMessage);
460            signal(SIGUSR2,IoMessage);
(gdb) l
461            alarm(TimeSlot);
462            for(;;)
463             {
464             message=GetMessage();
465                   switch(message)
466                     {
467                             case INTERRUPT :        printf(“Use Control-C t;

468                                                     break;
469                             case CHILD_IO:          WaitingIo();
470                                                     break;
显示了原代码,现在在AlarmMessage上加断点。
(gdb) b AlarmMessage
Breakpoint 2 at 0×8048ee3: file os.c, line 259.
(gdb)
然后我们继续运行程序。
(gdb)c
c或continue命令让我们继续被中断的程序。 显示:
Continuing.
Use Control-C to halt

Breakpoint 2, AlarmMessage () at os.c:259
259             ClearSignal();
注意我们下一句语句就是ClearSignal();
我们用s/step跟踪进入这个函数看看它是干什么的。
(gdb) s
ClearSignal () at os.c:227
227             signal(SIGINT,SIG_IGN);
用l命令列出原代码:
(gdb) l
222     }
223
224
225     void ClearSignal()    /* Clear other signals */
226     {
227             signal(SIGINT,SIG_IGN);
228             signal(SIGALRM,SIG_IGN);
229             signal(SIGUSR2,SIG_IGN);
230     }
231
(gdb)
我们可以用s命令继续跟踪。现在让我们来试试bt或backtrace命令。这个命令可以
显示栈中的内容。
(gdb) bt
#0  ClearSignal () at os.c:227
#1  0×8048ee8 in AlarmMessage () at os.c:259
#2  0xbffffaec in ?? ()
#3  0×80486ae in ___crt_dummy__ ()
(gdb)
大家一定能看懂显示的意思。栈顶是AlarmMessage,接下来的函数没有名字–就是
没有原代码符号。这显示了函数调用的嵌套。
好了,我们跟踪了半天还没有检查过变量的值呢。检查表达式的值的命令是p或print
格式是p <表达式>
444444让我们来找一个变量来看看。:-)
(gdb)l 1
还记得l的作用吗?l或list显示原代码符号,l或list加<行号>就显示从<行号>开始的
原代码。好了找到一个让我们来看看WaitingQueue的内容
(gdb) p WaitingQueue
$1 = {1, 2, 3, 4, 5, 6, 0}
(gdb)
WaitingQueue是一个数组,gdb还支持结构的显示,
(gdb) p Pcb
$2 = {{Pid = 0, State = 0, Prior = 0, pc = 0}, {Pid = 31474, State = 2,
    Prior = 24, pc = 0}, {Pid = 31475, State = 2, Prior = 19, pc = 0}, {
    Pid = 31476, State = 2, Prior = 16, pc = 0}, {Pid = 31477, State = 2,
    Prior = 23, pc = 0}, {Pid = 31478, State = 2, Prior = 22, pc = 0}, {
    Pid = 31479, State = 2, Prior = 20, pc = 0}}
(gdb)
这里可以对照原程序看看。
原文档里是一个调试过程,不过我想这里我已经把gdb的常用功能介绍了一遍,基本上
可以用来调试程序了。:-)

运行GDB及其参数配置: 
 
  
前面已经提到过如何运行GDB了,现在让我们来看一些更有趣的东西。你可以在运行 
GDB
时通过许多命令行参数指定大量的参数和选项,通过这个你可以在一开始就设置好 
程序运行的环境。 
  
这里将要描述的命令行参数覆盖了大多数的情况,事实上在一定环境下有的并没有 
什么大用处。最通常的命令就是使用一个参数: 
 $gdb <
可执行文档名
你还可以同时为你的执行文件指定一个core文件: 
 $gdb <
可执行文件名> core 
你也可以为你要执行的文件指定一个进程号: 
 $gdb <
可执行文件名> <进程号如:&gdb os 1234将使gdb与进程1234相联系(attach) 
除非你还有一个文件叫1234的。gdb首先检查一个core文件。 
如果你是使用一个远程终端进行远程调试的话,那如果你的终端不支持的话,你将无法 
使用第二个参数甚至没有core dump。如果你觉得开头的提示信息比较碍眼的话,你可以 
gdb -silent。你还可以用命令行参数更加详细的控制GDB的行为。 
打入gdb -help-h 可以得到这方面的提示。所有的参数都被按照排列的顺序传给gdb 
除非你用了-x参数。 
  
gdb开始运行时,它把任何一个不带选项前缀的参数都当作为一个可执行文件或core 
文件(或进程号)。就象在前面加了-se-c选项。gdb把第一个前面没有选项说明的参数 
看作前面加了-se 选项,而第二个(如果有的话)看作是跟着-c选项后面的。 
  
许多选项有缩写,用gdb -h可以看到。在gdb中你也可以任意的把选项名掐头去尾,只 
要保证gdb能判断唯一的一个参数就行。 
在这里我们说明一些最常用的参数选项 
-symbols <
文件名>(-s <文件名>)——<文件名>中读去符号。 
-exec <
文件名>(-e <文件名>)—-在合适的时候执行<文件名>来做用正确的数据与core 
 dump
的作比较。 
-se <
文件名>——<文件名>中读取符号并把它作为可执行文件。 
-core <
文件名>(-c <文件名>)–指定<文件名>为一个core dump 文件。 
-c <
数字>—-连接到进程号为<数字>,与attach命令相似。 
-command <
文件名
-x <
文件名>—–执行gdb命令,在<文件名>指定的文件中存放着一序列的gdb命令,就 
象一个批处理。 
-directory(-d) <
路径>—指定路径。把<路径>加入到搜索原文件的路径中。 
-m 
-mapped—- 
   
注意这个命令不是在所有的系统上都能用。如果你可以通过mmap系统调用来获得内存 
映象文件,你可以用这个命令来使gdb把你当前文件里的符号写入一个文件中,这个文件 
将存放在你的当前路径中。如果你调试的程序叫/temp/fred那么map文件就叫 
./fred.syms
这样当你以后再调试这个程序时,gdb会认识到这个文件的存在,从而从这 
个文件中读取符号,而不是从可执行文件中读取。.syms与主机有关不能共享。 
-r 
-readnow—
马上从符号文件中读取整个符号表,而不是使用缺省的。缺省的符号表是 
调入一部分符号,当需要时再读入一部分。这会使开始进入gdb慢一些,但可以加快以后 
的调试速度。 
 
 -m
-r一般在一起使用来建立.syms文件 

 

模式的选择
现在我们来聊聊gdb运行模式的选择。我们可以用许多模式来运行gdb,例如在“批模式”
或“安静模式”。这些模式都是在gdb运行时在命令行作为选项指定的。
`-nx’
`-n’
     不执行任何初始化文件中的命令。(一般初始化文件叫做`.gdbinit’).一般情况下在
这些文件中的命令会在所有的命令行参数都被传给gdb后执行。

`-quiet’
`-q’
     “安静模式”。不输出介绍和版权信息。这些信息在“批模式”中也被跳过。

`-batch’
     “批模式”。在“批模式”下运行。当在命令文件中的所有命令都被成功的执行后
     gdb返回状态“0”,如果在执行过程中出错,gdb返回一个非零值。
     “批模式”在把gdb作为一个过滤器运行时很有用。比如在一台远程计算机上下载且
     执行一个程序。信息“ Program exited normally”(一般是当运行的程序正常结束
     时出现)不会在这种模式中出现。
`-cd DIRECTORY’
     把DIRECTORY作为gdb的工作目录,而非当前目录(一般gdb缺省把当前目录作为工作目
     录)。
`-fullname’
`-f’
     GNU Emacs 设置这个选项,当我们在Emacs下,把gdb作为它的一个子进程来运行时,
     Emacs告诉gdb按标准输出完整的文件名和行号,一个可视的栈内容。这个格式跟在
     文件名的后面。行号和字符重新按列排,Emacs-to-GDB界面使用\032字符作为一个
     显示一页原文件的信号。
`-b BPS’
     为远程调试设置波特率。

`-tty DEVICE’
     使用DEVICE来作为你程序的标准输入输出。

退出gdb

quit’
     使用’quit’命令来退出gdb,或打一个文件结束符(通常是’ CTROL-D’)。如果
     你没有使用表达式,gdb会正常退出,否则它会把表达式的至作为error code
     返回。

     一个中断(通常是’CTROL-c)不会导致从gdb中退出,而是结束任何一个gdb的命
     令,返回gdb的命令输入模式。一般在任何时候使用’CTROL-C’是安全的,因为
     gdb会截获它,只有当安全时,中断才会起作用。
     如果你正在用gdb控制一个被连接的进程或设备,你可以用’detach’命令来释放
     它。

Shell命令
    当你偶尔要运行一些shell命令时,你不必退出调试过程,也不需要挂起它;你
    可以使用’shell’命令。

`shell COMMAND STRING’
     调用标准shell来执行’COMMAND STRING’.环境变量’SHELL’决定了那个shell被
     运行。否则gdb使用’/bin/sh’.
     ‘make’工具经常在开发环境中使用,所以你可以不用’shell’命令而直接打’make’

`make MAKE-ARGS’
     用指定的命令行变量来运行’make’程序,这等于使用’shell make MAKE-ARGS’
GDB 命令
   我们可以把一个gdb命令缩写成开头几个字母,如果这没有二意性你可以直接回车来
   运行。你还可以使用TAB键让gdb给你完成接下来的键入,或向你显示可选择的命令,
   如果有不止一个选择的话。

Command语法
   一个gdb命令是一个单行的输入。长度没有限制。它一个命令开头,后面可以跟参量。
   比如命令’step’接受一个参量表示单步执行多少步。你也可以不用参量。有的命令
   不接受任何参量。

   gdb命令只要没有二意性的话就可以被缩写。另外一些缩写作为一个命令列出。在某些
   情况下二意也是允许的。比如’s’是指定’step’的缩写,但还有命令’start’。你可以把
   这些缩写作为’help’命令的参量来测试它们。
   空行(直接回车)表示重复上一个命令。但有些命令不能重复比如象’run’,就不会以这
   种方式重复,另外一些当不小心重复会产生严重后果的命令也不能用这种方法重复。
   ‘list’和’x'命令当你简单的打回车时,会建立新的变量,而不是简单的重复上一个命
   令。这样你可以方便的浏览原代码和内存。
   gdb还有一种解释RET的方法:分割长输出。这种方法就和’more’命令相似。由于这时经
   常会不小心多打回车,gdb将禁止重复当一个命令产生很长的输出时。
   任何用’#'开头一直到行尾的命令行被看作是注释。主要在命令文件中使用。

输入命令的技巧
   前面已经提到过TAB键的使用。使用TAB键能让你方便的得到所要的命令。比如
gdb中:
   (gdb)info bre <TAB>(
键入info bre,后按TAB)
   gdb
能为你完成剩下的输入。它还能萎蔫提供选择的可能性。如果有两个以上可
能的话,第一次按<TAB>键,gdb会响铃提示,第二次则显示可能的选择。同样gdb
也可以为一些子命令提供快速的访问。用法与上相同。
 
上例中显示
   (gdb)info breakepoints
  
你也可以直接打回车,gdb就将你输入的作为命令的可能的缩写。来判断执行。
如果你打入的缩写不足以判断,那么gdb会显示一个列表,列出可能的命令。同样的
情况对于命令的参数。在显示完后gdb把你的输入拷贝到当前行以便让你继续输入。
  
如果你只想看看命令的列表或选项,你可以在命令行下打M-?(就是按着ESC
同时按SHIFT和?键)。你可以直接在命令行下打试试。
  (gdb)<M-?>
   gdb
会响铃并显示所有的命令。不过这种方式好象在远程调试是不行。当有的命令
使用一个字符串时,你可以用” ‘ “将其括起来。这种方法在调试C++程序时特别有用。
因为C++支持函数的重载。当你要在某个有重载函数上设断点时,不得不给出函数参数
以区分不同的重载函数。这时你就应该把整个函数用” ‘ “括起来。比如,你要在一个
name的函数上设断点,而这个函数被重载了(name(int)name(float))。你将不得
不给出参变量以区分不同的函数。使用‘name(int)’‘name(float)’。这里有个技巧,
你可以在函数名前加一个” ‘ “符号。然后打M-?.

得到帮助
你可以使用help命令来得到gdb的在线帮助。

`help’
`h’
      你可以使用help或h后面不加任何参数来得到一个gdb命令类的列表。

          (gdb) help
          List of classes of commands:

          running — Running the program
          stack — Examining the stack
          data — Examining data
          breakpoints — Making program stop at certain points
          files — Specifying and examining files
          status — Status inquiries
          support — Support facilities
          user-defined — User-defined commands
          aliases — Aliases of other commands
          obscure — Obscure features

          Type “help” followed by a class name for a list of
          commands in that class.
          Type “help” followed by command name for full
          documentation.
          Command name abbreviations are allowed if unambiguous.
          (gdb)

`help CLASS’
     使用上面列出的help class作为help或h的参量,你可以得到单一的命令列表。
     例如显示一个’status’类的列表。

          (gdb) help status
          Status inquiries.

          List of commands:

          show — Generic command for showing things set
           with “set”
          info — Generic command for printing status

          Type “help” followed by command name for full
          documentation.
          Command name abbreviations are allowed if unambiguous.
          (gdb)

`help COMMAND’
     详细列出单个命令的资料。

`complete ARGS’
     列出所有以ARGS开头的命令。例如:

          complete i

     results in:

          info
          inspect
          ignore

     This is intended for use by GNU Emacs.

   除了使用’help’你还可以使用gdb的命令’info’和’show’来查询你程序的
状态,每个命令可以查询一系列的状态。这些命令以恰当的方式显示所有的
子命令。

`info’
     此命令(可以缩写为’i')用来显示你程序的状态。比如,你可以使用info
args 列出你程序所接受的命令行参数。使用info registers列出寄存器的状态。
或用info breakpoint列出在程序中设的断点。要获得详细的关于info的信息打
help info.
`set’
     这个命令用来为你的程序设置一个运行环境(使用一个表达式)。比如你
可以用set prompt $来把gdb的提示符设为$.

`show’
     与’info’相反,’show’命令用来显示gdb自身的状态。你使用’set’命令来
可以改变绝大多数由’show’显示的信息。比如使用show radix命令来显示基数。
用不带任何参变量的’set’命令你可以显示所有你可以设置的变量的值。
有三个变量是不可以用’set’命令来设置的。
`show version’
     显示gdb的版本号。如果你发现gdb有bug的话你应该在bug-reports里加
入gdb的版本号。

`show copying’
显示版权信息。

`show warranty’
显示担保信息。
在gdb下运行你的程序
   当你在gdb下运行程序时,你必须先为gdb准备好带有调试信息的可执行文档。
还可以在gdb中为你的程序设置参变量,重定向你程序的输入/输出,设置环境变
量,调试一个已经执行的程序或kill掉一个子进程。
   这里许多内容在早先的例子中都已经用到过,可以参见gdb(二)。
目录:

* 编译::                         为调试编译带调试信息的代码
* 运行::                         运行你的程序
* 参变量::                       为你的程序设置参变量
* 运行环境::                    为你的程序设置运行时环境
* 设置工作目录::                在gdb中设置程序的工作目录。
* 输入/输出::                   设定你程序的输入和输出
* 连接::                         调试一个已经运行的程序
* 结束子进程::                  Kill子进程
* 进程信息::                    附加的进程信息
* 线程::                         调试带多线程的程序
* 多进程::                   调试带多进程的程序
为调试准备带调试信息的代码
   为了高效的调试一个程序,你需要使用编译器来产生附带调试信息的可执行代码
这些调试信息存储在目标文件中;描述了变量数据类型和函数声明,在原文件代码行
和执行代码之间建立联系。
   为产生调试信息,当你使用编译器时指定’-g’选项,就可以为你的程序产生带有
调试信息的可执行代码。
   有些c编译器不支持’-g’选项和’-O’选项,那你就有麻烦了,或者有别的方法产生
带调试信息的可执行代码,要不就没办法了。
   gcc,GNU的c语言编译器支持’-g’和’-O’选项。这样你就可以产生带调试信息的且
优化过的可执行代码.
   当你使用gdb来调试一个使用’-g’,'-O’选项产生的程序时,千万记住编译器为了优
化你的程序重新安排了你的程序。不要为运行次序与你原来设想的不同,最简单的例子
就是当你定义了一个变量但从未使用过它时,gdb中是看不到这个变量的–因为它已经
被优化掉了。
   所以有时你不要使用’-O’选项,如果当你不用优化时产生的程序是正确的,而优化
过后变的不正确了,那么这是编译器的bug你可以向开发者提供bug-reports(包括出错
的例子)。
   早期的GUN C语言编译器允许’-gg’选项,也用来产生调试信息,gdb不再支持这种格
式的调试信息,如果你的编译器支持’-gg’选项,请不要使用它。

运行你的程序
`run’
`r’
     使用’run’命令在gdb下启动你的程序。你必须先指定你程序的名字(用gdb的命令行
参数)或使用’file’命令,来指定文件名。如果你在一个支持多进程的环境下运行你的程
序’run’命令创建一个子进程然后加载你的程序。如果环境不支持进程,则gdb直接调到
程序的第一条命令。
   一些父进程设置的参量可以决定程序的运行。gdb提供了指定参量的途径,但你必须
在程序执行前设置好他们。你也可以在运行过程中改变它们,但每次改变只有在下一次
运行中才会体现出来。这些参量可以分为四类:
—参数
     你可以在使用’run’命令时设置,如果shell支持的话,你还可以使用通配符,或
变量代换。在UNIX系统中你可以使用’shell环境变量’来控制shell。
—环境:
     你的程序一般直接从gdb那里继承环境变量。但是你可以使用’set environment’
命令来设置专门的环境变量。
—工作目录
     你的程序还同时从gdb那里继承了工作目录,你可以使用’cd’命令在gdb中改变工作
目录。
—标准输入/输出
     你的程序一般使用与gdb所用的相似的设备来输入/输出。不过你可以为你的程序的
输入/输出进行重定向。使用’run’或’tty’命令来设置于gdb所用不同的设备。
*注意:当你使用输入/输出重定向时,你将不能使用无名管道来把你所调试的程序的输出
传给另一个程序。这样gdb会认为调试程序出错。
   当你发出’run’命令后,你的程序就开始运行。
   如果你的符号文件的时间与gdb上一次读入的不同,gdb会废弃原来的符号表并重新读
入。当前的断点不变。

程序环境
   “环境”包括了一系列的环境变量和它们的值。环境变量一般记录了一些常用的信息,
比如你的用户名,主目录,你的终端型号和你的运行程序的搜索路径。一般你可以在shell
下设置环境变量,然后这些变量被所有你所运行的程序所共享。在调试中,可以设置恰当
的环境变量而不用退出gdb.

`path DIRECTORY’
     在’PATH’环境变量前加入新的内容(‘PATH’提供了搜索执行文件的路径)。对于gdb和
你的程序来说你也许要设置一些专门的路径。使用’:'或空格来分隔。如果DIRECTORY已经
在路径中了,这个操作将会把它移到前面。
     你可以使用串’$cmd’来代表当前路径,如果你用’.'的话,它代表你使用’path’命令
时的路径,gdb将在把DIRECTORY加入搜索路径前用’.'代替当前路径

`show paths’
     显示当前路径变量的设置情况。

`show environment [VARNAME]‘
     显示某个环境变量的值。如果你不指明变量名,则gdb会显示所有的变量名和它们的
内容。environment可以被缩写成’env’

`set environment VARNAME [=] VALUE’
     设置某个环境变量的值。不过只对你所调试的程序有效。对gdb本身是不起作用的。
值可以是任何串。如果未指定值,则该变量值将被设为NULL.
看一个例子:
          set env USER = foo
     告诉一个linux程序,当它下一次运行是用户名将是’foo’

`unset environment VARNAME’
     删除某环境变量。

   注意:gdb使用’shell’环境变量所指定的shell来运行你的程序。

工作路径
   当你每次用’run’命令来运行你的程序时,你的程序将继承gdb的
当前工作目录。而gdb的工作目录是从它的父进程继承而来的(一般是
shell)。但你可以自己使用’cd’命令指定工作目录。
   gdb的工作目录就是它去寻找某些文件或信息的途径。
`cd DIRECTORY’
     把gdb的工作目录设为DIRECTORY
`pwd’
     打印输出当前目录。
你程序的输入/输出
   缺省时,你的程序的输入/输出和gdb的输入/输出使用同一个终端。
gdb在它自己和你的程序之间切换来和你交互,但这会引起混乱。
`info terminal’
     显示你当前所使用的终端的类型信息。
     你可以把你程序的输入/输出重定向。
例如:
     run > outfile
运行你的程序并把你程序的标准输出写入文件outfile中。
   另一个为你程序指定输入/输出的方法是使用’tty’命令,这个命令
接受一个文件名作为参量把这个文件作为以后使用’run’命令的缺省命
令文件。它还重新为子进程设置控制终端。
例如:
     tty /dev/ttyb
指定以后用’run’命令启动的进程使用终端’/dev/ttyb’作为程序的输入
/输出,而且把这个终端设为你进程的控制终端。
   一个清楚的使用’run’命令的重定向将重新设置’tty’所设置的内容
,但不影响控制终端。   当你使用’tty’命令或在’run’命令中对输入
/输出进行重定向时,只有你当前调试的程序的输入/输出被改变了,
并不会影响到别的程序。
调试一个已经运行的程序:
attach PROCESS-ID’
     这个命令把一个已经运行的进程(在gdb外启动)连接入gdb,以便
调试。PROCESS-ID是进程号。(UNIX中使用’ps’或’jobs -l’来查看进程)
     ‘attach’一般不重复。(当你打了一个以上的回车时)
   当然要使用’attach’命令的话,你的操作系统环境必须支持进程。
另外你还要有向此进程发信号的权力。
   当使用’attach’命令时,你应该先使用’file’命令来指定进程所
联系的程序源代码和符号表。   当gdb接到’attach’命令后第一件
事就是停止进程的运行,你可以使用所有gdb的命令来调试一个“连接”
的进程,就象你用’run’命令在gdb中启动它一样。如果你要进程继续运
行,使用’continue’或’c'命令就行了。
detach’
   当你结束调试后可以使用此命令来断开进程和gdb的连接。(解除gdb
对它的控制)在这个命令执行后进程将继续执行。
   如果你在用’attach’连接一个进程后退出了gdb,或使用’run’命令执
行了另一个进程,这个被’attach’的进程将被kill掉。但缺省时,gdb会
要求你确认你是否要退出或执行一个新的进程。

结束子进程
`kill’
     Kill命令结束你程序在gdb下开的子进程
     这个命令当你想要调试(检查)一个core dump文件时更有用。gdb在调试过程中
会忽略所有的core dump。
   在一些操作系统上,一个程序当你在上面加了断点以后就不能离开gdb独立运行。
你可以用kill命令来解决这个问题。
   ‘kill’命令当你想重新编译和连接你的程序时也很有用。因为有些系统不允许修改
正在执行的可执行程序。这样当你再一次使用’run’命令时gdb会知道你的程序已经被改
变了,那么gdb会重新load新的符号。(而且尽量保持你当前的断点设置。
附加的进程信息
   一些操作系统提供了一个设备目录叫做’/proc’的,供检查进程映象。如果gdb被在这
样的操作系统下运行,你可以使用命令’info proc’来查询进程的信息。(‘info proc’命
令只在支持’procfs’的SVR4系统上有用。
`info proc’
     显示进程的概要信息。
`info proc mappings’
     报告你进程所能访问的地址范围。
`info proc times’
     你进程和子进程的开始时间,用户时间(user CPU time),和系统CPU时间。
`info proc id’
     报告有关进程id的信息。
`info proc status’
     报告你进程的一般状态信息。如果进程停止了。这个报告还包括停止的原因和收到的
信号。
`info proc all’
     显示上面这些命令返回的所有信息。
对多线程程序的调试
   一些操作系统中,一个单独的程序可以有一个以上的线程在运行。线程和进程精确的定有自己的寄存器,运行时堆栈或许还会有私有内存。
   gdb提供了以下供调试多线程的进程的功能:
   * 自动通告新线程。
   * ‘thread THREADNO’,一个用来在线程之间切换的命令。
   * ‘info threads’,一个用来查询现存线程的命令。
   * ‘thread apply [THREADNO] [ALL] ARGS’,一个用来向线程提供命令的命令。
   * 线程有关的断点设置。
   注意:这些特性不是在所有gdb版本都能使用,归根结底要看操作系统是否支持。
   如果你的gdb不支持这些命令,会显示出错信息:
          (gdb) info threads
          (gdb) thread 1
          Thread ID 1 not known.  Use the “info threads” command to
          see the IDs of currently known threads.
   gdb的线程级调试功能允许你观察你程序运行中所有的线程,但无论什么时候
gdb控制,总有一个“当前”线程。调试命令对“当前”进程起作用。
   一旦gdb发现了你程序中的一个新的线程,它会自动显示有关此线程的系统信
息。比如:
     [New process 35 thread 27]
不过格式和操作系统有关。
   为了调试的目的,gdb自己设置线程号。
`info threads’
     显示进程中所有的线程的概要信息。gdb按顺序显示:
       1.线程号(gdb设置)
       2.目标系统的线程标识。
       3.此线程的当前堆栈。
       一前面打’*'的线程表示是当前线程。
     例如:
     (gdb) info threads
       3 process 35 thread 27  0×34e5 in sigpause ()
       2 process 35 thread 23  0×34e5 in sigpause ()
     * 1 process 35 thread 13  main (argc=1, argv=0×7ffffff8)
         at threadtest.c:68

`thread THREADNO’
     把线程号为THREADNO的线程设为当前线程。命令行参数THREADNO是gdb内定的
线程号。你可以用’info threads’命令来查看gdb内设置的线程号。gdb显示该线程
的系统定义的标识号和线程对应的堆栈。比如:

          (gdb) thread 2
          [Switching to process 35 thread 23]
          0×34e5 in sigpause ()
     “Switching后的内容取决于你的操作系统对线程标识的定义。

`thread apply [THREADNO] [ALL]  ARGS’
     此命令让你对一个以上的线程发出相同的命令”ARGS”,[THREADNO]的含义同上。
如果你要向你进程中的所有的线程发出命令使用[ALL]选项。
   无论gdb何时中断了你的程序(因为一个断点或是一个信号),它自动选择信号或
断点发生的线程为当前线程。gdb将用一个格式为’[Switching to SYSTAG]‘的消息
来向你报告。
   *参见:运行和停止多线程程序。
   *参见:设置观察点

调试多进程的程序
   gdb对调试使用’fork’系统调用产生新进程的程序没有很多支持。当一个程序开始
一个新进程时,gdb将继续对父进程进行调试,子进程将不受影响的运行。如果你在子
进程可能会执行到的地方设了断点,那么子进程将收到’SIGTRAP’信号,如果子进程没
有对这个信号进行处理的话那么缺省的处理就是使子进程终止。
   然而,如果你要一定要调试子进程的话,这儿有一个不是很麻烦的折衷的办法。在
子进程被运行起来的开头几句语句前加上一个’sleep’命令。这在调试过程中并不会引
起程序中很大的麻烦(不过你要自己注意例外的情况幺:-))。然后再使用’ps’命令列出
新开的子进程号,最后使用’attach’命令。这样就没有问题了。
  关于这一段,本人觉得实际使用上并不全是这样。我在调试程中就试过,好象不一定
能起作用,要看gdb的版本和你所使用的操作系统了。

停止和继续
   调试器的基本功能就是让你能够在程序运行时在终止之前在某些条件下停止下来,然
后再继续运行,这样的话你就可以检查当你的程序出错时你的程序究竟做了些什么。
   在gdb内部,你的程序会由于各种原因而暂时停止,比如一个信号,一个断点,或是
由于你用了’step’命令。在程序停止的时候你就可以检查和改变变量的值,设置或去掉
断点,然后继续你程序的运行。一般当程序停下来时gdb都会显示一些有关程序状态的信
息。比如象程序停止的原因,堆栈等等。如果你要了解更详细的信息,你可以使用’info
program’命令。另外,在任何时候你输入这条命令,gdb都会显示当前程序运行的状态信
息。

`info program’
     显示有关你程序状态的信息:你的程序是在运行还是停止,是什么进程,为什么停
止。

断点,观察点和异常
   断点的作用是当你程序运行到断点时,无论它在做什么都会被停止下来。对于每个断点
你都可以设置一些更高级的信息以决定断点在什么时候起作用。你可以使用’break’命令
来在你的程序中设置断点,在前面的例子中我们已经提到过一些这个命令的使用方法了。
你可以在行上,函数上,甚至在确切的地址上设置断点。在含有异常处理的语言(比如象
c++)中,你还可以在异常发生的地方设置断点。
   在SunOS 4.x,SVR4和Alpha OSF/1的设置中,你还可以在共享库中设置断点。
   观察点是一种特殊的断点。它们在你程序中某个表达式的值发生变化时起作用。你必
须使用另外一些命令来设置观察点。除了这个特性以外,你可以象对普通断点一样对观察
点进行操作–使用和普通断点操作一样的命令来对观察点使能,使不能,删除。
   你可以安排当你程序被中断时显示的程序变量。
   当你在程序中设置断点或观察点时gdb为每个断点或观察点赋一个数值.在许多对断点
操作的命令中都要使用这个数值。

设置断点
   使用’break’或简写成’b'来设置断点。gdb使用环境变量$bpnum来记录你最新设置的
断点。
   你有不少方法来设置断点。
 

`break FUNCTION’
     此命令用来在某个函数上设置断点。当你使用允许函数重载的语言比如C++时,有可
能同时在几个重载的函数上设置了断点。

`break +OFFSET’
`break -OFFSET’
     在当前程序运行到的前几行或后几行设置断点。OFFSET为行号。

`break LINENUM’
     在行号为LINENUM的行上设置断点。程序在运行到此行之前停止。

`break FILENAME:LINENUM’
     在文件名为FILENAME的原文件的第LINENUM行设置断点。

`break FILENAME:FUNCTION’
     在文件名为FILENAME的原文件的名为FUNCTION的函数上设置断点。
当你的多个文件中可能含有相同的函数名时必须给出文件名。

`break *ADDRESS’
     在地址ADDRESS上设置断点,这个命令允许你在没有调试信息的程
序中设置断点。
`break’
     当’break’命令不包含任何参数时,’break’命令在当前执行到的程
序运行栈中的下一条指令上设置一个断点。除了栈底以外,这个命令使
程序在一旦从当前函数返回时停止。相似的命令是’finish’,但’finish’
并不设置断点。这一点在循环语句中很有用。
     gdb在恢复执行时,至少执行一条指令。

`break … if COND’
     这个命令设置一个条件断点,条件由COND指定;在gdb每次执行到此
断点时COND都被计算当COND的值为非零时,程序在断点处停止。这意味着
COND的值为真时程序停止。…可以为下面所说的一些参量。

`tbreak ARGS’
     设置断点为只有效一次。ARGS的使用同’break’中的参量的使用。

`hbreak ARGS’
     设置一个由硬件支持的断点。ARGS同’break’命令,设置方法也和
‘break’相同。但这种断点需要由硬件支持,所以不是所有的系统上这个
命令都有效。这个命令的主要目的是用于对EPROM/ROM程序的调试。因为
这条命令可以在不改变代码的情况下设置断点。这可以同SPARCLite DSU
一起使用。当程序访问某些变量和代码时,DSU将设置“陷井”。注意:
你只能一次使用一个断点,在新设置断点时,先删除原断点。

`thbreak ARGS’
     设置只有一次作用的硬件支持断点。ARGS用法同’hbreak’命令。这个命令
和’tbreak’命令相似,它所设置的断点只起一次作用,然后就被自动的删除。这
个命令所设置的断点需要有硬件支持。

`rbreak REGEX’
     在所有满足表达式REGEX的函数上设置断点。这个命令在所有相匹配的函数
上设置无条件断点,当这个命令完成时显示所有被设置的断点信息。这个命令设
置的断点和’break’命令设置的没有什么不同。这样你可以象操作一般的断点一
样对这个命令设置的断点进行删除,使能,使不能等操作。当调试C++程序时这
个命令在重载函数上设置断点时非常有用。

`info breakpoints [N]‘
`info break [N]‘
`info watchpoints [N]‘
     显示所有的断点和观察点的设置表,有下列一些列

    *Breakpoint Numbers*—-断点号
    *Type*—-断点类型(断点或是观察点)
    *Disposition*—显示断点的状态。

    *Enabled or Disabled*—使能或不使能。’y'表示使能,’n'表示不使能。

    *Address*—-地址,断点在你程序中的地址(内存地址)
    *What*—地址,断点在你程序中的行号。
     如果断点是条件断点,此命令还显示断点所需要的条件。
     带参数N的’info break’命令只显示由N指定的断点的信息。
     此命令还显示断点的运行信息(被执行过几次),这个功能在使用’ignore’
命令时很有用。你可以’ignore’一个断点许多次。使用这个命令可以查看断点
被执行了多少次。这样可以更快的找到错误。
    gdb允许你在一个地方设置多个断点。但设置相同的断点无疑是弱智的。不过
你可以使用条件断点,这样就非常有用。
   gdb有时会自动在你的程序中加入断点。这主要是gdb自己的需要。比如为了正
确的处理C语言中的’longjmp’。这些内部断点都是负值,以’-1′开始。’info
breakpoints’不会显示它们。
   不过你可以使用命令’maint info breakpoints’来查看这些断点。

`maint info breakpoints’
     使用格式和’info breakpoints’相同,显示所有的断点,无论是你设置的还是
gdb自动设置的。
     以下列的含义:

    `breakpoint’
          断点,普通断点。
    `watchpoint’
          普通观察点。

    `longjmp’
          内部断点,用于处理’longjmp’调用。

    `longjmp resume’
          内部断点,设置在’longjmp’调用的目标上。

    `until’
          ‘until’命令所使用的内部断点。

    `finish’
          ‘finish’命令所使用的内部断点。

设置观察点
   你可以使用观察点来停止一个程序,当某个表达式的值改变时,观察点会将程序
停止。而不需要先指定在某个地方设置一个断点。
   由于观察点的这个特性,使观察点的使用时开销比较大,但在捕捉错误时非常有
用。特别是你不知道你的程序什么地方出了问题时。

`watch EXPR’
     这个命令使用EXPR作为表达式设置一个观察点。GDB将把表达式加入到程序中
并监视程序的运行,当表达式的值被改变时GDB就使程序停止。这个也可以被用在
SPARClite DSU提供的新的自陷工具中。当程序存取某个地址或某条指令时(这个地
址在调试寄存器中指定),DSU将产生自陷。对于数据地址DSU支持’watch’命令,然而
硬件断点寄存器只能存储两个断点地址,而且断点的类型必须相同。就是两个
‘rwatch’型断点,或是两个’awatch’型断点。

`rwatch EXPR’
     设置一个观察点,当EXPR被程序读时,程序被暂停。

`awatch EXPR’
     设置一个观察点,当EXPR被读出然后被写入时程序被暂停。这个命令和’awatch’
命令合用。

`info watchpoints’
     显示所设置的观察点的列表,和’info break’命令相似。
     *注意:*在多线程的程序中,观察点的作用很有限,GDB只能观察在一个线程中
的表达式的值如果你确信表达式只被当前线程所存取,那么使用观察点才有效。GDB
不能注意一个非当前线程对表达式值的改变。

断点和异常
   在一些语言中比如象GNU C++,实现了异常处理。你可以使用GDB来检查异常发生的
原因。而且GDB还可以列出在某个点上异常处理的所有过程。

`catch EXCEPTIONS’
     你可以使用这个命令来在一个被激活的异常处理句柄中设置断点。EXCEPTIONS是
一个你要抓住的异常。
     你一样可以使用’info catch’命令来列出活跃的异常处理句柄。
     现在GDB中对于异常处理由以下情况不能处理。
   * 如果你使用一个交互的函数,当函数运行结束时,GDB将象普通情况一样把控制返
回给你。如果在调用中发生了异常,这个函数将继续运行直到遇到一个断点,一个信号
或是退出运行。
   * 你不能手工产生一个异常( 即异常只能由程序运行中产生 )
   * 你不能手工设置一个异常处理句柄。
   有时’catch’命令不一定是调试异常处理的最好的方法。如果你需要知道异常产生的
确切位置,最好在异常处理句柄被调用以前设置一个断点,这样你可以检查栈的内容。
如果你在一个异常处理句柄上设置断点,那么你就不容易知道异常发生的位置和原因。
   要仅仅只在异常处理句柄被唤醒之前设置断点,你必须了解一些语言的实现细节。
比如在GNU C++中异常被一个叫’__raise_exception’的库函数所调用。这个函数的原
型是:

         /* ADDR is where the exception identifier is stored.
            ID is the exception identifier.  */
         void __raise_exception (void **ADDR, void *ID);
要使GDB在栈展开之前抓住所有的句柄,你可以在函数’__raise_exception’上设置断点。
   对于一个条件断点,由于它取决于ID的值,你可以在你程序中设置断点,当某个特
别的异常被唤醒。当有一系列异常被唤醒时,你可以使用多重条件断点来停止你的程序。

删除断点
   很自然当一个断点或是一个观察点完成了它的使命后,你需要把它从程序中删去。
不然你的程序还会在相同的地方停主,给你造成干扰。使用’clear’命令来从程序中删去
一个断点。
   使用’clear’命令你可以删除指定位置的断点。使用’delete’命令你可以使用断点号
来指定要删去的断点或观察点。
   在删除断点时不需要先运行过它,GDB会忽略你刚才删去的断点。所以你可以继续运行
你的程序而不必管断点。

`clear’
     在当前选择的栈帧上清除下一个所要执行到的断点(指令级)。当你当前选择帧是栈中
最内层时使用这个命令可以很方便的删去刚才程序停止处的断点。

`clear FUNCTION’
`clear FILENAME:FUNCTION’
     删除名为FUNCITON的函数上的断点。

`clear LINENUM’
`clear FILENAME:LINENUM’
     删除以LINENUM为行号上的断点。

`delete [breakpoints] [BNUMS...]‘
     删除参数所指定的断点,如果没有指定参数则删去程序中所有的断点。这个命令可以
缩写成为’d’

使断点暂时不起作用。
   如果你只是想让断点一时失去作用以方便调试的话,你可以先使断点不起作用。
当你以后又想使用时可以用’enable’命令激活它们。
   你使用’enable’命令来激活断点或是观察点,使用’disable’命令来使断点或观察点
不起作用。使用’info break’或’info watch’来查看那些断点是活跃的。
   断点或观察点有四种状态:
   * 使能。当程序运行到断点处时,程序停止。使用’break’命令设置的断点一开始缺省
是使能的。
   *不使能。断点对你程序的运行没有什么影响。
   *使能一次后变为不使能。断点对你的程序运行只有一次影响,然后就自动变成不使能
状态。使用’tbreak’设置的断点一开始缺省是这个状态。
   * 使能一次自动删除。断点在起了一次作用后自动被删除。
   你可以使用以下的命令来使能或使不能一个断点或观察点。

`disable [breakpoints] [BNUMS...]‘
     使由参数指定的断点或观察点变为不使能,如果没有参数的话缺省使所有断点和观察
点变为不使能。当一个断点或观察点被不使能后在被不使能前的状态被记录下来,在断点或
观察点再次被激活时,原来的状态得到继续。比如一个条件断点或一个设置了
‘ignore-counts’的断点在被使不能后记录活跃时断点被执行的次数,在不使能状态下,断
点的执行次数(ignore-counts)不增加,直到断点再次被激活时,再继续计算条件
(ignore-counts)。你可以使用’disable’命令的缩写’dis’

`enable [breakpoints] [BNUMS...]‘
     使能由参数指定的断点或全部断点。

`enable [breakpoints] once BNUMS…’
     功能同上条命令,只是这条命令使断点只使能一次。

`enable [breakpoints] delete BNUMS…’
     功能同上条命令,只是这条命令使被使能的断点起作用一次然后自动被删除。
     除了使用’tbreak’命令所设置的断点以外,断点被设置时都是使能的。

断点条件
   最简单的断点就是当你的程序每次执行到的时候就简单将程序挂起。你也可以为断点
设置“条件”。条件只是你所使用的编程语言的一个布尔表达式,带有条件表达式的断点
在每次执行时判断计算表达式的值,当表达式值为真时才挂起程序。
   这是使用“断言”的一中形式,在这种形式中你只有在断言为真时才挂起程序。如果
在C语言中你要使断言为假时挂起程序则使用:“!表达式”。
   条件表达式对观察点也同样有效,但你并不需要它,因为观察点本身就计算一个表达式但它也许会简单一些。比如只在一个变量名上设置观察点然后设置一个条件来测试新的赋
值。
  断点条件可能有副作用(side effects)会影响程序的运行。这一点有时也是很有用的
比如来激活一个显示程序完成情况的的函数,或使用你自己的打印函数来格式化特殊的
数据结构。当在同一位置没有另一个断点设置时,结果是可预见的。(在gdb中如果在同一
个地方使用了一个断点和一个条件断点则普通断点可能先被激活。)在条件断点的应用上
有很多技巧。
   断点条件可以在设置断点的同时被设置。使用’if’命令作为’break’命令的参数。断点
条件也可以在任何时候使用’condition’命令来设置。’watch’命令不能以’if’作为参数

所以使用‘condition’命令是在观察点上设置条件的唯一方法。

`condition BNUM EXPRESSION’
     把’EXPRESSIN’作为断点条件。断点用’BNUM’来指定。在你为BNUM号断点设置了条件
后,只有在条件为真时程序才被暂停。当你使用’condition’命令GDB马上同步的检查
‘EXPRESSION’的值判断表达式中的符号在断点处是否有效,但GDB并不真正计算表达式
的值。

`condition BNUM’
     删除在’BNUM’号断点处的条件。使之成为一个普通断点。
   一个条件断点的特殊例子是时一个程序在执行了某句语句若干次后停止。由于这
个功能非常常用,你可以使用一个命令来直接设置它那就是’ignore count’。每个
断点都有’ignore count’,缺省是零。如果’ignore count’是正的那么你的程序在
运行过断点处’count’次后被暂停。

`ignore BNUM COUNT’
     设置第BNUM号断点的’ignore count’为’COUNT’。
     如果要让断点在下次执行到时就暂停程序,那么把’COUNT’设为0.
     当你使用’continue’命令来继续你程序的执行时,你可以直接把’ignore count’
作为’continue’的参数使用。你只要直接在’continue’命令后直接跟要”ignore”的
次数就行。
     如果一个断点同时有一个ignore count和一个条件时,条件不被检查。只有当
‘ignore count’为零时GDB才开始检查条件的真假。
     另外你可以用’condition’命令来获得与用‘ignore count’同样效果的断点。用法
是用类似于’$foo–<=0′的参量作为’condition’命令的参数(使用一个不停减量的变量
作为条件表达式的成员)。

断点命令列表
   你可以为任一个断点或观察点指定一系列命令,当你程序执行到断点时,GDB自动执行
这些命令。例如:你可以打印一些表达式的值,或使能其他的断点。

`commands [BNUM]‘
`… COMMAND-LIST …’
`end’
     为断点号为BNUM的断点设置一个命令列表。这些命令在’…COMMAND-LIST…’中列
出使用’end’命令来表示列表的结束。
    要删除断点上设置的命令序列,你只需在’command’命令后直接跟’end’命令就可以
了。
    当不指定BNUM时,GDB缺省为最近遇到的断点或是观察点设置命令列表。
    使用回车来表示重复使用命令的特性在’…command list…’中不能使用。
    你可以使用命令列表中的命令来再次使你的程序进入运行状态。简单的在命令列表
中使用’continue’命令,或’step’命令。
    在使程序恢复执行的命令后的命令都被忽略。这是因为一旦你的程序重新运行就可
能遇到新的命令列表,那么就应该执行新的命令。防止了二义。
    如果你在命令列表中使用了’silent’命令,那么你程序在断点处停止的信息将不被
显示。这对于用一个断点然后显示一些信息,接着再继续执行很有用。但’silent’命令
只有在命令列表的开头有效。
    命令’echo’,'output’和’printf’允许你精确的控制显示信息,这些命令在”silent”
断点中很有用。
   例如:这个例子演示了使用断点命令列表来打印’x'的值.

     break foo if x>0
     commands
     silent
     printf “x is %d\n”,x
     cont
     end
   断点命令列表的一个应用是在遇到一个buf之后改正数据然后继续调试的过程。
使用命令来修改含有错误值的变量,然后使用’continue’命令继续程序的运行。
  使用’silent’命令屏蔽输出:

     break 403
     commands
     silent
     set x = y + 4
     cont
     end

断点菜单
   一些编程语言(比如象C++)允许一个函数名被多次使用(重载),以方便应用的使用。
当一个函数名被重载时,’break FUNCITON’命令向GDB提供的信息不够GDB了解你要设置
断点的确切位置。如果你了解到这个问题,你可以使用’break FUNCITONS(TYPES)’命令
来指定断点的确切位置。否则GDB会提供一个函数的选择的菜单供你选择。使用提示符
‘>’来等待你的输入。开始的两个选择一般是’[0] cancel’和’[1] all’输入1则在所有
同名函数上加入断点。输入0则退出选择。
   下例为企图在重载的函数符号’String::after’上设置断点。
     (gdb) b String::after
     [0] cancel
     [1] all
     [2] file:String.cc; line number:867
     [3] file:String.cc; line number:860
     [4] file:String.cc; line number:875
     [5] file:String.cc; line number:853
     [6] file:String.cc; line number:846
     [7] file:String.cc; line number:735
     > 2 4 6
     Breakpoint 1 at 0xb26c: file String.cc, line 867.
     Breakpoint 2 at 0xb344: file String.cc, line 875.
     Breakpoint 3 at 0xafcc: file String.cc, line 846.
     Multiple breakpoints were set.
     Use the “delete” command to delete unwanted
      breakpoints.
     (gdb)

2004年08月09日

    完了,又要一天到晚守电视机前了,上两界都是这样。不知道回到学校还有时间和机会吗?
相信中国代表团能够取得历史最好成绩。

2004年08月07日

Driver porting: more module changes

This article is part of the LWN Porting Drivers to 2.6 series.

The first article in this series noted a couple of changes that result from the new, kernel-based module loader. In particular, explicit module_init() and module_exit() declarations are now necessary. Quite a few other things have changed as well, however; this article will summarize the most important of those changes.

Module parameters

The old MODULE_PARM macro, which used to specify parameters which can be passed to the module at load time, is no more. The new parameter declaration scheme add type safety and new functionality, but at the cost of breaking compatibility with older modules.
/* 新的内核的模块参数的声明更加严格,增加了类型检查和新方法,但是兼容性会比较差 */

Modules with parameters should now include <linux/moduleparam.h> explicitly. Parameters are then declared with module_param:

 module_param(name, type, perm); name: 参数名称. type: 参数类型 perm: 权限,为0

Where name is the name of the parameter (and of the variable holding its value), type is its type, and perm is the permissions to be applied to that parameter’s sysfs entry. The type parameter can be one of byte, short, ushort, int, uint, long, ulong, charp, bool or invbool. That type will be verified during compilation, so it is no longer possible to create confusion by declaring module parameters with mismatched types. The plan is for module parameters to appear automatically in sysfs, but that feature had not been implemented as of 2.6.0-test9; for now, the safest alternative is to set perm to zero, which means “no sysfs entry.”

If the name of the parameter as seen outside the module differs from the name of the variable used to hold the parameter’s value, a variant on module param may be used:

 module_param_named(name, value, type, perm);

Where name is the externally-visible name and value is the internal variable.
/* 外部变量名和内部不同时 */

String parameters will normally be declared with the charpchar pointer which will be set to the parameter’s value. If you need to have a string value copied directly into a char array, declare it as: type; the associated variable is a

 module_param_string(name, string, len, perm);/* 利用该函数可以直接将用户那里的string拷贝到name中,而module_param()中指定type: charp只能获得指针 */

Usually, len is best specified as sizeof(string).

Finally, array parameters (supplied at module load time as a comma-separated list) may be declared with:

 module_param_array(name, type, num, perm);

The one parameter not found in module_param() (num) is an output parameter; if a value for name is supplied when the module is loaded, num will be set to the number of values given. This macro uses the declared length of the array to ensure that it is not overrun if too many values are provided.

As an example of how the new module parameter code works, here is a paramaterized version of the “hello world” module shown previously:

 #include <linux/init.h> #include <linux/module.h> #include <linux/moduleparam.h>

 MODULE_LICENSE("Dual BSD/GPL");

 /* * A couple of parameters that can be passed in: how many times we say * hello, and to whom. */ static char *whom = "world"; module_param(whom, charp, 0); static int howmany = 1; module_param(howmany, int, 0);

 static int hello_init(void) { int i; for (i = 0; i < howmany; i++) printk(KERN_ALERT "(%d) Hello, %s\n", i, whom); return 0; }

 static void hello_exit(void) { printk(KERN_ALERT "Goodbye, cruel %s\n", whom); }

 module_init(hello_init); module_exit(hello_exit);

Inserting this module with a command like:

 insmod ./hellop.ko howmany=2 whom=universe

causes the message “hello, universe” to show up twice in the system logfile.

Module aliases

A module alias is an alternative name by which a loadable module can be known. These aliases are typically defined in /etc/modules.conf, but many of them are really a feature of the module itself. In 2.6, module aliases can be embedded with a module’s source. Simply add a line like:

 MODULE_ALIAS("alias-name");

The module use count

In 2.4 and prior kernels, modules maintained their “use count” with macros like MOD_INC_USE_COUNT. The use count, of course, is intended to prevent modules from being unloaded while they are being used. This method was always somewhat error prone, especially when the use count was manipulated inside the module itself. In the 2.6 kernel, reference counting is handled differently./* 这样利用宏,在模块内部由开发者维护
的count是潜在的危险 */

The only safe way to manipulate the count of references to a module is outside of the module’s code. Otherwise, there will always be times when the kernel is executing within the module, but the reference count is zero. So this work has been moved outside of the modules, and life is generally easier for module authors.

Any code which wishes to call into a module (or use some other module resource) must first attempt to increment that module’s reference count:

 int try_module_get(&module);/* 引用计数的管理都交给内核,要使用模块导出的函数,程序必须尝试去增加该模块的引用计数 */

It is also necessary to look at the return value from try_module_get(); a zero return means that the try failed, and the module should not be used. Failure can happen, for example, when the module is in the process of being unloaded.

A reference to a module can be released with module_put().

Again, modules will not normally have to manage their own reference counts. The only exception may be if a module provides a reference to an internal data structure or function that is not accounted for otherwise. In that (rare) case, a module could conceivably call try_module_get() on itself.

As of this writing, modules are considered “live” during initialization, meaning that a try_module_get() will succeed at that time. /* 在模块初始化时,我们总认为它是存在的,所以try_module_get()总能返回成功. */There is still talk of changing things, however, so that modules are not accessible until they have completed their initialization process. That change will help prevent a whole set of race conditions that come about when a module fails initialization, but it also creates difficulties for modules which have to be available early on. For example, block drivers should be available to read partition tables off of disks when those disks are registered, which usually happens when the module is initializing itself. If the policy changes and modules go back off-limits during initialization, a call to a function like make_module_live() may be required for those modules which must be available sooner. (Update 2.6.0-test9: this change has not happened and seems highly unlikely at this point).

Finally, it is not entirely uncommon for driver authors to put in a special ioctl() function which sets the module use count to zero. Sometimes, during module development, errors can leave the module reference count in a state where it will never reach zero, and there was no other way to get the kernel to unload the module. The new module code supports forced unloading of modules which appear to have outstanding references – if the CONFIG_MODULE_FORCE_UNLOAD option has been set. Needless to say, this option should only be used on development systems, and, even then, with great caution.

Exporting symbols

For the most part, the exporting of symbols to the rest of the kernel has not changed in 2.6 – except, of course, for the fact that any user of those symbols should be using try_module_get() first. In older kernels, however, a module which did not arrange things otherwise would implicitly export all of its symbols. In 2.6, things no longer work that way; only symbols which have explicitly been exported are visible to the rest of the kernel.

Chances are that change will cause few problems. When you get a chance, however, you can remove EXPORT_NO_SYMBOLS EXPORT_NO_SYMBOLS is a no-op. lines from your module source. Exporting no symbols is now the default, so

The 2.4 inter_module_ functions have been deprecated as unsafe. The symbol_get() function exists for the cases when normal symbol linking does not work well enough. Its use requires setting up weak references at compile time, and is beyond the scope of this document; there are no users of symbol_get() in the 2.6.0-test9 kernel source.

Kernel version checking

2.4 and prior kernels would include, in each module, a string containing the version of the kernel that the module was compiled against. Normally, modules would not be loaded if the compile version failed to match the running kernel.

In 2.5, things still work mostly that way. The kernel version is loaded into a separate, “link-once” ELF section, however, rather than being a visible variable within the module itself. As a result, multi-file modules no longer need to define __NO_VERSION__ before including <linux/module.h>.

The new “version magic” scheme also records other information, including the compiler version, SMP status, and preempt status; it is thus able to catch more incompatible situations than the old scheme did.

Module symbol versioning (“modversions”) has been completely reworked for the 2.6 kernel. Module authors who use the makefiles shipped with the kernel (and that is about the only way to work now) will find that dealing with modversions has gotten easier than before. The #define hack which tacked checksums onto kernel symbols has gone away in favor of a scheme which stores checksum information in a separate ELF section