2011年2月13日 星期日

Netlink, NETLINK_FIREWALL


關於NETLINK的介紹請看Netlink introduction,這裡假設您已經了解NETLINK,並且準備使用NETLINK_FIREWALL這個netlink family,這個family必須載入ip_queue.ko這個module。而或者您已經直接將他編進kernel當中。

我們由kernel的觀點來看NETLINK_FIREWALL提供哪些功能,首先看到net/ipv4/netfilter/ip_queue.c
static int __init ip_queue_init(void)
{
    ...
    //註冊NETLINK_FIREWALL的handler,即ipq_rcv_skb
        ipqnl = netlink_kernel_create(&init_net, NETLINK_FIREWALL, 0,
                                      ipq_rcv_skb, NULL, THIS_MODULE);
    ...
}

static void
ipq_rcv_skb(struct sk_buff *skb)
{
        mutex_lock(&ipqnl_mutex);
        __ipq_rcv_skb(skb);
        mutex_unlock(&ipqnl_mutex);
}

static inline void
__ipq_rcv_skb(struct sk_buff *skb)
{
    ...
    status = ipq_receive_peer(NLMSG_DATA(nlh), type,
                              nlmsglen - NLMSG_LENGTH(0));
    if (status < 0)
            RCV_SKB_FAIL(status);

    if (flags & NLM_F_ACK)
            netlink_ack(skb, nlh, 0);
}

// 這裡就是提供NETLINK_FIREWALL control功能的function了
// 包含了設定copy to user-space的packet型態,
// 以及設定packet的verdict(NF_DROP/NF_ACCEPT等)
static int
ipq_receive_peer(struct ipq_peer_msg *pmsg,
                 unsigned char type, unsigned int len)
{
        int status = 0;

        if (len < sizeof(*pmsg))
                return -EINVAL;

        switch (type) {
        case IPQM_MODE:
         // 設定copy到user-space的模式為何?IPQ_COPY_META或是IPQ_COPY_PACKET
                status = ipq_set_mode(pmsg->msg.mode.value,
                                      pmsg->msg.mode.range);
                break;

        case IPQM_VERDICT:
        // packet的verdict
                if (pmsg->msg.verdict.value > NF_MAX_VERDICT)
                        status = -EINVAL;
                else
                        status = ipq_set_verdict(&pmsg->msg.verdict,
                                                 len - sizeof(*pmsg));
                        break;
        default:
                status = -EINVAL;
        }
        return status;
}
上述這段code就能大概了解NETLINK_FIREWALL在kernel的流程與提供的facility為何,透過IPQM_MODE設定copy to user-space的資料模式,當user-space收到資料後,判斷該資料是要DROP還是ACCEPT,決定後再透過IPQM_VERDICT告訴kernel,該封包是要DROP還是ACCEPT。

初步了解kernel提供的功能之後,下面就寫一個當接收到icmp echo封包,且seq為奇數的就DROP,其餘的就ACCEPT的範例。
#include <stdio.h>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <netinet/in.h>
#include <linux/netlink.h>
#include <linux/netfilter.h>
#include <linux/netfilter_ipv4/ip_queue.h>

/**
 * 建立socket
 */
static int create_nl_socket(int proto)
{
    int sock;
    struct sockaddr_nl addr;

    if ((sock = socket(AF_NETLINK, SOCK_RAW, proto)) < 0) {
        fprintf(stderr, "open sock failed.(%s)\n", strerror(errno));
        return -1;
    }

    memset(&addr, 0, sizeof(addr));
    addr.nl_family = AF_NETLINK;
    addr.nl_pid = getpid();

    if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
        fprintf(stderr, "bind failed.(%s)\n", strerror(errno));
        goto bind_err;
    }

    return sock;

bind_err:
    close(sock);
    return -1;
}


/**
 * 設定IPQM_MODE
 */
