[13]APUE:KQUEUE / FreeBSD

[a] 概述node

  • kqueue API 由兩個函數(kqueue、kevent)、一個輔助宏(EV_SET)、一個結構體(struct kevent)構成,能夠應用於 socket、FIFO、pipe、aio、signal、process、regular file、path 等對象
  • 與 Linux 下的 epoll 功能相似,性能至關

[b] kqueue / kevent / EV_SET數組

#include <sys/types.h>
#include <sys/event.h>
#include <sys/time.h>
int kqueue(void) 
//成功返回 kqueue 標識符,供 kevent 函數首個參數使用,出錯返回 -1 int kevent(int kq, const struct kevent *changelist, int nchange, struct kevent *eventlist, int nevent, const struct timespec *timeout)
//成功返回 eventlist 數組的元素個數,出錯信息寫入 struct kevent 中的 flag 字段,不能寫入的出錯信息返回 -1,若 timeout 超時,則返回 0 EV_SET(kev, ident, filter, flags, fflags, data, udata)
//kev 是指向 struct kevent 的指針,其他字段依次對應於 struct kevent 中的字段 
struct kevent {
    uintptr_t    ident; //event 標識符,根據 filter 的不一樣能夠爲 fd、pid 等
    short    filter; //監聽類別(目標),如 socket、process 等
    u_short    flags; //通用標誌,可用於保存返回信息
    u_int    fflags; //特定 filter 的專有標誌,可用於保存專有返回信息
    intptr_t    data; //特定 filter 存儲專有信息
    void    *udata; //進程預約義內容,常做識別用途,kqueue 不更改此項,原樣返回其內容
  • kqueue 函數用於初始化一個 kqueue 隊列,單個進程能夠創建多個 kqueue,kqueue 描述符與文件描述符相似,也能夠複用,不能被 fork 出的子進程繼承
  • kevent 用於設置策略及如何獲取結果,其中:
    • changelist 用於添加或修改 event
    • eventlist 用於向調用進程返回 event 結果,若實際產生的事件數量超過了 nevent,可經過屢次調用獲取所有結果;若 nevent 爲 0,則即便指定了 timeout 也會當即返回,不阻塞
    • changelist 與 eventlist 能夠指向同一個 kevent 結構體數組;nchange 與 nevent 分別表明兩個結構體數組的元素數量上限
    • 若是 timeout 參數爲 NULL,則 kevent 函數將阻塞,直到有事件發生,若不爲 NULL,阻塞到超時爲止(<24h),若 timespec 結構體各字段均設置爲 0,則當即返回當前還沒有處理的全部事件
  • EV_SET 僅是預約義的一個用於設置 struct kevent 的輔助宏,與手動指定結構體的各字段效果相同

[c] filter 及專有規則app

  • /usr/include/sys/event.h 中定義的 10 種 filter
  • #define EVFILT_READ             (-1)
    #define EVFILT_WRITE            (-2)
    #define EVFILT_AIO              (-3)    /* attached to aio requests */
    #define EVFILT_VNODE            (-4)    /* attached to vnodes */
    #define EVFILT_PROC             (-5)    /* attached to struct proc */
    #define EVFILT_SIGNAL           (-6)    /* attached to struct proc */
    #define EVFILT_TIMER            (-7)    /* timers */
    #define EVFILT_PROCDESC         (-8)    /* attached to process descriptors */
    #define EVFILT_FS               (-9)    /* filesystem events */
    #define EVFILT_LIO              (-10)   /* attached to lio requests */
  • EVFILT_READ:用於檢測數據什麼時候可讀,不一樣的描述符將返回不一樣的信息
    • 若 ident 字段指定爲一個文件描述符,則該事件表示文件偏移量還沒有到達文件末尾,當前基於 SEEK_END 的 offset 值保存在 data 成員字段中
    • 若爲 pipe 或 FIFO 則表示已有實際數據可讀,data 字段存儲可讀的字節數量
  • EVFILT_WRITE:用於檢測是否能夠對描述符執行寫操做;對 pipe、FIFO 或 socket,data 字段表明緩衝區中可用的空間(bytes),此 filter 對文件或目錄沒有意義(vnodes)
  • EVFILT_AIO:用於異步 I/O 操做,用於檢測和 aio_error 系統調用相似的條件
  • EVFILT_VNODE:用於檢測文件系統上某個文件的各類變更事件,ident 須指定一個有效的文件描述符,fflags 字段設定檢測的事件類別(以下)
    • #define NOTE_DELETE     0x0001                  /* vnode was removed */
      #define NOTE_WRITE      0x0002                  /* data contents changed, a write occurred on the file */
      #define NOTE_EXTEND     0x0004                  /* size increased */
      #define NOTE_ATTRIB     0x0008                  /* attributes changed */
      #define NOTE_LINK       0x0010                  /* link count changed */
      #define NOTE_RENAME     0x0020                  /* vnode was renamed */
      #define NOTE_REVOKE     0x0040                  /* vnode access was revoked */
      #define NOTE_OPEN       0x0080                  /* vnode was opened */
      #define NOTE_CLOSE      0x0100                  /* file closed, fd did not allowed write */
      #define NOTE_READ       0x0400                  /* file was read */
  • EVFILT_PROC:用於檢測發生在另外一個進程裏的事件,此時 ident 字段須指定進程 ID,fflags 字段設定檢測的事件類別(以下)
    • #define NOTE_EXIT       0x80000000              /* process exited, exit status will be stored in 'data' */
      #define NOTE_FORK       0x40000000              /* process forked */
      #define NOTE_EXEC       0x20000000              /* process exec'd */
      #define NOTE_TRACK      0x00000001              /* follow across forks */
      #define NOTE_TRACKERR   0x00000002              /* could not track child */
      #define NOTE_CHILD      0x00000004              /* am a child process */
  • EVFILT_SIGNAL:檢測是否有信號發送至 ident 所指定的進程 ID
    • 事件將在進行完常規的信號處理以後,放到 kqueue 中,data 字段存儲收到的信號數量,包括被設置了 SIG_IGN 的信號(SIG_CHLD 除外,此信號若被忽略則不計數)
    • 此 filter 默認帶有 EV_CLEAR 標誌

[d] 通用標誌:flags異步

