[Linux Kernel] 讀取 /proc 底下資料最佳方法: seq_file interface

前言 最近在整合公司內部

Atheros(被高通買下) 晶片的 Router,從原本 2.6.15 升級到 2.6.34.7,升級過程遇到很多困難,其中一項升級 Wireless Driver 部份,發現在 Kernel Socket 與 User Space 溝通之間出了問題,利用 Ioctl 來取得目前在 AP 上面所有 Client 資料(包含 mac address, 處於 N or G mode…等),在 User Space 上會掉資料,後來利用 /proc 底下檔案來跟 User 之間溝通,才沒有發生任何問題,由於輸出的檔案比較多,就偏向用 2.6 Kernel 提供的 seq_file 介面( interface )建立虛擬檔案 (virtual file) 與 User Space 溝通(此方法為 Alexander Viro 所設計),此功能其實在 2.4.15 已經實做了,只是在 2.6 版本才被大量使用。 程式設計師可以透過引入 <linux/seq_file.h> 來實做 seq_file interface,seq_file 最大優勢就是讀取完全沒有4k boundry 的限制,也就是不用管會不會超出 output buffer。

The iterator interface 為了能夠讓 iterator 正常運作,我們必須實做 4 個 function (start, next, stop, show),跑得過程為 start -> show -> next -> show -> next -> stop,為了方便講解,參考

Linux Kernel(4)- seq_file 裡面範例如下:

#include 
#include 
#include  /* Necessary because we use proc fs */
#include  /* for seq_file */
#include 

MODULE_LICENSE("GPL");

#define MAX_LINE 1000
static uint32_t *lines;

/**
 * seq_start() takes a position as an argument and returns an iterator which
 * will start reading at that position.
 */
static void* seq_start(struct seq_file *s, loff_t *pos)
{
    uint32_t *lines;

    if (*pos >= MAX_LINE) {
        return NULL; // no more data to read
    }

    lines = kzalloc(sizeof(uint32_t), GFP_KERNEL);
    if (!lines) {
        return NULL;
    }

    *lines = *pos + 1;

    return lines;
}

/**
 * move the iterator forward to the next position in the sequence
 */
static void* seq_next(struct seq_file *s, void *v, loff_t *pos)
{
    uint32_t *lines = v;
    *pos = ++(*lines);
    if (*pos >= MAX_LINE) {
        return NULL; // no more data to read
    }
    return lines;
}

/**
 * stop() is called when iteration is complete (clean up)
 */
static void seq_stop(struct seq_file *s, void *v)
{
    kfree(v);
}

/**
 * success return 0, otherwise return error code
 */
static int seq_show(struct seq_file *s, void *v)
{
    seq_printf(s, "Line #%d: This is Brook's demo\n", *((uint32_t*)v));
    return 0;
}

static struct seq_operations seq_ops = {
    .start = seq_start,
    .next  = seq_next,
    .stop  = seq_stop,
    .show  = seq_show
};

static int proc_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &seq_ops);
}

static struct file_operations proc_ops = {
    .owner   = THIS_MODULE, // system
    .open    = proc_open,
    .read    = seq_read,    // system
    .llseek  = seq_lseek,   // system
    .release = seq_release  // system
};

static int __init init_modules(void)
{
    struct proc_dir_entry *ent;

    ent = create_proc_entry("brook", 0, NULL);
    if (ent) {
        ent->proc_fops = &proc_ops;
    }
    return 0;
}

static void __exit exit_modules(void)
{
    if (lines) {
        kfree(lines);
    }
    remove_proc_entry("brook", NULL);
}

module_init(init_modules);
module_exit(exit_modules);
[Read More]

[Linux] 釋放虛擬記憶體 (cache)

Linux Kernel 2.6.16 之後加入了 drop caches 的機制,可以讓系統清出多餘的記憶體,這對於搞嵌入式系統相當重要阿,Memory 不夠就不能 upgrade firmware,我們只要利用讀寫 proc 檔案就可以清除 cache 記憶體檔案,底下是操作步驟:

釋放 pagecache:捨棄一般沒使用的 cache

echo 1 > /proc/sys/vm/drop_caches

釋放 dentries and inodes

echo 2 > /proc/sys/vm/drop_caches

釋放 pagecache, dentries and inodes

echo 3 > /proc/sys/vm/drop_caches

Reference: Drop Caches 觀察 Linux 的虛擬記憶體

[Kernel Driver] 撰寫簡易 Timer 機制

