2009年11月26日 星期四

Linux Kernel(3)- procfs


(III)將和大家介紹procfs,雖然後來procfs已經失控,也不再建議大家使用,但是還是有一定的用途,所以,有興趣的人可以一起探討一下。

/proc是一個特殊的檔案系統,當讀取/proc底下的檔案時,其內容由kernel動態產生,很多應用程式也都是存取/proc底下的檔案,我們藉由兩個範例來說明。第一個用於當/proc只提供read-only的時候,第二個用於/proc提供具有write功能的時候。

Read-Only

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>

MODULE_LICENSE("GPL");

#define MAX_LINE        1000

/* read file operations */
static int read_proc(char *page, char **start, off_t off,
                       int count, int *eof, void *data)
{
    uint32_t curline;
    char *p, *np;

    *start = p = np = page;
    curline = *(uint32_t*) data;

    printk("count=%d, off=%ld\n", count, off);
    for (;curline < MAX_LINE && (p - page) < count; curline++) {
        np += sprintf(p, "Line #%d: This is Brook's demo\n", curline);
        if ((count - (np - page)) < (np - p)) {
            break;
        }
        p = np;
    }

    if (curline < MAX_LINE) {
        *eof = 1;
    }
    *(uint32_t*)data = curline;
    return (p - page);
}

/* private data */
static uint32_t *lines;

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

    lines = kzalloc(sizeof(uint32_t), GFP_KERNEL);
    if (!lines) {
        printk("no mem\n");
        return -ENOMEM;
    }
    *lines = 0;

    /* create a procfs entry for read-only */
    ent = create_proc_read_entry ("brook", S_IRUGO, NULL, read_proc, lines);
    if (!ent) {
        printk("create proc failed\n");
        kfree(lines);
    }
    return 0;
}

static void __exit exit_modules(void)
{
    if (lines) {
        kfree(lines);
    }
    /* remove procfs entry */
    remove_proc_entry("brook", NULL);
}

module_init(init_modules);
module_exit(exit_modules);

在這個例子中我們使用一個globa variable "lines",用於存放上次列印到第幾個數值,MAX_LINE是上限。一開始載入module就先分配記憶體給lines,接著呼叫create_proc_read_entry()建立一個唯讀的procfs檔案。

static inline struct proc_dir_entry *
    create_proc_read_entry(const char *name,
        mode_t mode, struct proc_dir_entry *base,
        read_proc_t *read_proc, void * data);


name是proc底下的檔案名稱。mode是檔案的mode,可以直接用0666的表示法表示,如果是建立目錄則要or S_IFDIR。base如果為null,則會把/proc當根目錄,可以是其他的proc_dir_entry pointer,那麼就會以proc_dir_entry當根目錄開始往下長。read_proc則是當user讀取該檔案時的call-back function。data則用於存放額外資訊的地方,可以先alloc,然後每次執行read時,都可以拿出來使用。

typedef int (read_proc_t)(char *page, char **start, off_t off,
    int count, int *eof, void *data);

page是系統給的buffer,也是我們要寫入的地方,寫到buffer裡面的資料,會被當成proc的內容。start如果只要一次就可以read完,可以忽略start,否則只要將page設定給start就可以了。count則是user-space想要讀取的資料量。offset指示目前檔案位置。eof指示是否已經讀到結尾。data用於存放額外的資訊。


Writable

#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>

MODULE_LICENSE("GPL");

#define MAX_LINE        1000

/* write file operations */
static int write_proc(struct file *file, const char __user *buf,
                       unsigned long count, void *data)
{
    char num[10], *p;
    int nr, len;

    /* no data be written */
    if (!count) {
        printk("count is 0\n");
        return 0;
    }

    /* Input size is too large to write our buffer(num) */
    if (count > (sizeof(num) - 1)) {
        printk("input is too large\n");
        return -EINVAL;
    }

    if (copy_from_user(num, buf, count)) {
        printk("copy from user failed\n");
        return -EFAULT;
    }

    /* atoi() */
    p = num;
    len = count;
    nr = 0;
    do {
        unsigned int c = *p - '0';
        if (*p == '\n') {
            break;
        }
        if (c > 9) {
            printk("%c is not digital\n", *p);
            return -EINVAL;
        }
        nr = nr * 10 + c;
        p++;
    } while (--len);
    *(uint32_t*) data = nr;

    return count;
}