static int ipq_set_mode(int sock, uint8_t mode, size_t range)
{
    unsigned char buf[1024];
    struct msghdr msg;
    struct sockaddr_nl dst = { .nl_family = AF_NETLINK };
    struct nlmsghdr *nlh;
    struct ipq_peer_msg *pmsg;
    struct iovec iov = {
        .iov_base = (void *) buf,
        .iov_len = sizeof(buf)
    };

    memset(buf, 0, sizeof(buf));
    msg = (struct msghdr) {
            .msg_name = (void *)&dst,
            .msg_namelen = sizeof(dst),
            .msg_iov = &iov,
            .msg_iovlen = 1,
    };

    nlh = (struct nlmsghdr*) buf;
    *nlh = (struct nlmsghdr) {
        .nlmsg_len = sizeof(buf),
        .nlmsg_flags = NLM_F_REQUEST,
        .nlmsg_type = IPQM_MODE,
        .nlmsg_pid = getpid(),
    };

    pmsg = (struct ipq_peer_msg*) NLMSG_DATA(nlh);
    *pmsg = (struct ipq_peer_msg) {
        .msg.mode.value = mode, // IPQM_META或是IPQM_PACKET
        .msg.mode.range = range, // 封包的大小
    };

    printf("%s(#%d):  nlmsglen:%d, NLMSG_LENGTH(0):%d\n",
            __func__, __LINE__, nlh->nlmsg_len, NLMSG_LENGTH(0));
    return sendmsg(sock, &msg, 0);
}

/**
 * 列印封包內容
 */
static void print_pkt(ipq_packet_msg_t *ipq_pkt)
{
    int i;
    printf("packet_id:0x%lx, mark:0x%lx\n,"
            "hook:%d, idev:%s, odev:%s\n,"
            "hw_proto:%d, hw_type:%d, hw_addrlen:%d\n,"
            "hw_addr:0x%02X%02X%02X%02X%02X%02X%02X%02X\n,"
            "data_len:%ld, payload:\n",
            ipq_pkt->packet_id, ipq_pkt->mark,
            ipq_pkt->hook, ipq_pkt->indev_name, ipq_pkt->outdev_name,
            ipq_pkt->hw_protocol, ipq_pkt->hw_type, ipq_pkt->hw_addrlen,
            ipq_pkt->hw_addr[0], ipq_pkt->hw_addr[1],
            ipq_pkt->hw_addr[2], ipq_pkt->hw_addr[3],
            ipq_pkt->hw_addr[4], ipq_pkt->hw_addr[5],
            ipq_pkt->hw_addr[6], ipq_pkt->hw_addr[7],
            ipq_pkt->data_len);
    for (i = 0; i < ipq_pkt->data_len; i++) {
        printf("%02X ", ipq_pkt->payload[i]);
        if (!((i+1) % 16)) printf("\n");
    }
}


/**
 * 根據封包內容給verdict
 */
static void 
get_verdict(ipq_packet_msg_t *ipq_pkt, int *verdict, unsigned long *id)
{
    struct iphdr *iph;
    struct icmphdr *icmph;

    *id = ipq_pkt->packet_id;
    if (ipq_pkt->data_len < sizeof(struct iphdr)) {
        *verdict = NF_DROP;
        return;
    }
    iph = (struct iphdr *) ipq_pkt->payload;
    if (iph->protocol == IPPROTO_ICMP) {
        icmph = (struct icmphdr *) (ipq_pkt->payload + iph->ihl * 4);
        printf("Type: %d, Id:0x%04x, seq:0x%04x\n",
                icmph->type, ntohs(icmph->un.echo.id),
           ntohs(icmph->un.echo.sequence));
        // 序號為奇數就將之DROP
        if (ntohs(icmph->un.echo.sequence) % 2) {
            *verdict = NF_DROP;
            return;
        }
    }
    // 其餘就是ACCEPT
    *verdict = NF_ACCEPT;
}

