[轉]QNX總結-QNX之編寫資源管理器1

如果你認爲本系列文章對你有所幫助,請大家有錢的捧個錢場,點擊此處贊助,贊助額0.1元起步,多少隨意

聲明:本文只用於個人學習交流,若不慎造成侵權,請及時聯繫我,立即予以改正

鋒影

email:[email protected]

 

The Bones of a Resource Manager

這篇文章將從服務器端和客戶端兩側來描述大體的框架和分層,並會給出實例。

1. Under the covers

1.1 Under the client's covers

當一個客戶端調用需要路徑名解析的函數時(比如open()/rename()/stat()/unlink()),它會同時向進程管理器和對應的資源管理器發送消息,進而去獲取文件描述符,並通過這個文件描述符向與路徑名關聯的設備發送消息。

/*
 * In this stage, the client talks 
 * to the process manager and the resource manager.
 */
fd = open("/dev/ser1", O_RDWR);

/*
 * In this stage, the client talks directly to the
 * resource manager.
 */
for (packet = 0; packet < npackets; packet++)
{
    write(fd, packets[packet], PACKET_SIZE);
}
close(fd);

上邊的這個代碼,背後的邏輯是怎麼實現的呢?我們假設這是名爲devc-ser8250資源管理器管理的串口設備,註冊的路徑名爲dev/ser1

Under-the-cover communication between the client, the process manager, and the resource manager

 

  1. 客戶端發送query消息,open()函數會向進程管理器發送消息,請求查找名字,比如/dev/ser1

  2. 進程管理器會返回與路徑名關聯的nd/pid/child/handle。當devc-ser8250資源管理器使用/dev/ser1註冊時,進程管理器會維護一個類似於下邊的條目信息:
    0, 47167, 1, 0, 0, /dev/ser1
    其中條目的各項分別代表:

  • 0, Node descriptor(nd),用於描述在網絡中的節點;
  • 47167, Process ID(pid),資源管理器的進程ID號;
  • 1, Channel ID(chid),資源管理器用於接收消息的通道;
  • 0, Handle的索引號,由資源管理器註冊的Handle;
  • 0, 在名稱註冊期間傳遞的打開類型;
  • /dev/ser1,註冊的路徑名;
    在進程管理器中會維護一個條目表,用於記錄各個資源管理器的信息。在名字匹配的時候會選擇最長匹配。
  1. 客戶端需要向資源管理器發送一個connect消息,首先它需要創建一個連接的通道:
fd = ConnectAttach(nd, pid, chid, 0, 0);

客戶端也是使用這個fd向資源管理器發送連接消息,請求打開/dev/ser1。當資源管理器收到連接消息後,會進行權限檢查。

  1. 資源管理器通常會迴應通過或失敗。

  2. 當客戶端獲取到文件描述符後,可以直接通過它向設備發送消息。示例代碼看起來像是客戶端直接向設備寫入,實際上在調用write()時,會先發送一個_IO_WRITE消息給資源管理器,請求數據寫入,客戶端調用close()時,會發送_IO_CLOSE_DUP消息給資源管理器,完成最後資源的回收清理工作。

1.2 Under the resource manager's covers

資源管理器就是一個服務器,通過send/receive/reply消息協議來接收和回覆消息,僞代碼如下:

initialize the resource manager
register the name with the process manager
DO forever
    receive a message
    SWITCH on the type of message
        CASE _IO_CONNECT:
            call io_open handler
            ENDCASE
        CASE _IO_READ:
            call io_read handler
            ENDCASE
        CASE _IO_WRITE:
            call io_write handler
            ENDCASE
        .   /* etc. handle all other messages */
        .   /* that may occur, performing     */
        .   /* processing as appropriate      */
    ENDSWITCH
ENDDO

事實上,上述代碼中的很多細節在實現一個資源管理器中可能用不到,因爲有一些庫進行了封裝,但是自己還是需要去實現對消息的回覆。

2. Layers in a resource manager

資源管理器由四層組成,從下到上分別爲:

  1. iofunc layer
  2. resmgr layer
  3. dispatch layer
  4. thread pool layer

2.1 iofunc layer