static int read_proc(char *page, char **start, off_t off,
                       int count, int *eof, void *data)
{
    uint32_t curline;
    char *p, *np;

    *start = p = np = page;
    curline = *(uint32_t*) data;

    printk("count=%d, off=%ld\n", count, off);
    for (;curline < MAX_LINE && (p - page) < count; curline++) {
        np += sprintf(p, "Line #%d: This is Brook's demo\n", curline);
        if ((count - (np - page)) < (np - p)) {
            break;
        }
        p = np;
    }

    if (curline < MAX_LINE) {
        *eof = 1;
    }
    *(uint32_t*)data = curline;
    return (p - page);
}

static uint32_t *lines;

static int __init init_modules(void)
{

    struct proc_dir_entry *ent;
    lines = kzalloc(sizeof(uint32_t), GFP_KERNEL);
    if (!lines) {
        printk("no mem\n");
        return -ENOMEM;
    }
    *lines = 0;

    ent = create_proc_entry("brook", S_IFREG | S_IRWXU, NULL);
    if (!ent) {
        printk("create proc failed\n");
        kfree(lines);
    } else {
        ent->write_proc = write_proc;
        ent->read_proc = read_proc;
        ent->data = lines;
    }
    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);

第二個範例則提供了write的功能,所以不能直接使用create_proc_read_entry()建立procfs檔案,而是要使用create_proc_entry()建立procfs檔案,並且設定read/write file operations,以及"data"。該範例中的write是設定line。

typedef int (write_proc_t)(struct file *file, const char __user *buffer,
    unsigned long count, void *data);




2009年11月25日 星期三

Linux Kernel(2)- register char device


II我們將介紹如何向Linux註冊char device,並且read/write該device。
#include <linux/init.h>
#include <linux/init.h>
#include <linux/module.h>

#include <linux/fs.h> // chrdev
#include <linux/cdev.h> // cdev_add()/cdev_del()
#include <asm/uaccess.h> // copy_*_user()

MODULE_LICENSE("GPL");

#define DEV_BUFSIZE 1024

int dev_major;
int dev_minor;
struct cdev *dev_cdevp = NULL;

int dev_open(struct inode*, struct file*);
int dev_release(struct inode*, struct file*);
ssize_t dev_read(struct file*, char __user*, size_t, loff_t*);
ssize_t dev_write(struct file*, const char __user *, size_t, loff_t*);
static void __exit exit_modules(void);

struct file_operations dev_fops = {
    .owner = THIS_MODULE,
    .open = dev_open,
    .release = dev_release,
    .read = dev_read,
    .write = dev_write,
};

int dev_open(struct inode *inode, struct file *filp)
{
    printk("%s():\n", __FUNCTION__);
    return 0;
}

int dev_release(struct inode *inode, struct file *filp)
{
    printk("%s():\n", __FUNCTION__);
    return 0;
}

ssize_t 
dev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    char data[] = "brook";
    ssize_t ret = 0;

    printk("%s():\n", __FUNCTION__);

    if (*f_pos >= sizeof(data)) {
        goto out;
    }

    if (count > sizeof(data)) {
        count = sizeof(data);
    }

    if (copy_to_user(buf, data, count) < 0) {
        ret = -EFAULT;
        goto out;
    }
    *f_pos += count;
    ret = count;

out:
    return ret;
}

ssize_t 
dev_write(struct file *filp, const char __user *buf, size_t count,
          loff_t *f_pos)
{
    char *data;
    ssize_t ret = 0;

    printk("%s():\n", __FUNCTION__);

    data = kzalloc(sizeof(char) * DEV_BUFSIZE, GFP_KERNEL);
    if (!data) {
        return -ENOMEM;
    }

    if (count > DEV_BUFSIZE) {
        count = DEV_BUFSIZE;
    }

    if (copy_from_user(data, buf, count) < 0) {
        ret = -EFAULT;
        goto out;
    }
    printk("%s(): %s\n", __FUNCTION__, data);
    *f_pos += count;
    ret = count;
out:
    kfree(data);
    return ret;
}

static int __init init_modules(void)
{
    dev_t dev;
    int ret;

    ret = alloc_chrdev_region(&dev, 0, 1, "brook");
    if (ret) {
        printk("can't alloc chrdev\n");
        return ret;
    }

    dev_major = MAJOR(dev);
    dev_minor = MINOR(dev);
    printk("register chrdev(%d,%d)\n", dev_major, dev_minor);

    dev_cdevp = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    if (dev_cdevp == NULL) {
        printk("kzalloc failed\n");
        goto failed;
    }
    cdev_init(dev_cdevp, &dev_fops);
    dev_cdevp->owner = THIS_MODULE;
    ret = cdev_add(dev_cdevp, MKDEV(dev_major, dev_minor), 1);
    if (ret < 0) {
        printk("add chr dev failed\n");
        goto failed;
    }

    return 0;

failed:
    if (dev_cdevp) {
        kfree(dev_cdevp);
        dev_cdevp = NULL;
    }
    return 0;
}

