第五章 WinME在Linux作業系統下的實作
5.3 事件通知機制的實作
5.3.5 其它的事件
關於其它事件,在核心中的實作方式就是在改變狀態的核心程式碼中增加一 個 rtnetlink 的觸發點即可。
可是有一點要注意的,就是當改變狀態的核心程式碼是在中斷環境 (Interrupt Context) 下處理時,我們是沒有辦法直接傳送 rtnetlink 的訊息給使用者空間的,
因為在中斷環境下是不能處理會進入睡眠模式的或是進行 block I/O 動作的程式 碼的。可是通常和驅動程式有關的狀態大部分都是在中斷環境中所改變的,因為 當硬體上的資訊改變時通常都是藉由中斷來通知系統。所以這個時候就沒辦法靠 增加 rtnetlink 的觸發點來傳送訊息。
要解決以上的問題,Linux 在 2.4 版的核心中有提供 task queue 的下半部 (BottomHalf) 處理方式,而其中的 scheduler queue 中的 task 則可以在程序環境 (Process Context) 下所執行的。
所以我們在中斷環境下新增一個名稱為 win_task_queue 的 scheduler queue,
把我們要執行的傳送 rtnetlink 訊息給使用者空間的函式加入 win_task_queue 中,
然後呼叫核心中提供的 schedule_task 函式將 win_task_queue 為參數傳入,這樣就 可以在程序環境中執行我們的函式。schedule_task 會喚醒核心執行緒 keventd 來 處理 win_task_queue 中所佇列的函式。
以 LINK_UP和LINK_DOWN的事件為例,當網路卡驅動程式發現這是一個 LINK_UP或是LINK_DOWN的中斷時,驅動程式會去執行netif_carrier_on來將核 心中維護網路卡狀態的carry bit 開啟,或是執行 netif_carrier_off 將 carry bit 清 掉,不過這些都是在中斷環境中所執行的,因此我們沒辦法直接在
netif_carrier_xx的函式中傳送LINK_UP或LINK_DOWN的訊息給使用者空間。所 以這邊我們就以task queue的方式解決。相關程式碼可參考 Table 5-6。
Table 5-6 送 LINK_UP、LINK_DOWN 事件的核心程式碼
/* use rtnetlink socket to notify user space LINK events */
static void win_notifier_taskq(void* dummy) {
/* if nocarry bit is zero, send NETDEV_LINKDOWN event */
if(test_bit(__LINK_STATE_NOCARRIER, &win_dev->state)) {
win_rtmsg_ifinfo(NETDEV_LINKDOWN, RTM_DRIVER_EVENT,…);
} else {
win_rtmsg_ifinfo(NETDEV_LINKUP, RTM_DRIVER_EVENT, ..);
} }
/* declare a scheduler queue */
static struct tq_struct linkwatch_queue;
static int linkwatch_fire_event(struct net_device *dev) {
<-- some initial code -->
/* call schedule_task to process functions in linkwatch_queue */
schedule_task(&linkwatch_queue);
}
/* when interface card connect POA, it’s driver will call this routine*/
void netif_carrier_on(struct net_device *dev) {
if (test_and_clear_bit(__LINK_STATE_NOCARRIER, &dev->state)) linkwatch_fire_event(dev);
if (netif_running(dev))
__netdev_watchdog_up(dev);
}
/* when interface card disconnect POA, it’s driver will call this routine*/
void netif_carrier_off(struct net_device *dev) {
if (!test_and_set_bit(__LINK_STATE_NOCARRIER, &dev->state)) linkwatch_fire_event(dev);
}
第 6 章 成果及貢獻
Table 6-1 Linux 下使用 rtnetlink socket 新增一個資料到路由表中的程式碼片段
int main(int argc, char *argv[]) {
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
/* setup local address & bind using this address */
bzero(&la, sizeof(la));
la.nl_family = AF_NETLINK;
la.nl_pid = getpid();
bind(fd, (struct sockaddr*) &la, sizeof(la));
/* initialize RTNETLINK request buffer */
bzero(&req, sizeof(req));
六
/* add first attrib: */
/* set destination IP addr and increment the RTNETLINK buffer size*/
rtap = (struct rtattr *) req.buf;
rtap->rta_type = RTA_DST;
rtap->rta_len = sizeof(struct rtattr) + 4;
inet_pton(AF_INET, dsts, ((char *)rtap) + sizeof(struct rtattr));
rtl += rtap->rta_len;
/* add second attrib: */
/* set ifc index and increment the size */
rtap = (struct rtattr *) (((char *)rtap) + rtap->rta_len);
rtap->rta_type = RTA_OIF;
rtap->rta_len = sizeof(struct rtattr) + 4;
memcpy(((char *)rtap) + sizeof(struct rtattr), &ifcn, 4);
rtl += rtap->rta_len;
/* setup the NETLINK header */
req.nl.nlmsg_len = NLMSG_LENGTH(rtl);
req.nl.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
req.nl.nlmsg_type = RTM_NEWROUTE;
/* setup the service header (struct rtmsg) */
req.rt.rtm_family = AF_INET;
req.rt.rtm_table = RT_TABLE_MAIN;
req.rt.rtm_protocol = RTPROT_STATIC;
req.rt.rtm_scope = RT_SCOPE_UNIVERSE;
req.rt.rtm_type = RTN_UNICAST;
/* set the network prefix size */
req.rt.rtm_dst_len = pn;
/* create the remote address to communicate */
bzero(&pa, sizeof(pa));
pa.nl_family = AF_NETLINK;
/* initialize & create the struct msghdr supplied to the sendmsg() function */
bzero(&msg, sizeof(msg));
msg.msg_name = (void *) &pa;
msg.msg_namelen = sizeof(pa);
/* place the pointer & size of the RTNETLINK message in the struct msghdr */
iov.iov_base = (void *) &req.nl;
iov.iov_len = req.nl.nlmsg_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
/* send the RTNETLINK message to kernel */
rtn = sendmsg(fd, &msg, 0);
/* close socket */
close(fd);
}
Table 6-2 使用 WinME 所提供的程式界面新增一個資料到路由表中的程式碼片段
struct set_routing_table_param { UINT8 act;
int main(int argc, char* argv[]) {
/* routing table’s attribute, dst means destination address * ifcn = 2 means out network interface’s Link ID is 2
struct set_routing_table_param *srtp;
cmd.i8_cmd = SET_ ROUTING_TABLE;
cmd.pv_param = (VOID*)malloc(sizeof(struct set_routing_table_param));
srtp = (struct set_routing_table_param *)cmd.pv_param;
/* fill param field */
srtp->act = 1;
inet_pton(AF_INET, dsts, ((char *)srtp->dst);
srtp->gateway = 0;
inet_pton(AF_INET, netmask, ((char *)srtp->netmask);
srtp->link_id = ifcn;