/**
 * 設定封包的verdict
 */
static int set_verdict(int sock, int verdict, unsigned long id)
{
    unsigned char buf[1024];
    struct msghdr msg;
    struct sockaddr_nl dst = { .nl_family = AF_NETLINK };
    struct nlmsghdr *nlh;
    struct ipq_peer_msg *pmsg;
    struct iovec iov = { .iov_base = (void *) buf, .iov_len = sizeof(buf) };

    memset(buf, 0, sizeof(buf));
    msg = (struct msghdr) {
            .msg_name = (void *)&dst,
            .msg_namelen = sizeof(dst),
            .msg_iov = &iov,
            .msg_iovlen = 1,
    };

    nlh = (struct nlmsghdr*) buf;
    *nlh = (struct nlmsghdr) {
        .nlmsg_len = sizeof(buf),
        .nlmsg_flags = NLM_F_REQUEST,
        .nlmsg_type = IPQM_VERDICT,
        .nlmsg_pid = getpid(),
    };

    pmsg = (struct ipq_peer_msg*) NLMSG_DATA(nlh);
    *pmsg = (struct ipq_peer_msg) {
        .msg.verdict.value = verdict, // NF_DROP或是NF_ACCEPT
        // packet_id詳細資料請看kernel的ipq_set_verdict()
        .msg.verdict.id = id,
    };

    char *p = "NONE";
    switch (verdict) {
        case NF_DROP:
            p = "DROP";
            break;
        case NF_ACCEPT:
            p = "ACCEPT";
            break;
    }
    printf("%s(#%d): %s packet %ld\n", __func__, __LINE__, p, id);
    return sendmsg(sock, &msg, 0);
}

/**
 * 處理接收到的封包
 */
static int ipq_recv_pkt(int sock, size_t len)
{
    unsigned char buf[NLMSG_SPACE(0) + len];
    struct msghdr msg;
    struct sockaddr_nl dst = { .nl_family = AF_NETLINK };
    struct nlmsghdr *nlh;
    struct iovec iov = {
        .iov_base = (void *) buf,
        .iov_len = len,
    };

    memset(buf, 0, sizeof(buf));
    msg = (struct msghdr) {
            .msg_name = (void *)&dst,
            .msg_namelen = sizeof(dst),
            .msg_iov = &iov,
            .msg_iovlen = 1,
    };

    len = recvmsg(sock, &msg, 0);
    for (nlh = (struct nlmsghdr *) buf; NLMSG_OK (nlh, len);
            nlh = NLMSG_NEXT (nlh, len)) {
        /* The end of multipart message. */
        if (nlh->nlmsg_type == NLMSG_DONE) {
            printf("NLMSG_DONE\n");
            return 0;
        }

        /* Do some error handling. */
        if (nlh->nlmsg_type == NLMSG_ERROR) {
            fprintf(stderr, "NLMSG_ERROR\n");
            return -1;
        }

        if (nlh->nlmsg_type == IPQM_PACKET) {
            int verdict;
            unsigned long id;

            print_pkt(NLMSG_DATA(nlh));
            get_verdict(NLMSG_DATA(nlh), &verdict, &id);
            set_verdict(sock, verdict, id);
        }
    }
    return 0;
}


int main(int argc, char *argv[])
{
    int sock, ret, cnt;

    sock = create_nl_socket(NETLINK_FIREWALL);
    if (sock < 0) {
        fprintf(stderr, "create_nl_socket failed\n");
        return -1;
    }

    ret = ipq_set_mode(sock, IPQ_COPY_PACKET, 2048);
    if (ret < 0) {
        fprintf(stderr, "ipq_set_mode failed\n");
    } else {
        printf("ipq_set_mode success\n");
    }

    for (cnt = 0; cnt < 10; cnt++) {
        ret = ipq_recv_pkt(sock, 2048);
    }
    close(sock);
    return 0;
}