在底層 Linux Kernel 提供了時序(timing)機制,方便驅動程式設計者所使用,核心是依據硬體發出的『計時器中斷』來追蹤時間的流動狀況。我們可以依據 HZ 的值來設計 Delay 機制,讓驅動程式可以每隔固定一段時間啟動或者是發出訊號,也可以利用 Timer 來讓 LED 閃爍變化,在介紹 Timer API 之前,可以先參考 Linux Kernel: 簡介HZ, tick and jiffies 這篇文章,瞭解一些相關名詞,舉例:如果想知道一秒後的 jiffies 時間,可以寫成底下: #ifdef CONFIG_BMA150_TIMER #include #endif j = jiffies; /* 一秒之後 */ stamp_1 = j + HZ; /* 半秒之後 */ stamp_1 = j + HZ/2; /* 20秒之後 */ stamp_1 = j + 20*HZ; Timer API 用法 筆記一下自己在寫 BOSCH Sensortec 三軸加速偵測器(BMA150 Sensor) Driver 的時候,遇到底層要回報 input event X,Y,Z 到 Android HAL(Hardware abstraction layer),所以利用 Timer 的機制定時 report 給 Android。 首先宣告: [Read More]

[Linux Kernel] 簡單 hello world: License and Module 介紹(part 3)

在 Kernel 2.4 或以上版本,在編譯模組完成,要進行 load module 之前,你會發現底下訊息: # insmod hello-3.o Warning: loading hello-3.o will taint the kernel: no license See http://www.tux.org/lkml/#export-tainted for information about tainted modules 很顯然這訊息是要您在 kernel module 裡面加上版權宣告,例如:"GPL","GPL v2"…等來宣告您的 module 並非 open source,利用 MODULE_LICENSE() 巨集來宣告程式 License,同樣的,可以用 MODULE_DESCRIPTION() 來描述此模組或者是 Driver 的功用跟簡介,以及用 MODULE_AUTHOR() 來定義此模組作者,這些巨集都可以在 linux/module.h 裡找到,但是這些並非用於 Kernel 本身,如果大家想看範例程式,可以到 drivers/ 資料夾底下觀看每一個 Driver 程式,底下是簡單 hello world 範例: #include /* pr_info所需 include 檔案*/ #include #include /* 所有 module 巨集需要檔案*/ #include static int __init hello_init(void) { pr_info(" [Read More]

[Linux Kernel] 撰寫 Hello, World module: The __init and __exit Macros (part 2).

再看此篇之前,可以先閱讀作者先前寫的:『[Linux Kernel Driver] 撰寫簡單 Hello, World module (part 1).』,今天要介紹 Driver 的 init module 區別,在 Kernel 2.4 版本,您可以自行定義 init 跟 cleanup 函式,他們不再被個別稱為 init_module() 和 cleanup_module(),現在都使用 module_init() 和 module_exit() 兩大巨集,這兩函式被定義在 linux/init.h 檔案裡面,所以在寫程式務必將其 include 喔,另外一個核心模組(MODULE_LICENSE),用於讓核心知道此模組遵守自由授權條款,若沒這項宣告,核心會跟您抱怨的喔,底下為範例: #include /* pr_info所需 include 檔案*/ #include #include /* 所有 module 需要檔案*/ #include MODULE_DESCRIPTION("Hello World !!"); MODULE_AUTHOR("Bo-Yi Wu "); MODULE_LICENSE("GPL"); static int __init hello_init(void) { pr_info("Hello, world appleboy\n"); pr_info("The process is \"%s\" (pid %i)\n", current-comm, current-pid); return 0; } static void __exit hello_exit(void) { printk(KERN_INFO " [Read More]

[Linux Kernel] 撰寫簡單 Hello, World module (part 1).

來筆記如何在 Kernel 撰寫 hello world! module,在 Ubuntu Kernel 2.6.31-14 環境下撰寫,其實不難啦,首先先進入 Kernel 目錄,請在 /usr/src 底下看自己的系統版本,或者是利用 uname -r 來知道 Kernel 版本,底下是在 Ubuntu Kernel 2.6.31-14 Kernel 實做: 進入 Kernel 目錄 # # cd Kernel directory # cd /usr/src/linux-headers-2.6.31-14-generic-pae 建立 hello 目錄 # # mkdir directory # mkdir hello 建立 Makfile 以及 hello.c hello.c: #include /* pr_info 所需 include 檔案*/ #include #include /* 所有 module 需要檔案*/ #include MODULE_DESCRIPTION("Hello World !!"); MODULE_AUTHOR(" [Read More]

[Linux Kernel] built-in vs. module

在編譯 Android Linux Kernel 2.6.29 Driver,常常遇到該把 Driver 用 built-in 或者是編譯成 module 呢?這其實看人習慣,就跟問你編輯器是用 Vim 或者是 emacs 是同樣意思,這兩者是有很大的差異,built-in 用在開機自動讀取載入,所以直接編譯成 uImage 檔案給嵌入式系統,像是 SCSI 或者是 SATA Driver 都建議編譯成 built-in 的方式,反而是一些音效驅動程式,可以編譯成 module,NTFS 就是可以編譯成 module,等您需要的時候在動態載入就可以,這樣可以減少 Kernel Image 的使用空間。 如果不想用 built-in 編譯,開機又需要驅動程式,那就需要透過 initrd 方式來啟動。底下整理兩者差異:

built-in:

開機自動載入,不可移除 Linux Kernel Image 大 需要重新 Compile

module:

可動態載入 Linux Kernel Image 小 不需要重新 Compile reference: [gentoo-user] kernel: built-in vs. module