主頁 > 作業系統 > onps堆疊移植說明(3)——添加網卡

onps堆疊移植說明(3)——添加網卡

2022-11-08 06:29:52 作業系統

4. 添加網卡

       移植的最后一步就是撰寫網卡驅動然后將網卡添加到協議堆疊,網卡驅動其本質上完成的是資料鏈路層的作業,在整個通訊鏈路上處于通訊樞紐位置,通訊報文的發送和接收均由其實際完成,針對網卡部分的移植作業共三步:

1)撰寫網卡驅動;

2)注冊網卡到協議堆疊;

3)對接網卡資料收發介面;

協議堆疊目前支持兩種網卡型別:ethernet和ppp,兩種網卡的移植作業雖然步驟一樣,但具體移植細節還是有很大區別的,需要分開單獨進行,

4.1 ethernet網卡

       從移植的角度看,ethernet網卡驅動要提供三個介面函式并完成與協議堆疊的對接:

1)網卡初始化函式,完成網卡初始及啟動作業,并將其添加到協議堆疊;

2)網卡發送函式,發送上層協議傳遞的通訊報文到對端;

3)網卡接收函式,接收到達的通訊報文并傳遞給上層協議;

對于網卡初始化函式,其要做的作業用一句話總結就是:參照網卡資料手冊對其進行配置,然后將其注冊到協議堆疊:

#define DHCP_REQ_ADDR_EN 1 //* dhcp請求ip地址使能宏
static PST_NETIF l_pstNetifEth = NULL; //* 協議堆疊回傳的netif結構
int ethernet_init(void)
{
    /* 進行初始配置,比如引腳配置、使能時鐘、相關作業引數配置等作業 */ 
    //* 在這里添加能夠完成上述作業的相關代碼,請參照目標網卡的技術手冊撰寫
    ……
    ……
    /* 到這里網卡配置作業完成,但還未啟動 */
    
    //* 添加網卡到協議堆疊,一定要注意啟動以太網卡之前一定要先將其添加到協議堆疊
    EN_ONPSERR enErr; 
    ST_IPV4 stIPv4; 
#if !DHCP_REQ_ADDR_EN
    //* 分配一個靜態地址,請根據自己的具體網路情形設定地址
    stIPv4.unAddr = inet_addr_small("192.168.0.4"); 
    stIPv4.unSubnetMask = inet_addr_small("255.255.255.0"); 
    stIPv4.unGateway = inet_addr_small("192.168.0.1"); 
    stIPv4.unPrimaryDNS = inet_addr_small("1.2.4.8"); 
    stIPv4.unSecondaryDNS = inet_addr_small("8.8.8.8"); 
    stIPv4.unBroadcast = inet_addr_small("192.168.0.255"); 
#else
    //* 地址清零,為dhcp客戶端申請動態地址做好準備
    memset(&stIPv4, 0, sizeof(stIPv4)); 
#endif  
    
    //* 注冊網卡,也就是將網卡添加到協議堆疊
    l_pstNetifEth = ethernet_add(……); 
    if(!l_pstNetifEth)
    {
#if SUPPORT_PRINTF    
        printf("ethernet_add() failed, %s\r\n", onps_error(enErr)); 
#endif    
        return -1; 
    }
    
    //* 啟動網卡,開始作業,在這里添加與目標網卡啟動相關的代碼
    ……
    
#if DHCP_REQ_ADDR_EN  
    //* 啟動一個dhcp客戶端,從dhcp服務器申請一個動態地址
    if(dhcp_req_addr(l_pstNetifEth, &enErr))  
    {
    #if SUPPORT_PRINTF     
        printf("dhcp request ip address successfully.\r\n"); 
    #endif    
    }
    else
    {
    #if SUPPORT_PRINTF     
        printf("dhcp request ip address failed, %s\r\n", onps_error(enErr)); 
    #endif    
    }
#endif
    
    return 0; 
}