透過iptable將經過OUTPUT chain的packet送到QUEUE去,這樣kernel才會將packet丟到NFTLINK_FIRWALL處理。


您可以看到kernel送出來的packet內容是從IP header開始。


您可以發現ping有一半的packet被DROP了。

Kernel version:2.6.37


2011年2月12日 星期六

安裝iptables到QEMU中


首先到netfilter.org中下載iptables。

接著configure並且make之後執行DESTDIR=/path/to/install make install將iptables安裝到特定目錄去(QEMU的root filesystem,我的路徑是~/initramfs)。

並且將相關的檔案(library)複製到root filesystem中。
brook@vista:~/initramfs$ echo "/usr/local/lib" > etc/ld.so.conf
brook@vista:~/initramfs$ cp /lib64/ld-linux-x86-64.so.2 lib64
brook@vista:~/initramfs$ cp /lib/libdl.so.2 lib
brook@vista:~/initramfs$ cp /lib/libm.so.6 lib
brook@vista:~/initramfs$ cp /lib/libc.so.6 lib
brook@vista:~/initramfs$ cp /sbin/ldconfig sbin
brook@vista:~/initramfs$ cp /sbin/ldconfig.real sbin
brook@vista:~/initramfs$ fakeroot
root@vista:~/initramfs# chown -R root.root .
root@vista:~/initramfs# find . |cpio -H newc -o > ../initrd


您可以發現share library路徑並沒有包含/usr/local/lib,所以要執行ldconfig


雖然順利執行iptables,但是kernel中的module並沒有load進來,所以把相關的ko複製到root filesystem中吧。

root@vista:~/initramfs# cp /usr/src/linux/net/ipv4/netfilter/iptable_filter.ko lib/modules/2.6.37/
root@vista:~/initramfs# cp /usr/src/linux/net/ipv4/netfilter/ip_tables.ko lib/modules/2.6.37/
root@vista:~/initramfs# cp /usr/src/linux/net/netfilter/x_tables.ko lib/modules/2.6.37/


終於順利的執行iptables了。

相關文章:
如何利用kvm/qemu練習linux module


2011年1月16日 星期日

Linux softirq執行分析(轉)


又是一篇精彩的文章,強力轉貼。

Linux softirq執行分析 

Author:  sinister
Email:   sinister@whitecell.org
Homepage:http://www.whitecell.org 
Date:    2007-01-11

本文對 Linux 內核軟中斷的執行流程進行了分析,並盡可能的結合當前運行環境詳細地寫出我的理解,
但這並不表明我的理解一定正確。這本是論壇裏的一篇帖子,發出來是為了抛磚引玉,如果您在閱讀本文
時發現了我的錯誤,還望得到您的指正。


今天無意中看了眼 2.6 內核的軟中斷實現,發現和以前我看到的大不相同(以前也是走馬觀花,不大仔
細),可以說改動很大。連 softirq 的調用點都不一樣了,以前是三個調用點,今天搜索了一下源代
碼,發現在多出了ksoftirqd 後,softirq 在系統中的調用點僅是在 ISR 返回時和使用了 
local_bh_enable() 函數後被調用了。網卡部分的顯示調用,我覺得應該不算是系統中的調用點。
ksoftirqd 返回去調用 do_softirq() 函數應該也只能算是其中的一個分支,因為其本身從源頭上
來講也還是在 ISR 返回時 irq_exit() 調用的。這樣一來就和前些日子寫的那份筆記
(Windows/Linux/Solaris 軟中斷機制)裏介紹的 Linux 內核部分的軟中斷有出處了,看來以後
討論 Linux kernel 代碼一定要以內核版本為前題,要不非亂了不可。得買本 Linux 方面的書了,
每次上來直接看相關代碼也不是回事,時間也不允許。


//
// do_IRQ 函數執行完硬體 ISR 後退出時調用此函數。
//