static void __exit exit_modules(void)
{
    dev_t dev;

    dev = MKDEV(dev_major, dev_minor);
    if (dev_cdevp) {
        cdev_del(dev_cdevp);
        kfree(dev_cdevp);
    }
    unregister_chrdev_region(dev, 1);
    printk("unregister chrdev\n");
}

module_init(init_modules);
module_exit(exit_modules);

當module被load進來時,init_modules()會被執行,而init_modules()做了幾件事情,首先向Linux要求分配char device number "alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)"。dev是分配到的dev_t(output parameter),baseminor是起始的minor number,當baseminor=0會由系統分配,否則會試圖尋找可以符合baseminor的minor number,count是minor的數量,name是註冊的名稱。
接著設定struct cdev "cdev_init()",並且註冊到系統"cdev_add()",在cdev_init()的同時也設定了該device的file operations。

struct file_operations就是device的file operations,簡單的說,UNIX所有的東西都可以視為檔案,當然包含device,既然是檔案,就會有open、close/release、read和write等等操作,而這些操作都會觸發對應的function來反應,這就是file operations的功能了。

我們的read會回應"brook",而write則是藉由printk()印出來,open和close/release都單純的回應成功而已。

而exit_modules()當然就是負責歸還當初和OS要的資源了"kfree()",當然包含之前註冊的char device number,現在也要unregister "unregister_chrdev_region()"。




U-Boot Standalone Applications


U-Boot支援"standlone" applications,可以動態的load並且執行。這些application可以存取U-Boot的I/O,memort和interrupt services。

在U-Boot目錄裡面有example,裡面就有"standlone" application的範例,只要確定U-Boot/Makefile的SUBDIRS有將example包含進去即可。接著我們就用loads指令將hello_world.srec載到U-Boot上,並且執行。注意:要將modem設為ASCII。

這樣可以快速的在U-Boot上發展一些應用程式。



2009年11月23日 星期一

configure bridge


最近在玩kvm/qemu,因為要設定自己的網路,所以就順便寫一下紀錄。
網路設定在Linux上都是使用/etc/network/interfaces,這邊大概沒有什麼網路設定不能設的了。 我的設定是要把TUN/TAP(tap0)加入bridge(br0)中,並且設定br0的IP,如此簡單而已,我的/etc/network/interfaces設定如下:
auto br0
iface br0 inet static
auto br0
iface br0 inet static
    pre-up brctl addbr br0
    pre-up tunctl -b -u brook -t tap0
    pre-up brctl addif br0 tap0
    pre-up ifconfig tap0 up
    post-dwon brctl delif br0 tap0
    post-down tunctl -d tap0
    post-down brctl delbr br0
    address 192.168.12.1
    netmask 255.255.255.0
    bridge_port qtap0
    bridge_fd 9
    bridge_hello 2
    bridge_maxage 12
    bridge_stp off
iface br0 inet static是說br0是static IP。
pre-up 是說在up該interface之前,先執行
post-down 是說在down該interface之後,執行
address / netmask 是設定IP資訊。
bridge_xxx 是設定bridge參數。

這樣每次開機後,就會產生tap0並且把他加入br0,以及設定好br0。



2009年11月22日 星期日

好態度能改變一切


每天都可以看到一個公益廣告,"這題你不是練好幾遍 笨得喔",換個方式說"你不笨 是這題得練好幾遍喔",其實很多人需要鼓勵,讓他能在鼓勵中成長茁壯,真得是時代在變,回想我小時候,父母用的都是打罵教育,我們也沒因此倒地不起,而且常常被教育是要越挫越勇,就像七龍珠裡面的孫悟空。
不過幾天前,某人告訴我,打罵也是教育,鼓勵也是教育,何不讓小孩在快樂的環境中長大,其實我也認同,讓小孩有正面的態度,其實並不需要打罵。


Linux Kernel(1)- Linux Module簡介


Linux module練習手札I紀錄如何撰寫一個簡單的module,並且編輯它,以及load和unload一個module。

write a module

#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("GPL");

static int __init init_modules(void)
{
    printk("hello world\n");
    return 0;
}

static void __exit exit_modules(void)
{
    printk("goodbye\n");
}