上面給出的樣例代碼中,省略的部分是與目標系統相關的網卡初始配置代碼,其余則是與協議堆疊有關的網卡注冊代碼,這部分代碼主要是完成了兩塊作業:一,注冊網卡到協議堆疊;二,指定或申請一個靜態/動態地址,注冊網卡的作業是由協議堆疊提供的ethernet_add()函式完成的,其詳細說明如下:

//* 注冊ethernet網卡到協議堆疊,只有如此協議堆疊才能正常使用該網卡進行資料通訊,
//*           pszIfName:網卡名稱
//*          ubaMacAddr:網卡mac地址
//*             pstIPv4:指向ST_IPV4結構體的指標(include/netif/netif.h),這個結構體保存用戶指定的ip地址、網關、dns、子網掩碼等配置資訊
//*
//*        pfunEmacSend:函式指標,指向發送函式,函式原型為INT(* PFUN_EMAC_SEND)(SHORT sBufListHead, UCHAR *pubErr),這個指標指向的其實
//*                      就是網卡發送函式
//*
//* pfunStartTHEmacRecv:函式指標,協議堆疊使用該函式啟動網卡接收執行緒,該執行緒為協議堆疊內部作業執行緒,用戶移植時只需提供啟動該執行緒的介面函式即可
//* 
//*           ppstNetif:二維指標,協議堆疊成功注冊網卡后ethernet_add()函式會回傳一個PST_NETIF指標給呼叫者,這個引數指向這個指標,其最侄訓被
//*                      協議堆疊通過pvParam引數傳遞給pfunStartTHEmacRecv指向的函式
//* 
//*              penErr:如果注冊失敗,ethernet_add()函式會回傳一個錯誤碼,這個引數用于接收這個錯誤碼
//*
//* 注冊成功,回傳一個PST_NETIF型別的指標,后續的報文收發均用到這個指標;注冊失敗則回傳NULL,具體錯誤資訊參見penErr引數攜帶的錯誤碼,
PST_NETIF ethernet_add(const CHAR *pszIfName, const UCHAR ubaMacAddr[ETH_MAC_ADDR_LEN], PST_IPV4 pstIPv4, PFUN_EMAC_SEND pfunEmacSend, 
                       void (*pfunStartTHEmacRecv)(void *pvParam), PST_NETIF *ppstNetif, EN_ONPSERR *penErr);

ethernet_add()函式提供的引數看起來較為復雜,但其實就完成了一件事情:告訴協議堆疊這個新增加的網卡的相關身份資訊及功能介面,包括名稱、地址、資料讀寫介面等,這個函式有兩個地方需要特別說明:一個是樣例代碼中該函式的回傳值保存在了一個靜態存盤時期的變數l_pstNetifEth中;另一個是入口引數pfunStartTHEmacRecv,前一個用于接收注冊成功后回傳的PST_NETIF指標;后一個則是需要提供一個執行緒啟動函式,啟動協議堆疊內部的以太網接收執行緒thread_ethernet_ii_recv(),該執行緒在協議堆疊原始碼ethernet.c檔案中實作,PST_NETIF指標非常重要,它是網卡能夠正常作業的關鍵,報文收發均用到這個指標,它的生命周期應該與協議堆疊的生命周期相同,因此這個指標變數在上面的樣例代碼中被定義成一個靜態存盤時期的變數,并確保網卡的接收、發送函式均能訪問,pfunStartTHEmacRecv引數指向的函式要實作的功能與前面我們撰寫的os適配層函式os_thread_onpstack_start()相同,其就是呼叫os提供的執行緒啟動函式啟動thread_ethernet_ii_recv()執行緒,比如rt-thread下:

#define THETHIIRECV_PRIO      21      //* ethernet網卡接收執行緒(任務)優先級
#define THETHIIRECV_STK_SIZE  384 * 4 //* 接收執行緒堆疊大小,這個堆疊要相對大一些,太小會報錯
#define THETHIIRECV_TIMESLICE 10      //* 單次任務調度執行緒能夠作業的最大時間片
static void start_thread_ethernet_ii_recv(void *pvParam)
{
  rt_thread_t tid = rt_thread_create("EthRcv", thread_ethernet_ii_recv, pvParam, THETHIIRECV_STK_SIZE, THETHIIRECV_PRIO, THETHIIRECV_TIMESLICE);  
  if(tid != RT_NULL)
    rt_thread_startup(tid);
}