void irq_exit(void)
{
    account_system_vtime(current);
    trace_hardirq_exit();
    sub_preempt_count(IRQ_EXIT_OFFSET);

        //
        // 判斷當前是否有硬體中斷嵌套,並且是否有軟中斷在
        // pending 狀態,注意:這裏只有兩個條件同時滿足
        // 時,才有可能調用 do_softirq() 進入軟中斷。也就是
        // 說確認當前所有硬體中斷處理完成,且有硬體中斷安裝了
        // 軟中斷處理時理時才會進入。
        // 
    if (!in_interrupt() && local_softirq_pending())
                //
                // 其實這裏就是調用 do_softirq() 執行
                //
        invoke_softirq();
    preempt_enable_no_resched();
}


#ifndef __ARCH_HAS_DO_SOFTIRQ

asmlinkage void do_softirq(void)
{
    __u32 pending;
    unsigned long flags;

    //
    // 這個函數判斷,如果當前有硬體中斷嵌套,或者
    // 有軟中斷正在執行時候,則馬上返回。在這個
    // 入口判斷主要是為了與 ksoftirqd 互斥。
    //
    if (in_interrupt())
        return;

    //
    // 關中斷執行以下代碼
    //
    local_irq_save(flags);

    //
    // 判斷是否有 pending 的軟中斷需要處理。
    //
    pending = local_softirq_pending();

    //
    // 如果有則調用 __do_softirq() 進行實際處理
    //
    if (pending)
        __do_softirq();

    //
    // 開中斷繼續執行
    //
    local_irq_restore(flags);
}


//
// 最大軟中斷調用次數為 10 次。
//

#define MAX_SOFTIRQ_RESTART 10