module_init(init_modules);
module_exit(exit_modules);
<linux/init.h>和#include <linux/module.h>是Linux 任何的module都會用到的header file,init.h主要定義module的init和cleanup,如module_init()和module_exit()。而module.h定義了module所需要的資料結構與macro。
對於__init的解釋在init.h有非常好的解釋:
The kernel can take this as hint that the function is used only during the initialization phase and free up used memory resources after.
簡單的說就是這個function在初始化後(執行完)就被free了。

而__exit的解釋是:
__exit is used to declare a function which is only required on exit: the function will be dropped if this file is not compiled as a module.

module_init()的解釋是:
The module_init() macro defines which function is to be called at module insertion time (if the file is compiled as a module), or at boot time: if the file is not compiled as a module the module_init() macro becomes equivalent to __initcall(), which through linker magic ensures that the function is called on boot.
主要是用來設定當insert該module後,應該要被執行的進入點(enrty point)。

module_exit()的解釋是:
This macro defines the function to be called at module removal time (or never, in the case of the file compiled into the kernel). It will only be called if the module usage count has reached zero. This function can also sleep, but cannot fail: everything must be cleaned up by the time it returns.
Note that this macro is optional: if it is not present, your module will not be removable (except for 'rmmod -f').

簡言之,就是當user執行rmmod時,會被執行到的function。沒有module_exit(),module就不能被rmmod。


write a Makefile to manage the module

mname := brook_modules
$(mname)-objs := main.o
obj-m := $(mname).o

KERNELDIR := /lib/modules/`uname -r`/build

all:
        $(MAKE) -C $(KERNELDIR) M=`pwd` modules

clean:
        $(MAKE) -C $(KERNELDIR) M=`pwd` clean
$(mname)-objs是告訴make這個module有哪些object files。
obj-m是告訴make這個module的name是什麼。
KERNELDIR是告訴make這個module的kernel所在的位置。
後面就接兩個target(all/clean),用於處理產生和清除module用。


load/unload a module




2009年11月20日 星期五

衝吧ubuntu


由於現在的CPU都有動態調整CPU頻率的能力,所以,一般都會在比較低的頻率上執行,在ubuntu 9.10上,預設是ondemand,所以平常都是以最低的頻率執行,有需要的時候才會逐漸調高頻率執行,有哪些參數可以下?請參考:
brook@ubuntu:~$ more /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors 
conservative ondemand userspace powersave performance 

您可以在您的/etc/init.d底下發現一個名為ondemand的檔案,將內文中的"ondemand"改為您想要執行的governor即可,我是效能愛好者,所以,我當然是"performance"嚕。如:
#! /bin/sh
### BEGIN INIT INFO
# Provides:          ondemand
# Required-Start:    $remote_fs $all
# Required-Stop:
# Default-Start:     2 3 4 5
# Default-Stop:
# Short-Description: Set the CPU Frequency Scaling governor to "ondemand"
### END INIT INFO


PATH=/sbin:/usr/sbin:/bin:/usr/bin

. /lib/init/vars.sh
. /lib/lsb/init-functions

case "$1" in
    start)
        start-stop-daemon --start --background --exec /etc/init.d/ondemand -- background
        ;;
    background)
        sleep 60 # probably enough time for desktop login

        for CPUFREQ in /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
        do
                [ -f $CPUFREQ ] || continue
                echo -n performance > $CPUFREQ
        done
        ;;
    restart|reload|force-reload)
        echo "Error: argument '$1' not supported" >&2
        exit 3
        ;;
    stop)
        ;;
    *)
        echo "Usage: $0 start|stop" >&2
        exit 3
        ;;
esac



2009年11月14日 星期六

coverity初體驗


最近在玩coverity,安裝設定上看看手冊大家應該都不成問題,不過在設定green hill可能會遇到小小的問題,就是coverity用__ghs,而一般的code則使用__ghs__,所以要小改一下。
再來就是幫忙review code的看法,以及新手常犯的錯誤,拿出來一起討論一下吧:

Return Address Of Local Variable

由於local variable的address存在stack之中,當程式結束後stack就會被回收,於是存取這塊記憶體時,就會變成invalid access。
錯誤的Example
#define SIZE    10
char* test()
{
    char s[SIZE];
    strcpy(s, "hello");
    return s;
}
幸運的是,GCC通常會出現"warning: function returns address of local variable"提醒Programmer。
基本上,可以使用malloc或者由外面傳進來:
Example
char* test()
{
    char *s;
    s = (char *) malloc(sizeof(char) * SIZE);
    strcpy(s, "hello");
    return s;
}
或者
char* test(char *s, int len)
{
    strncpy(s, "hello", len);
    return s;
}
個人比較偏愛由外面傳進來,再由外面的函數負責free resource。