其余os與之類似,我們啟動的這個以太網接收執行緒完成實際的以太網層的報文接收及處理作業,其輪詢等待網卡接收中斷函式發送的報文到達信號,收到信號則立即讀取并處理到達的報文,我們在后面講述網卡接收函式的移植細節時還會提到這個接收執行緒,

       對于網卡發送函式,有一點需要注意的是——其原型必須符合協議堆疊的要求,因為我們在進行網卡注冊時還要向協議堆疊注冊發送函式的入口地址,前面在介紹ethernet_add()注冊函式時我們已經給出了發送函式的原型定義,也就是pfunEmacSend引數指向的函式原型,協議堆疊的目標系統是資源受限的單片機系統,為了最大限度節省記憶體,協議堆疊采用了寫時零復制(zero copy)技術,網卡發送函式需要結合協議堆疊的buf list機制撰寫實作代碼,其偽代碼實作如下:

int ethernet_send(SHORT sBufListHead, UCHAR *pubErr)
{
    SHORT sNextNode = sBufListHead;
    UCHAR *pubData; 
    USHORT usDataLen;
    
    //* 呼叫buf_list_get_len()函式計算當前要發送的ethernet報文長度,其由協議堆疊提供
    UINT unEthPacketLen = buf_list_get_len(sBufListHead);
    
    //* 逐個取出buf list節點發送出去
__lblGetNextNode:
    pubData = https://www.cnblogs.com/neo-T/archive/2022/11/07/(UCHAR *)buf_list_get_next_node(&sNextNode, &usDataLen); //* 獲取下一個節點,buf_list_get_next_node()函式由協議堆疊提供
    if (NULL == pubData) //* 回傳空意味著已經到達鏈表尾部,沒有要發送的資料了,直接回傳就可以了
        return (int)unEthPacketLen; 
    
    //* 啟動發送,將取出的資料發送出去,其中pubData指向要發送的資料,usDataLen為要發送的資料長度,這兩個值已經通過buf_list_get_next_node()函式得到
    //* 在這里添加與具體目標網卡相關的資料發送代碼
    …… 
    
    //* 取下一個資料節點
    goto __lblGetNextNode; 
}

關于buf list,其實作機制其實很簡單,以udp通訊為例,用戶要發送資料到對端,會直接呼叫udp發送函式,將資料傳遞給udp層,udp層收到用戶資料后,為了節省記憶體,避免復制,協議堆疊直接將用戶資料掛接到buf list鏈表上成為鏈表的資料節點,接著,udp層會再申請一個節點把udp報文頭掛接到資料節點的前面,組成一個擁有兩個節點的完整udp報文鏈表——鏈表第一個節點掛載udp報文頭,第二個節點掛載用戶要發送的資料,至此,udp層的報文封裝作業完成,資料繼續向ip層傳遞,ip層會繼續申請一個節點把ip報文頭掛接到udp報文頭節點的前面,組成一個擁有三個節點的完整ip 報文鏈表,ip報文在ip層經過路由選擇后被送達資料鏈路層,也就是ethernet層,在這一層,協議堆疊再將ethernet ii報文頭掛接到ip報文頭節點的前面,至此,整個報文的封裝完成,協議堆疊此時會根據網卡注冊資訊呼叫對應網卡的ethernet_send()函式將報文發送出去,ethernet_send()函式的核心處理邏輯就是按照上述機制再依序取出鏈表節點攜帶的各層報文資料,然后順序發送出去,

       網卡移植拼圖的最后一塊就是完成網卡接收函式,把網卡收到的資料推送給協議堆疊,其偽代碼實作如下:

//* 網卡接收函式,可以是接收中斷服務子函式,也可以是普通函式,普通函式必須確保能夠在資料到達的第一時間就能讀取并推送給協議堆疊
void ethernet_recv(void)
{
    EN_ONPSERR enErr;
    unsigned int unPacketLen;
    unsigned char *pubRcvedPacket; 
    
    //* 在這里添加與具體網卡相關的代碼,等待接收報文到達,如果資料到達將報文長度賦值unPacketLen變數,將報文首地址賦值給pubRcvedPacket
    ……
    ……
    //* 讀取到達報文并將其推送給協議堆疊進行處理,首先利用協議堆疊mmu模塊動態申請一塊記憶體用于保存到達的報文
    unsigned char *pubPacket = (UCHAR *)buddy_alloc(sizeof(ST_SLINKEDLIST_NODE) + unPacketLen, &enErr);    
    //* 申請成功,根據協議堆疊要求,剛才申請的記憶體按照PST_SLINKEDLIST_NODE鏈表節點方式組織并保存剛剛收到的報文
    PST_SLINKEDLIST_NODE pstNode = (PST_SLINKEDLIST_NODE)pubPacket;
    pstNode->uniData.unVal = unPacketLen; 
    memcpy(pubPacket + sizeof(ST_SLINKEDLIST_NODE), (UCHAR *)pubRcvedPacket, unPacketLen);
    
    //* 將上面組織好的報文節點放入接收鏈表,這個接收鏈表由協議堆疊管理,ethernet_put_packet()函式由協議堆疊提供
    //* thread_ethernet_ii_recv()接收執行緒負責等待ethernet_put_packet()函式投遞的信號并讀取這個鏈表
    //* 引數l_pstNetifEth為前面注冊網卡時由協議堆疊回傳的PST_NETIF指標值
    ethernet_put_packet(l_pstNetifEth, pstNode); 
}

其中buddy_alloc()函式在功能上與c語言的標準庫函式malloc()完全相同,都是動態分配一塊指定大小的記憶體給呼叫者使用,使用完畢后再由用戶通過buddy_free()函式釋放,這兩個函式由協議堆疊的記憶體管理(mmu)模塊提供,ethernet_put_packet()函式需要重點解釋一下,這個函式由協議堆疊提供,它完成的作業非常重要,它在網卡接收函式與協議堆疊以太網接收執行緒thread_ethernet_ii_recv()之間搭建了一個資料流通的“橋”,接收函式收到報文后按照協議堆疊要求,將報文封裝成ST_SLINKEDLIST_NODE型別的鏈表節點,然后傳遞給ethernet_put_packet()函式,該函式將立即把傳遞過來的節點掛載到由協議堆疊管理的以太網接收鏈表的尾部,然后投遞一個“有新報文到達”的信號量,前文提到的以太網接收執行緒thread_ethernet_ii_recv()會輪詢等待這個信號量,一旦信號到達,接收執行緒將立即讀取鏈表并取出報文交給協議堆疊處理,

       至此,ethernet網卡相關的移植作業完成,

4.2 ppp撥號網卡

       在Linux系統,2g/4g/5g模塊作為一個通訊終端,驅動層會把它當作一個tty設備來看待,Linux下ppp堆疊也是圍繞著操作一個標準的tty設備來實作底層通訊邏輯的,至于如何操作這個ppp撥號終端進行實際的資料收發tty層并不關心,所以,協議堆疊完全借鑒了這個成功的設計思想,在底層驅動與撥號終端之間增加了一個tty層,將具體的設備操作與上層的業務邏輯進行了剝離,ppp撥號網卡的的移植作業其實就是完成tty層到底層驅動的封裝作業,協議堆疊利用句柄來唯一的標識一個tty設備,在os_datatype.h檔案中定義了這個句柄型別:

#if SUPPORT_PPP
   typedef INT HTTY;       //* tty終端句柄
   #define INVALID_HTTY -1 //* 無效的tty終端句柄
#endif

這個句柄型別非常重要,所有與tty操作相關的函式都要用到這個句柄型別,tty層要完成的驅動封裝作業涉及的函式原型定義依然是在os_adapter.h檔案中:

#if SUPPORT_PPP
//* 打開 tty 設備,回傳 tty 設備句柄,引數 pszTTYName 指定要打開的 tty 設備的名稱
OS_ADAPTER_EXT HTTY os_open_tty(const CHAR *pszTTYName);

//* 關閉 tty 設備,引數 hTTY 為要關閉的 tty 設備的句柄
OS_ADAPTER_EXT void os_close_tty(HTTY hTTY);

//* 向 hTTY 指定的 tty 設備發送資料,回傳實際發送的資料長度
//*     hTTY:設備句柄
//*  pubData:指標,指向要發送的資料的指標
//* nDataLen:要發送的資料長度
//* 回傳值為實際發送的位元組數
OS_ADAPTER_EXT INT os_tty_send(HTTY hTTY, UCHAR *pubData, INT nDataLen);

//* 從引數 hTTY 指定的 tty 設備等待接收資料,阻塞型
//*       hTTY:設備句柄
//*  pubRcvBuf:指標,指向資料接識訓沖區的指標,用于保存收到的資料
//* nRcvBufLen:接識訓沖區的長度
//*  nWaitSecs:等待的時長,單位:秒,0 一直等待;直至收到資料或報錯,大于 0,等待指定秒數;小于 0,不支持
//* 回傳值為實際收到的資料長度,單位:位元組
OS_ADAPTER_EXT INT os_tty_recv(HTTY hTTY, UCHAR *pubRcvBuf, INT nRcvBufLen, INT nWaitSecs); 

//* 復位 tty 設備,這個函式名稱體現了2g/4g/5g模塊作為tty設備的特殊性,其功能從本質上看就是一個 modem,modem 設備出現通訊
//* 故障時,最好的修復故障的方式就是直接復位,復位可以修復絕大部分的因軟體問題產生的故障
OS_ADAPTER_EXT void os_modem_reset(HTTY hTTY);
#endif

依據上述函式的原型定義及功能說明,我們在os_adapter.c檔案編碼實作它們,相關偽代碼實作如下:

#if SUPPORT_PPP
HTTY os_open_tty(const CHAR *pszTTYName)
{
    //* 如果你的系統存在多個ppp撥號終端,那么pszTTYName引數用于區分打開哪個串口
    //* 在這里添加串口打開代碼將連接撥號終端的串口打開
    ……
    ……

    //* 如果目標系統只有一個撥號終端,那么這里回傳的tty句柄x值為0,如果目標系統存在多個模塊,這里需要你根據引數
    //* pszTTYName指定的名稱來區分是哪個設備,并據此回傳不同的tty句柄,句柄值x應從0開始自增,步長為1
    return x;
}

void os_close_tty(HTTY hTTY)
{
    //* 在這里添加串口關閉代碼,關閉哪個串口的依據是tty設備句柄hTTY
    ……
}

INT os_tty_send(HTTY hTTY, UCHAR *pubData, INT nDataLen)
{
    //* 在這里添加資料發送代碼,其實就是呼叫對應的串口驅動函式發送資料到撥號終端,如果存在多個tty設備,請依據引數hTTY來
    //* 確定需要呼叫哪個串口驅動函式發送資料,回傳值為實際發送的位元組數
    ……
}

INT os_tty_recv(HTTY hTTY, UCHAR *pubRcvBuf, INT nRcvBufLen, INT nWaitSecs)
{
    //* 同上,在這里添加資料讀取代碼,其實就是呼叫對應的串口驅動函式從撥號終端讀取資料,如果存在多個tty設備,請依據引數hTTY來
    //* 確定需要呼叫哪個串口驅動函式讀取資料,回傳值為實際讀取到的位元組數
    ……
}

void os_modem_reset(HTTY hTTY)
{
    //* 在這里添加撥號終端的復位代碼,如果你的目標板不支持軟體復位模塊,可以省略這一步
    //* 復位模塊的目的是解決絕大部分的因軟體問題產生的故障
    ……
}
#endif