  • /usr/include/sys/event.h 定義的通用 flags,按位 OR 進行組合
    • /* actions */
      #define EV_ADD          0x0001          /* add event to kq (implies enable) */
      #define EV_DELETE       0x0002          /* delete event from kq */
      #define EV_ENABLE       0x0004          /* enable event */
      #define EV_DISABLE      0x0008          /* disable event (not reported) */
      #define EV_FORCEONESHOT 0x0100          /* enable _ONESHOT and force trigger */
      
      /* flags */
      #define EV_ONESHOT      0x0010          /* only report one occurrence */
      #define EV_CLEAR        0x0020          /* clear event state after reporting */
      #define EV_RECEIPT      0x0040          /* force EV_ERROR on success, data=0 */
      #define EV_DISPATCH     0x0080          /* disable event after once reporting */
      
      #define EV_SYSFLAGS     0xF000          /* reserved by system */
      #define EV_DROP         0x1000          /* note should be dropped */
      #define EV_FLAG1        0x2000          /* filter-specific flag */
      #define EV_FLAG2        0x4000          /* filter-specific flag */
      
      /* returned values */
      #define EV_EOF          0x8000          /* EOF detected */
      #define EV_ERROR        0x4000          /* error, data contains errno */
  • EV_ADD:添加事件,默認附帶 EV_ENABLE 行爲,相同事件重複添加會致使舊的事件被覆蓋
  • EV_DELETE:從 kqueue 中清除指定項(filter)
  • EV_DISABLE:禁止 kqueue 返回某個事件的信息,但並不刪除
  • EV_ONESHOT:針對同一對象的同類事件僅返回一次
  • EV_CLEAR:返回事件後清空現有狀態,用途是監測每次狀態的改變,而非當前的狀態;對可寫、可讀這種有持續存在的事件狀態每每須要設置,對一次性狀態,如信號計數,則不須要

[e] 樣例 socket

#include <sys/types.h>
#include "sys/event.h"
#include <sys/time.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

char *t0 = "/tmp/test_0";
char *t1 = "/tmp/test_1";

void unlinktmp(int signo)
{
        unlink(t0);
        unlink(t1);
        printf("%s, %s deleted! \n", t0, t1);
        exit(0);
}

int main(void)
{
        struct kevent event[2];
        struct kevent tevent[2];
        int kq, ret;
        unsigned long fd_0, fd_1;
        
        struct sigaction act;
        act.sa_handler=unlinktmp;
        sigemptyset(&act.sa_mask);
        act.sa_flags=0;

        sigaction(SIGINT, &act, NULL);

        fd_0 = open(t0, O_WRONLY|O_CREAT|O_TRUNC, 0600);
        fd_1 = open(t1, O_WRONLY|O_CREAT|O_TRUNC, 0600);

        kq = kqueue();
        if (kq == -1)
        {
                perror("kqueue()");
        }

        EV_SET(&event[0], fd_0, EVFILT_VNODE, EV_ADD|EV_CLEAR, NOTE_LINK|NOTE_EXTEND, 0, NULL);
        EV_SET(&event[1], fd_1, EVFILT_VNODE, EV_ADD|EV_CLEAR, NOTE_LINK|NOTE_EXTEND, 0, NULL);

        kevent(kq, event, 2, NULL, 0, NULL);

        while(1) 
        {
                ret = kevent(kq, NULL, 0, tevent, 2, NULL);
                if (ret < 0) 
                {
                        perror("kevent");
                }
                else
                {
                        for(int i = 0; i < ret; ++i)
                        {
                                if (tevent[i].fflags & NOTE_LINK)
                                {
                                        printf("%s: link num changed! \n", tevent[i].ident == fd_0 ? t0 : t1);
                                }
                                else if (tevent[i].fflags & NOTE_EXTEND)
                                {
                                        printf("%s: extanded! \n", tevent[i].ident == fd_0 ? t0 : t1);
                                }
                                else
                                {
                                        printf("%s: nothing happened!\n", tevent[i].ident == fd_0 ? t0 : t1);
                                }
                        }
                }
        }
}
相關文章
相關標籤/搜索