真的理解C嗎?


昨天忽然搜尋到真的理解 C 語言的 types 嗎?後,自己在拜讀一下C99的standard,才發現自己真的對C感到很陌生,忽然對文中的"20年的工作經驗不過是一年的經驗,重複了20年"感觸頗深。
就以自己的工作經驗而言,常常發現許多工作好幾年的人與其年資完全不符,似乎是進來的沒多,而抱怨著,寫code不會賺錢,先不論寫code會不會賺錢,其實,自己的年資已經和累積的經驗已經有差距了,如何讓工作不再變成只是重複性的工作,我想這是任何一個programmer應該思考的議題。




vmware的timekeeping問題


您是不是常常被vmware抱怨CPU速度和偵測到的速度不符合,這是由於x86的power-saving讓CPU降頻執行所導致,解決的方法就是告知vmware,CPU的正確頻率是多少,找到設定檔config.ini,然後設定CPU頻率:
  • Windows 2000 or XP - %AllUsersProfile%\Application Data\VMware\<VMware-Product>\config.ini
  • Windows Vista or Windows 7 - C:\ProgramData\VMware\VMware\<VMware-Product>\config.ini

不存在的話就自行建立新檔,並且將以下字串貼入其中:
host.cpukHz = "2000000"
      host.noTSC = "TRUE"
      ptsc.noTSC = "TRUE"

"2000000"是我的電腦速度2G,請依照您的頻率調整。 參考資料:
http://kb.vmware.com/selfservice/microsites/search.do?language=en_US&cmd=displayKC&externalId=1227



2009年11月12日 星期四

atftp on ubuntu


在ubuntu上安裝atftp當然就是一個指令搞定,不過有些東西要小改一下。
apt-get install atftpd

首先,不要直接修改/etc/init.d/atftpd了,改/etc/default/atftpd即可。
USE_INETD=false
OPTIONS="--daemon --tftpd-timeout 300 --retry-timeout 5 --mcast-port 1758 --mcast-addr 239.239.239.0-255 --mcast-ttl 1 --maxthread 100 --verbose=5 /var/lib/tftpboot"
沒有加--daemon會出現以下錯誤。
如果出現
atftpd: can't bind port :69/udp

就把/etc/inetd.conf的tftp註解掉。



2009年11月9日 星期一

廢了三天的wireless


三天前,我在我的CQ45 101TX上裝ubuntu 9.10 (karmic),wireless都有訊號但卻是都無法正常work,最後還是換了一張USB的wireless,立刻正常work,看來broadcom的driver似乎有些問題,真是廢了三天。



ubuntu上好用的MSN軟體 -- emesene


因為好用,所以值得推薦,emesene使用上給我的感覺和MSN真的差異不大,有機會試試看吧。




2009年11月5日 星期四

Connectting Windows Desktop on Linux


在ubuntu上有個rdesktop的套件,可以直接連上windows的遠端桌面 --- rdesktop,安裝後執行rdesktop ip即可連上windows的遠端桌面。
sudo apt-get install rdesktop
rdesktop 172.23.19.243

2009年11月3日 星期二

C macro的誤用


macro主要是拿來取代,比如#define MAX_NUM 10,當用到MAX_NUM時,就會被轉成10,我們常用拿macro來define一些函數,比如:
#include <stdio.h>
#define MAX(X, Y) ((X) > (Y) ? (X) : (Y))

int main(int argc, char *argv[])
{
    int x=10, y=11, big;
    big = MAX(++x, ++y);
    printf("the bigger + 1 = %d\n", big);

    return 0;
}

可是這裡就會發生問題了,當你使用CPP你可以看到,X全部被++x取代,而Y全部被++y取代,所以執行完的結果顯然就會錯誤了。
int main(int argc, char *argv[])
{
    int x=10, y=11, big;
    big = ((++x) > (++y) ? (++x) : (++y));
    printf("the bigger + 1 = %d\n", big);

    return 0;
}
結果是13而不是12。

這邊應該改寫為
#include <stdio.h>
#define MAX(x, y) \
({ typeof(x) _x = (x); \
    typeof(y) _y = (y); \
 _x > _y ? _x : _y; })

int main(int argc, char *argv[])
{
    int x=10, y=11, big;
    big = MAX(++x, ++y);
    printf("the bigger + 1 = %d\n", big);

    return 0;
}
因為你無法預期使用者會輸入何種表示法,為了避免side effect,還是在內部宣告一個變數儲存使用者的輸入吧。
至於typeof可以參考一下GCC的說明http://gcc.gnu.org/onlinedocs/gcc/Typeof.html。