參照上述偽代碼,依據目標系統具體情況撰寫相應功能代碼即可,注意,上述代碼能夠正常作業的關鍵是目標系統的串口驅動必須能夠正常作業且健壯、可靠,因為tty層封裝的其實就是操作ppp撥號終端的串口驅動代碼,tty只是做了一層簡單封裝罷了,os_adapter.c檔案中關于ppp部分還有如下幾項定義需要根據你的實際目標環境進行配置:

#if SUPPORT_PPP
//* 連接ppp撥號終端的串口名稱,有幾個模塊,就指定幾個,其存盤的單元索引應等于os_open_tty()回傳的對應串口的tty句柄值
const CHAR *or_pszaTTY[PPP_NETLINK_NUM] = { …… /* 如"串口1", "串口2"等 */ }; 

//* 指定ppp撥號的apn、用戶和密碼,系統支持幾路ppp,就需要指定幾組撥號資訊
//* ST_DIAL_AUTH_INFO結構體保存這幾個資訊,該結構體的詳細內容參見協議堆疊原始碼ppp/ppp.h檔案
//* 這里設定的apn等撥號認證資訊會替代前面說過的APN_DEFAULT、AUTH_USER_DEFAULT、AUTH_PASSWORD_DEFAULT等預設設定
const ST_DIAL_AUTH_INFO or_staDialAuth[PPP_NETLINK_NUM] = {
  { "4gnet", "card", "any_char" },  //* 注意ppp賬戶和密碼盡量控制在20個位元組以內,太長需要需要修改chap.c
                                    //* 中send_response()函式的szData陣列容量及pap.c中pap_send_auth_request()函式的
                                    //* ubaPacket陣列的容量,確保其能夠封裝一個完整的回應報文
  /* 系統存在幾路ppp鏈路,就在這里添加幾路撥號認證資訊 */
}; 

//* ppp鏈路協商的初始協商配置資訊,協商成功后這里保存最終的協商結果,ST_PPPNEGORESULT結構體的詳細說明參見下文
ST_PPPNEGORESULT o_staNegoResult[PPP_NETLINK_NUM] = {
  {
    { 0, PPP_MRU, ACCM_INIT,{ PPP_CHAP, 0x05 /* CHAP協議,0-4未使用,0x05代表采用MD5演算法 */ }, TRUE, TRUE, FALSE, FALSE },
    { IP_ADDR_INIT, DNS_ADDR_INIT, DNS_ADDR_INIT, IP_ADDR_INIT, MASK_INIT }, 0
  }, 

  /* 系統存在幾路ppp鏈路,就在這里添加幾路的協商初始值,如果不確定,可以將上面預定義的初始值直接復制過來即可 */
}; 
#endif

上面給出的代碼做了幾件事情:
1)指定tty設備連接的串口名稱;
2)指定撥號認證資訊:apn、用戶和密碼;
3)指定ppp鏈路協商初始值;

總之,你的目標系統連接了幾個撥號終端,這幾件事情就要針對特定的終端分別做一遍,單獨指定,這里需要重點說明的是ppp鏈路協商配置資訊,這些資訊由ST_PPPNEGORESULT結構體保存(參見negotiation_storage.h檔案):