asmlinkage void __do_softirq(void)
{
    //
    // 軟體中斷處理結構,此結構中包括了 ISR 中
    // 註冊的回調函數。
    //
    struct softirq_action *h;
    __u32 pending;
    int max_restart = MAX_SOFTIRQ_RESTART;
    int cpu;

    //
    // 得到當前所有 pending 的軟中斷。
    // 
    pending = local_softirq_pending();
    account_system_vtime(current);

    //
    // 執行到這裏要遮罩其他軟中斷,這裏也就證明了
    // 每個 CPU 上同時運行的軟中斷只能有一個。
    //
    __local_bh_disable((unsigned long)__builtin_return_address(0));
    trace_softirq_enter();

    //
    // 針對 SMP 得到當前正在處理的 CPU
    //
    cpu = smp_processor_id();
//
// 迴圈標誌
//
restart:
    //
    // 每次迴圈在允許硬體 ISR 強佔前,首先重置軟中斷
    // 的標誌位元。
    //
    /* Reset the pending bitmask before enabling irqs */
    set_softirq_pending(0);

    //
    // 到這裏才開中斷運行,注意:以前運行狀態一直是關中斷
    // 運行,這時當前處理軟中斷才可能被硬體中斷搶佔。也就
    // 是說在進入軟中斷時不是一開始就會被硬體中斷搶佔。只有
    // 在這裏以後的代碼才可能被硬體中斷搶佔。
    //
    local_irq_enable();

    //
    // 這裏要注意,以下代碼運行時可以被硬體中斷搶佔,但
    // 這個硬體 ISR 執行完成後,它的所註冊的軟中斷無法馬上運行,
    // 別忘了,現在雖是開硬體中斷執行,但前面的 __local_bh_disable()
    // 函數遮罩了軟中斷。所以這種環境下只能被硬體中斷搶佔,但這
    // 個硬中斷註冊的軟中斷回調函數無法運行。要問為什麼,那是因為
    // __local_bh_disable() 函數設置了一個標誌當作互斥量,而這個
    // 標誌正是上面的 irq_exit() 和 do_softirq() 函數中的
    // in_interrupt() 函數判斷的條件之一,也就是說 in_interrupt() 
    // 函數不僅檢測硬中斷而且還判斷了軟中斷。所以在這個環境下觸發
    // 硬中斷時註冊的軟中斷,根本無法重新進入到這個函數中來,只能
    // 是做一個標誌,等待下面的重複迴圈(最大 MAX_SOFTIRQ_RESTART)
    // 才可能處理到這個時候觸發的硬體中斷所註冊的軟中斷。
    //


    //
    // 得到軟中斷向量表。
    //
    h = softirq_vec;

    //
    // 迴圈處理所有 softirq 軟中斷註冊函數。
    // 
    do {
        //
        // 如果對應的軟中斷設置 pending 標誌則表明
        // 需要進一步處理它所註冊的函數。
        //
        if (pending & 1) {
            //
            // 在這裏執行了這個軟中斷所註冊的回調函數。
            //
            h->action(h);
            rcu_bh_qsctr_inc(cpu);
        }
        //
        // 繼續找,直到把軟中斷向量表中所有 pending 的軟
        // 中斷處理完成。
        //
        h++;

        //
        // 從代碼裏可以看出按位操作,表明一次迴圈只
        // 處理 32 個軟中斷的回調函數。
        //
        pending >>= 1; 
    } while (pending);

    //
    // 關中斷執行以下代碼。注意:這裏又關中斷了,下面的
    // 代碼執行過程中硬體中斷無法搶佔。
    //
    local_irq_disable();

    //
    // 前面提到過,在剛才開硬體中斷執行環境時只能被硬體中斷
    // 搶佔,在這個時候是無法處理軟中斷的,因為剛才開中
    // 斷執行過程中可能多次被硬體中斷搶佔,每搶佔一次就有可
    // 能註冊一個軟中斷,所以要再重新取一次所有的軟中斷。
    // 以便下面的代碼進行處理後跳回到 restart 處重複執行。
    //
    pending = local_softirq_pending();

    //
    // 如果在上面的開中斷執行環境中觸發了硬體中斷,且每個都
    // 註冊了一個軟中斷的話,這個軟中斷會設置 pending 位,
    // 但在當前一直遮罩軟中斷的環境下無法得到執行,前面提
    // 到過,因為 irq_exit() 和 do_softirq() 根本無法進入到
    // 這個處理過程中來。這個在上面詳細的記錄過了。那麼在
    // 這裏又有了一個執行的機會。注意:雖然當前環境一直是
    // 處於遮罩軟中斷執行的環境中,但在這裏又給出了一個執行
    // 剛才在開中斷環境過程中觸發硬體中斷時所註冊的軟中斷的
    // 機會,其實只要理解了軟中斷機制就會知道,無非是在一些特
    // 定環境下調用 ISR 註冊到軟中斷向量表裏的函數而已。
    //

    //
    // 如果剛才觸發的硬體中斷註冊了軟中斷,並且重複執行次數
    // 沒有到 10 次的話,那麼則跳轉到 restart 標誌處重複以上
    // 所介紹的所有步驟:設置軟中斷標誌位元,重新開中斷執行...
    // 注意:這裏是要兩個條件都滿足的情況下才可能重複以上步驟。 
    //
    if (pending && --max_restart)
        goto restart;

    //
    // 如果以上步驟重複了 10 次後還有 pending 的軟中斷的話,
    // 那麼系統在一定時間內可能達到了一個峰值,為了平衡這點。
    // 系統專門建立了一個 ksoftirqd 線程來處理,這樣避免在一
    // 定時間內負荷太大。這個 ksoftirqd 線程本身是一個大循環,
    // 在某些條件下為了不負載過重,它是可以被其他進程搶佔的,
    // 但注意,它是顯示的調用了 preempt_xxx() 和 schedule()
    // 才會被搶佔和切換的。這麼做的原因是因為在它一旦調用 
    // local_softirq_pending() 函數檢測到有 pending 的軟中斷
    // 需要處理的時候,則會顯示的調用 do_softirq() 來處理軟中
    // 斷。也就是說,下面代碼喚醒的 ksoftirqd 線程有可能會回
    // 到這個函數當中來,尤其是在系統需要回應很多軟中斷的情況
    // 下,它的調用入口是 do_softirq(),這也就是為什麼在 do_softirq()
    // 的入口處也會用 in_interrupt()  函數來判斷是否有軟中斷
    // 正在處理的原因了,目的還是為了防止重入。ksoftirqd 實現
    // 看下面對 ksoftirqd() 函數的分析。
    //
    if (pending)
               //
               // 此函數實際是調用 wake_up_process() 來喚醒 ksoftirqd
               // 
        wakeup_softirqd();

    trace_softirq_exit();
    account_system_vtime(current);

    //
    // 到最後才開軟中斷執行環境,允許軟中斷執行。注意:這裏
    // 使用的不是 local_bh_enable(),不會再次觸發 do_softirq()
    // 的調用。
    // 
    _local_bh_enable();
}