這一層的主要功能是提供POSIX特性,用戶編寫資源管理器時不需要太關心向外界提供POSIX文件系統所涉及的細節。
這一層由一組函數iofunc_*組成,包含了默認處理程序,如果沒有提供自己的處理程序的話,就會使用默認提供的程序。比如,如果沒有提供io_open處理程序的話,就會調用iofunc_open_default()。同時也包含了默認處理程序調用的幫助函數,當自己實現處理程序時,也可以調用這些幫助函數。

2.2 resmgr layer

這一層負責管理資源管理器庫的大部分細節:

  • 檢查傳入消息
  • 調用合適的處理程序來處理消息
    如果不使用這一層的話,則需要用戶自己解析消息,大部分資源管理器都會用到這一層。

     

    The resmgr layer to handle _IO_* message

2.3 dispatch layer

這一層充當多種事件類型的阻塞點,使用這一層,可以處理:

  • `IO* 消息, 使用resmgr層;
  • select(),註冊一個處理函數,當數據包到達時調用,這裏的函數是select_*()函數;
  • Pulses,註冊一個處理函數,當pulses來的時候調用,這裏的函數是pulse_*()函數;
  • 其他消息,可以提供自己創建的一系列消息類型和處理函數,當消息到達時調用對應的處理函數,這裏的函數是message_*()函數;

    use the dispatch layer to handle _IO_* messages, select, pulses, and other messages


    消息進行分發時,會進行查找和匹配,然後會調用到匹配的Handler函數,如果沒有找到匹配的,則返回MsgError

2.4 thread pool layer

這一層允許用戶創建單線程或多線程資源管理器,這意味着可以一個線程讀,一個線程寫。需要爲線程提供阻塞函數,以及阻塞函數返回時需要調用的處理函數。大多數情況下,可以將dispatch layer的任務分配到線程上來,當然,也可以是resmgr layer任務或自己實現的功能。

3. Simple examples of device resource managers

3.1 Single-threaded device resource managers

先來看一份完整的單線程設備資源管理器代碼吧:

#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>

static resmgr_connect_funcs_t    connect_funcs;
static resmgr_io_funcs_t         io_funcs;
static iofunc_attr_t             attr;

main(int argc, char **argv)
{
    /* declare variables we'll be using */
    resmgr_attr_t        resmgr_attr;
    dispatch_t           *dpp;
    dispatch_context_t   *ctp;
    int                  id;

    /* initialize dispatch interface */
    if((dpp = dispatch_create()) == NULL) {
        fprintf(stderr,
                "%s: Unable to allocate dispatch handle.\n",
                argv[0]);
        return EXIT_FAILURE;
    }

    /* initialize resource manager attributes */
    memset(&resmgr_attr, 0, sizeof resmgr_attr);
    resmgr_attr.nparts_max = 1;
    resmgr_attr.msg_max_size = 2048;

    /* initialize functions for handling messages */
    iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs, 
                     _RESMGR_IO_NFUNCS, &io_funcs);

    /* initialize attribute structure used by the device */
    iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0);

    /* attach our device name */
    id = resmgr_attach(
            dpp,            /* dispatch handle        */
            &resmgr_attr,   /* resource manager attrs */
            "/dev/sample",  /* device name            */
            _FTYPE_ANY,     /* open type              */
            0,              /* flags                  */
            &connect_funcs, /* connect routines       */
            &io_funcs,      /* I/O routines           */
            &attr);         /* handle                 */
    if(id == -1) {
        fprintf(stderr, "%s: Unable to attach name.\n", argv[0]);
        return EXIT_FAILURE;
    }

    /* allocate a context structure */
    ctp = dispatch_context_alloc(dpp);

    /* start the resource manager message loop */
    while(1) {
        if((ctp = dispatch_block(ctp)) == NULL) {
            fprintf(stderr, "block error\n");
            return EXIT_FAILURE;
        }
        dispatch_handler(ctp);
    }
}

上述代碼完成以下功能:

  1. 初始化dispatch接口
    通過調用dispatch_create()接口創建dispatch_t結構,這個結構中包含了channel ID,但channel ID實際的創建是在附加其他內容的時候,比如調用resmgr_attach()/message_attach()/pulse_attach()等。

  2. 初始化資源管理器屬性
    當調用resmgr_attach()時會傳入resmgr_attr_t結構,在這個示例中主要配置:

  • nparts_max,可供服務器回覆的IOV的個數;
  • msg_max_size,最大接收緩衝大小;
  1. 初始化消息處理函數
    在這個例子中提供了兩張表,用於指定在特定消息到達時調用哪個函數:
  • 連接函數表;
  • I/O函數表;
    可以調用iofunc_func_init()接口來配置默認操作函數。
  1. 初始化設備使用的屬性結構
    調用iofunc_attr_init()接口來設置,屬性結構中至少應該包括以下信息:
  • 權限和設備類型
  • 組ID和屬主ID
  1. 在名字空間中註冊一個名字
    調用resmgr_attach()來註冊資源管理器的路徑。在資源管理器能接收到其他程序的消息之前,需要通過進程管理器通知其他程序它與路徑名的綁定關係。

  2. 分配context結構體
    調用dispatch_context_alloc()接口來分配,context結構體包含了用於接收消息的緩衝,也包含了可用於回覆消息的IOVs緩衝。

  3. 開始資源管理器消息循環
    當進入循環後,資源管理器便在dispatch_block()中接收消息,並調用dispatch_handler()進行分發處理,選擇連接函數表和I/O函數表中的合適函數進行執行。當執行完畢後,會再進入dispatch_block()來等待接收其他消息。

3.2 Multi-threaded device resource managers

下邊是多線程設備資源管理器示例代碼:

#include <errno.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>

/*
 * Define THREAD_POOL_PARAM_T such that we can avoid a compiler
 * warning when we use the dispatch_*() functions below
 */
#define THREAD_POOL_PARAM_T dispatch_context_t

#include <sys/iofunc.h>
#include <sys/dispatch.h>

static resmgr_connect_funcs_t    connect_funcs;
static resmgr_io_funcs_t         io_funcs;
static iofunc_attr_t             attr;

main(int argc, char **argv)
{
    /* declare variables we'll be using */
    thread_pool_attr_t   pool_attr;
    resmgr_attr_t        resmgr_attr;
    dispatch_t           *dpp;
    thread_pool_t        *tpp;
    dispatch_context_t   *ctp;
    int                  id;

    /* initialize dispatch interface */
    if((dpp = dispatch_create()) == NULL) {
        fprintf(stderr, "%s: Unable to allocate dispatch handle.\n",
                argv[0]);
        return EXIT_FAILURE;
    }

    /* initialize resource manager attributes */
    memset(&resmgr_attr, 0, sizeof resmgr_attr);
    resmgr_attr.nparts_max = 1;
    resmgr_attr.msg_max_size = 2048;

    /* initialize functions for handling messages */
    iofunc_func_init(_RESMGR_CONNECT_NFUNCS, &connect_funcs, 
                     _RESMGR_IO_NFUNCS, &io_funcs);

    /* initialize attribute structure used by the device */
    iofunc_attr_init(&attr, S_IFNAM | 0666, 0, 0);

    /* attach our device name */
    id = resmgr_attach(dpp,            /* dispatch handle        */
                       &resmgr_attr,   /* resource manager attrs */
                       "/dev/sample",  /* device name            */
                       _FTYPE_ANY,     /* open type              */
                       0,              /* flags                  */
                       &connect_funcs, /* connect routines       */
                       &io_funcs,      /* I/O routines           */
                       &attr);         /* handle                 */
    if(id == -1) {
        fprintf(stderr, "%s: Unable to attach name.\n", argv[0]);
        return EXIT_FAILURE;
    }

    /* initialize thread pool attributes */
    memset(&pool_attr, 0, sizeof pool_attr);
    pool_attr.handle = dpp;
    pool_attr.context_alloc = dispatch_context_alloc;
    pool_attr.block_func = dispatch_block; 
    pool_attr.unblock_func = dispatch_unblock;
    pool_attr.handler_func = dispatch_handler;
    pool_attr.context_free = dispatch_context_free;
    pool_attr.lo_water = 2;
    pool_attr.hi_water = 4;
    pool_attr.increment = 1;
    pool_attr.maximum = 50;

    /* allocate a thread pool handle */
    if((tpp = thread_pool_create(&pool_attr, 
                                 POOL_FLAG_EXIT_SELF)) == NULL) {
        fprintf(stderr, "%s: Unable to initialize thread pool.\n",
                argv[0]);
        return EXIT_FAILURE;
    }

    /* start the threads; will not return */
    thread_pool_start(tpp);
}

從代碼中可以看出,大部分代碼與單線程示例一樣。在這個代碼中,線程在阻塞循環中用到了dispatch_*()函數(dispatch layer)。

  1. 初始化線程池的屬性
    pool_attr的各個字段賦值,用於告知線程在阻塞循環時調用哪些函數,以及線程池需要維護多少線程。

  2. 分配一個線程池的handle
    調用thread_pool_create()接口來分配,這個handle用於控制整個線程池。

  3. 開啓線程
    調用thread_pool_start()接口,啓動線程池。每個新創建的線程都會使用在屬性結構中給出的context_alloc()函數來分配THREAD_POOL_PARAM_T定義類型的context結構。

3.3 Using MsgSend() and MsgReply()

可以不使用read()write()接口來與資源管理器交互,可以調用MsgSend()來發送消息,下邊的示例將分兩部分:服務器和客戶端。服務器端需要使能PROCMGR_AID_PATHSPACE,用於確保能調用resmgr_attach()函數。

  • 服務器代碼:
/*
 * ResMgr and Message Server Process
 */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/neutrino.h>
#include <sys/iofunc.h>
#include <sys/dispatch.h>

resmgr_connect_funcs_t  ConnectFuncs;
resmgr_io_funcs_t       IoFuncs;
iofunc_attr_t           IoFuncAttr;


typedef struct
{
    uint16_t msg_no;
    char     msg_data[255];
} server_msg_t;


int message_callback( message_context_t *ctp, int type, unsigned flags, 
                      void *handle )
{
    server_msg_t *msg;
    int num;
    char msg_reply[255];

    /* Cast a pointer to the message data */
    msg = (server_msg_t *)ctp->msg;

    /* Print some useful information about the message */
    printf( "\n\nServer Got Message:\n" );
    printf( "  type: %d\n" , type );
    printf( "  data: %s\n\n", msg->msg_data );

    /* Build the reply message */
    num = type - _IO_MAX;
    snprintf( msg_reply, 254, "Server Got Message Code: _IO_MAX + %d", num );
   
    /* Send a reply to the waiting (blocked) client */ 
    MsgReply( ctp->rcvid, EOK, msg_reply, strlen( msg_reply ) + 1 );

    return 0;
}



int main( int argc, char **argv )
{
    resmgr_attr_t        resmgr_attr;
    message_attr_t       message_attr;
    dispatch_t           *dpp;
    dispatch_context_t   *ctp, *ctp_ret;
    int                  resmgr_id, message_id;

    /* Create the dispatch interface */
    dpp = dispatch_create();
    if( dpp == NULL )
    {
        fprintf( stderr, "dispatch_create() failed: %s\n", 
                 strerror( errno ) );
        return EXIT_FAILURE;
    }

    memset( &resmgr_attr, 0, sizeof( resmgr_attr ) );
    resmgr_attr.nparts_max = 1;
    resmgr_attr.msg_max_size = 2048;

    /* Setup the default I/O functions to handle open/read/write/... */
    iofunc_func_init( _RESMGR_CONNECT_NFUNCS, &ConnectFuncs,
                      _RESMGR_IO_NFUNCS, &IoFuncs );

    /* Setup the attribute for the entry in the filesystem */
    iofunc_attr_init( &IoFuncAttr, S_IFNAM | 0666, 0, 0 );

    resmgr_id = resmgr_attach( dpp, &resmgr_attr, "serv", _FTYPE_ANY, 
                               0, &ConnectFuncs, &IoFuncs, &IoFuncAttr );
    if( resmgr_id == -1 )
    {
        fprintf( stderr, "resmgr_attach() failed: %s\n", strerror( errno ) );
        return EXIT_FAILURE;
    }

    /* Setup our message callback */
    memset( &message_attr, 0, sizeof( message_attr ) );
    message_attr.nparts_max = 1;
    message_attr.msg_max_size = 4096;

    /* Attach a callback (handler) for two message types */
    message_id = message_attach( dpp, &message_attr, _IO_MAX + 1,
                                 _IO_MAX + 2, message_callback, NULL );
    if( message_id == -1 )
    {
        fprintf( stderr, "message_attach() failed: %s\n", strerror( errno ) );
        return EXIT_FAILURE;
    }

    /* Setup a context for the dispatch layer to use */
    ctp = dispatch_context_alloc( dpp );
    if( ctp == NULL )
    {
        fprintf( stderr, "dispatch_context_alloc() failed: %s\n", 
                 strerror( errno ) );
        return EXIT_FAILURE;
    }


    /* The "Data Pump" - get and process messages */
    while( 1 )
    {
        ctp_ret = dispatch_block( ctp );
        if( ctp_ret )
        {
            dispatch_handler( ctp );
        }
        else
        {
            fprintf( stderr, "dispatch_block() failed: %s\n", 
                     strerror( errno ) );
            return EXIT_FAILURE;
        }
    }

    return EXIT_SUCCESS;
}
  1. 首先是調用dispatch_create()接口來創建dpp,通過這個handle將接收到的消息轉發到合適的層(resmgr, message, pulse);
  2. 設置調用resmgr_attach()所需要的變量;
  3. 調用iofunc_func_init()來設置默認處理函數,調用iofunc_attr_init()來設置屬性結構;
  4. 調用resmgr_attach(),注意這時候註冊的路徑不是絕對路徑,因此默認在它的執行路徑下。.
  5. 告訴dispatch layer,除了由resmgr layer處理的標準I/O和連接消息之外,還需要處理自己的消息。
  6. 調用message_attach()接口來註冊自己的消息處理函數。
  7. 當調用dispatch_block()接收消息後,調用dispatch_handler()來處理,實際上會在dispatch_handler()中調用message_callback()函數。當消息類型爲_IO_MAX + 1_IO_MAX + 2時,就會回調函數。
  • 客戶端代碼:
/* 
 * Message Client Process 
 */ 

#include <stdio.h> 
#include <unistd.h> 
#include <stdlib.h> 
#include <fcntl.h> 
#include <errno.h> 
#include <string.h> 
#include <sys/neutrino.h> 
#include <sys/iofunc.h> 
#include <sys/dispatch.h> 

typedef struct 
{ 
    uint16_t msg_no; 
    char msg_data[255]; 
} client_msg_t; 

int main( int argc, char **argv ) 
{ 
    int fd; 
    int c; 
    client_msg_t msg; 
    int ret; 
    int num; 
    char msg_reply[255]; 
    
    num = 1; 
    
    /* Process any command line arguments */ 
    while( ( c = getopt( argc, argv, "n:" ) ) != -1 ) 
    { 
        if( c == 'n' ) 
        { 
            num = strtol( optarg, 0, 0 ); 
        } 
    } 
    /* Open a connection to the server (fd == coid) */ 
    fd = open( "serv", O_RDWR ); 
    if( fd == -1 ) 
    { 
        fprintf( stderr, "Unable to open server connection: %s\n", 
            strerror( errno ) ); 
        return EXIT_FAILURE; 
    } 
    
    /* Clear the memory for the msg and the reply */ 
    memset( &msg, 0, sizeof( msg ) ); 
    memset( &msg_reply, 0, sizeof( msg_reply ) ); 
    
    /* Set up the message data to send to the server */ 
    msg.msg_no = _IO_MAX + num; 
    snprintf( msg.msg_data, 254, "client %d requesting reply.", getpid() ); 
    
    printf( "client: msg_no: _IO_MAX + %d\n", num ); 
    fflush( stdout ); 
    
    /* Send the data to the server and get a reply */ 
    ret = MsgSend( fd, &msg, sizeof( msg ), msg_reply, 255 ); 
    if( ret == -1 ) 
    { 
        fprintf( stderr, "Unable to MsgSend() to server: %s\n", strerror( errno ) ); 
        return EXIT_FAILURE; 
    } 
    
    /* Print out the reply data */ 
    printf( "client: server replied: %s\n", msg_reply ); 
    
    close( fd ); 
    
    return EXIT_SUCCESS; 
} 

客戶端通過open()接口獲取connection id,調用MsgSend()接口去往服務器上發送消息,並且等待回覆。

上述服務器和客戶端的例子非常簡單,但覆蓋了大部分的內容,基於這個基礎的框架,還可以做其他的事情:

  • 基於不同的消息類型,註冊不同的消息回調函數;
  • 除了消息之外,使用pulse_attach()來接收pulse
  • 重寫默認的I/O消息處理函數以便客戶端也能使用read()write()來與服務器交互;
  • 使用線程池來編寫多線程服務器;


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章