typedef struct _ST_PPPNEGORESULT_ {
    struct {
        UINT unMagicNum; //* 幻數(魔術字)
        USHORT usMRU;    //* 最大接收單元,預設值由PPP_MRU宏指定,一般為1500位元組
        UINT unACCM;     //* ACCM,異步控制字符映射,指定哪些字符需要轉義,如果不確定,建議采用ACCM_INIT宏指定的預設值
        struct { //* 保存認證資訊的結構體
            USHORT usType;     //* 指定認證型別:chap或pap,預設chap認證
            UCHAR ubaData[16]; //* 認證報文攜帶的資料,不同的協議攜帶的資料型別不同,一般情況下采用協議堆疊的預設值即可
        } stAuth;
        BOOL blIsProtoComp;            //* 是否采用協議域壓縮(本地設定項,代表協議堆疊一側,協商結果不影響該欄位)
        BOOL blIsAddrCtlComp;          //* 是否采用地址及控制域壓縮(本地設定項,代表協議堆疊一側,協商結果不影響該欄位)
        BOOL blIsNegoValOfProtoComp;   //* 協議域是否壓縮的協商結果值(遠端設定項,代表對端是否支持該配置,協商結果影響該欄位)
        BOOL blIsNegoValOfAddrCtlComp; //* 地址及控制域是否壓縮的協商結果值(遠端設定項,同上)
    } stLCP;
    
    //* 存盤ppp鏈路的初始及協商成功后的地址資訊
    struct {
        UINT unAddr;             //* ip地址,初始值由協議堆疊提供的IP_ADDR_INIT宏指定,不要擅自修改
        UINT unPrimaryDNS;       //* 主dns服務器地址,初始值由協議堆疊提供的DNS_ADDR_INIT宏指定,不要擅自修改
        UINT unSecondaryDNS;     //* 次dns服務器地址,初始值由協議堆疊提供的DNS_ADDR_INIT宏指定,不要擅自修改
        UINT unPointToPointAddr; //* 點對點地址,初始值由協議堆疊提供的IP_ADDR_INIT宏指定,不要擅自修改
        UINT unSubnetMask;       //* 子網掩碼
    } stIPCP;
    UCHAR ubIdentifier;   //* 標識域,從0開始自增,唯一的標識一個ppp報文,用于確定應答報文
    UINT unLastRcvedSecs; //* 最近一次收到對端報文時的秒數,其用于ppp鏈路故障探測,無需關心,協議堆疊底層使用
} ST_PPPNEGORESULT, *PST_PPPNEGORESULT;

基本上,要調整的地方幾乎沒有,我們直接采用預設值即可,

       移植作業的最后一步就是把ppp網卡的主處理執行緒thread_ppp_handler()添加到os適配層的作業執行緒串列中,也就是前面講解os適配層移植作業時提到的lr_stcbaPStackThread陣列,這個陣列保存了協議堆疊內部作業執行緒串列,我們先前已經添加了one-shot定時器作業執行緒thread_one_shot_timer_count(),我們再把ppp主處理執行緒添加到這個陣列中即可,偽代碼實作如下:

//* 協議堆疊內部作業執行緒串列
const static STCB_PSTACKTHREAD lr_stcbaPStackThread[] = {
	{ thread_one_shot_timer_count, NULL}, 	
#if SUPPORT_PPP
	//* 在此按照順序建立ppp作業執行緒,入口函式為thread_ppp_handler(),執行緒入口引數為os_open_tty()回傳的tty句柄值
	//* 其直接強行進行資料型別轉換即可,即作為執行緒入口引數時直接以如下形式傳遞:
	//* (void *)句柄值
	//* 不要傳遞引數地址,即(void *)&句柄,這種方式是錯誤的
#endif
};

ppp主處理執行緒將在協議堆疊加載時由os適配層函式os_thread_onpstack_start()啟動,在這里只需把其添加到作業執行緒串列中即可,剩下的交由協議堆疊自動處理,在這里需要特別說明的是主處理執行緒的入口引數為tty句柄,其值應直接傳遞給執行緒,不能傳遞句柄地址(參見上面的偽代碼注釋),比如實際移植到目標系統時如果系統只存在一路ppp,os_open_tty()回傳的tty句柄值為0,那么添加到作業執行緒串列中的ppp主處理執行緒入口引數的值應為“(void *)0”,不用關心前面的“(void *)”,這段資料型別強制轉換代碼只是為了避免編譯器報錯,ppp鏈路建立成功后,協議堆疊會以“ppp+tty句柄”的方式命名該鏈路,命名時的tty句柄值就是通過這個啟動引數獲得的,所以這個值一定要配置正確,對于單路ppp,由于tty句柄值為0,所以ppp鏈路的名稱為“ppp0”,

       至此,ppp網卡相關的移植作業完成,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/528688.html

標籤:其他

上一篇:記在Linux系統實作用nginx決議php

下一篇:Linux 掛載Windows共享檔案夾和NAS存盤

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more