static int ksoftirqd(void * __bind_cpu)
{
    //
    // 顯示調用此函數設置當前進程的靜態優先順序。當然,
    // 這個優先順序會隨調度器策略而變化。
    //
    set_user_nice(current, 19);

    //
    // 設置當前進程不允許被掛啟
    //
    current->flags |= PF_NOFREEZE;

    //
    // 設置當前進程狀態為可中斷的狀態,這種睡眠狀
    // 態可回應信號處理等。
    // 
    set_current_state(TASK_INTERRUPTIBLE);

    //
    // 下面是一個大循環,迴圈判斷當前進程是否會停止,
    // 不會則繼續判斷當前是否有 pending 的軟中斷需
    // 要處理。
    //
    while (!kthread_should_stop()) {
        //
        // 如果可以進行處理,那麼在此處理期間內禁止
        // 當前進程被搶佔。
        //
        preempt_disable();

        //
        // 首先判斷系統當前沒有需要處理的 pending 狀態的軟中斷
        //
        if (!local_softirq_pending()) {
            //
            // 沒有的話在主動放棄 CPU 前先要允許搶佔,因為
            // 一直是在不允許搶佔狀態下執行的代碼。
            //
            preempt_enable_no_resched();

            //
            // 顯示調用此函數主動放棄 CPU 將當前進程放入睡眠佇列,
            // 並切換新的進程執行(調度器相關不記錄在此)
            //
            schedule();

            //
            // 注意:如果當前顯示調用 schedule() 函數主動切換的進
            // 程再次被調度執行的話,那麼將從調用這個函數的下一條
            // 語句開始執行。也就是說,在這裏當前進程再次被執行的
            // 話,將會執行下面的 preempt_disable() 函數。
            //

            //
            // 當進程再度被調度時,在以下處理期間內禁止當前進程被搶佔。
            //
            preempt_disable();
        }

        //
        // 設置當前進程為運行狀態。注意:已經設置了當前進程不可搶佔
        // 在進入迴圈後,以上兩個分支不論走哪個都會執行到這裏。一是
        // 進入迴圈時就有 pending 的軟中斷需要執行時。二是進入迴圈時
        // 沒有 pending 的軟中斷,當前進程再次被調度獲得 CPU 時繼續
        // 執行時。
        //
        __set_current_state(TASK_RUNNING);

        //
        // 迴圈判斷是否有 pending 的軟中斷,如果有則調用 do_softirq()
        // 來做具體處理。注意:這裏又是一個 do_softirq() 的入口點,
        // 那麼在 __do_softirq() 當中迴圈處理 10 次軟中斷的回調函數
        // 後,如果還有 pending 的話,會又調用到這裏。那麼在這裏則
        // 又會有可能去調用 __do_softirq() 來處理軟中斷回調函數。在前
        // 面介紹 __do_softirq() 時已經提到過,處理 10 次還處理不完的
        // 話說明系統正處於繁忙狀態。根據以上分析,我們可以試想如果在
        // 系統非常繁忙時,這個進程將會與 do_softirq() 相互交替執行,
        // 這時此進程佔用 CPU 應該會很高,雖然下面的 cond_resched() 
        // 函數做了一些處理,它在處理完一輪軟中斷後當前處理進程可能會
        // 因被調度而減少 CPU 負荷,但是在非常繁忙時這個進程仍然有可
        // 能大量佔用 CPU。
        //
        while (local_softirq_pending()) {
            /* Preempt disable stops cpu going offline.
               If already offline, we'll be on wrong CPU:
               don't process */
            if (cpu_is_offline((long)__bind_cpu))
                //
                // 如果當前被關聯的 CPU 無法繼續處理則跳轉
                // 到 wait_to_die 標記出,等待結束並退出。
                // 
                goto wait_to_die;

                //
                // 執行 do_softirq() 來處理具體的軟中斷回調函數。注
                // 意:如果此時有一個正在處理的軟中斷的話,則會馬上
                // 返回,還記得前面介紹的 in_interrupt() 函數麼。
                //
                do_softirq();

                //
                // 允許當前進程被搶佔。
                //
                preempt_enable_no_resched();
                        
                //
                // 這個函數有可能間接的調用 schedule() 來切換當前
                // 進程,而且上面已經允許當前進程可被搶佔。也就是
                // 說在處理完一輪軟中斷回調函數時,有可能會切換到
                // 其他進程。我認為這樣做的目的一是為了在某些負載
                // 超標的情況下不至於讓這個進程長時間大量的佔用 CPU,
                // 二是讓在有很多軟中斷需要處理時不至於讓其他進程
                // 得不到回應。
                //
                cond_resched();

                //
                // 禁止當前進程被搶佔。
                //
                preempt_disable();

                //
                // 處理完所有軟中斷了嗎?沒有的話繼續迴圈以上步驟
                //
        }

        //
        // 待一切都處理完成後,允許當前進程被搶佔,並設置
        // 當前進程狀態為可中斷狀態,繼續迴圈以上所有過程。
        //
        preempt_enable();
        set_current_state(TASK_INTERRUPTIBLE);
    }
   
    //
    // 如果將會停止則設置當前進程為運行狀態後直接返回。
    // 調度器會根據優先順序來使當前進程運行。
    //
    __set_current_state(TASK_RUNNING);
    return 0;

//
// 一直等待到當前進程被停止
//
wait_to_die:

    //
    // 允許當前進程被搶佔。
    //
    preempt_enable();
    /* Wait for kthread_stop */

    //
    // 設置當前進程狀態為可中斷的狀態,這種睡眠狀
    // 態可回應信號處理等。
    // 
    set_current_state(TASK_INTERRUPTIBLE);

    //
    // 判斷當前進程是否會被停止,如果不是的話
    // 則設置進程狀態為可中斷狀態並放棄當前 CPU
    // 主動切換。也就是說這裏將一直等待當前進程
    // 將被停止時候才結束。
    //
    while (!kthread_should_stop()) {
        schedule();
        set_current_state(TASK_INTERRUPTIBLE);
    }

    //
    // 如果將會停止則設置當前進程為運行狀態後直接返回。
    // 調度器會根據優先順序來使當前進程運行。
    //
    __set_current_state(TASK_RUNNING);
    return 0;
}


參考:
linux kernel source 2.6.19.1 /kernel/softirq.c
WSS(Whitecell Security Systems),一個非營利性民間技術組織,致力於各種系統安全技術的研究。
堅持傳統的hacker精神,追求技術的精純。
WSS 主頁:http://www.whitecell.org/ 
WSS 論壇:http://www.whitecell.org/forums/



熱門文章