2009年12月27日 星期日

Linux Modules(7.2)- tasklet


Tasklet和timer類似(基本上都是運作在Softirqs上面),但是不同於timer會在特定時間執行,tasklet會在下一次interrupt來臨時執行。Tasklet有兩種implement,分別為TASKLET_SOFTIRQ和HI_SOFTIRQ,這兩種的差別在於HI_SOFTIRQ筆TASKLET_SOFTIRQ早執行。另外Tasklet只在註冊的CPU上面執行,而且註冊的tasklet同一時間只會被某個CPU執行。

您可以dynamically或statically的建立tasklet,
DECLARE_TASKLET(task, func, data);
DECLARE_TASKLET_DISABLED(task, func, data);

tasklet_init(task, func, data);

宣告後,還必須呼叫tasklet_schedule(task)才會被執行,但如果是用
DECLARE_TASKLET_DISABLED()宣告成disabled狀態,那就還必須用tasklet_enable()將其狀態設成enabled才能被執行。您也可以透過tasklet_disabled() disabled某個tasklet。tasklet_kill()可以保證tasklet不會被schedule,如果已經在執行,就會等它執行結束。

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/slab.h>

MODULE_LICENSE("GPL");

static void f(unsigned long name);

// create tasklet statically
static DECLARE_TASKLET(t1, f, (unsigned long)"t1");
static DECLARE_TASKLET_DISABLED(t2, f, (unsigned long)"t2");

static struct tasklet_struct *t3;

static void f(unsigned long name)
{
    printk("%s(): on cpu %d\n", (char*)name, smp_processor_id());
}

static void f3(unsigned long name)
{
    static u32 c = 0;
    tasklet_schedule(t3);
    if (!(c++ % 2000000)) { // 每隔2000000次呼叫就印出訊息
        printk("%s(): on cpu %d\n", (char*)name, smp_processor_id());
    }
}

static int __init init_modules(void)
{
    // create tasklet dynamically
    t3 = kzalloc(sizeof(struct tasklet_struct), GFP_KERNEL);
    tasklet_init(t3, f3, (unsigned long)"t3");

    tasklet_schedule(&t1);
    tasklet_schedule(&t2);
    tasklet_schedule(t3);
    tasklet_enable(&t2); // 沒有enable就不會被啟動
    return 0;
}

static void __exit exit_modules(void)
{
    // remove module就應該要確保tasklet有被移除
    tasklet_kill(&t1);
    tasklet_kill(&t2);
    tasklet_kill(t3);
}

module_init(init_modules);
module_exit(exit_modules);


Based on Kernel Version:2.6.35

參考資料:
Linux Kernel Development 3rd.
Linux Device Driver 3rd, http://www.makelinux.net/ldd3/chp-7-sect-5.shtml



Linux Kernel(7.1)- timer


有時候我們希望能在某個時間點執行某些動作,這時候便可以使用timer,在使用timer有些規矩必須被遵守。因為不是user-space來喚起,所以不允許存取user-space,current也就沒有意義。不能休眠,也不准schedule()或者任何有可能休眠的動作都不准。
struct timer_list {
 struct list_head entry;
 unsigned long expires;

 void (*function)(unsigned long);
 unsigned long data;

 struct tvec_base *base;
#ifdef CONFIG_TIMER_STATS
 void *start_site;
 char start_comm[16];
 int start_pid;
#endif
#ifdef CONFIG_LOCKDEP
 struct lockdep_map lockdep_map;
#endif
};

timer_list必須初始化之後才能使用,您可以選擇init_timer()或TIMER_INITIALIZER(),接著就可以設定expires/callback function/data(參數),並且使用add_timer()將其加入timer中,或者使用del_timer()移除pending中的timer,也可以使用mod_timer()修改或者重新設定timer。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>

MODULE_LICENSE("GPL");

struct timer_list brook_timer;
static void callback(unsigned long);
struct data {
    int count;
};
static struct data data;

static void callback(unsigned long data)
{
    struct data *dp = (struct data*) data;
    printk("%s(): %d\n", __FUNCTION__, dp->count++);
    mod_timer(&brook_timer, jiffies + 5 * HZ);
}

static int __init init_modules(void)
{
    init_timer(&brook_timer);
    brook_timer.expires = jiffies + 5 * HZ;
    brook_timer.function = &callback;
    brook_timer.data = (unsigned long) &data;
    add_timer(&brook_timer);
    return 0;
}

static void __exit exit_modules(void)
{
    del_timer(&brook_timer);
}

module_init(init_modules);
module_exit(exit_modules);


kernel timer最短的間隔是1個jiffies,而且會受到硬體中斷,和其他非同步事件的干擾,所以不適合非常精密的應用。

Linux Kernel(7)- timing


kernel會定期產生timer interrupt,HZ定義每秒產生timer interrupt的次數,定義在linux/param.h,根據平台的不同從50~1200不等。
而jiffies每當發生一次timer interrupt就會遞增一次,jiffies定義於linux/jiffies.h,所以簡單的說,jiffies就等於1/HZ,不管在64bit或32bit上的機器,Linux kernel都使用64位元版的jiffies_64,而jiffies其實是jiffies_64的低32位元版,除了讀取外,我們都不應該直接修改jiffies/jiffies_64。
kernel提供幾組macro來比較時間的先後,time_after()/timer_before()/time_after_eq()/time_before_eq()。
/*
 * These inlines deal with timer wrapping correctly. You are 
 * strongly encouraged to use them
 * 1. Because people otherwise forget
 * 2. Because if the timer wrap changes in future you won't have to
 *    alter your driver code.
 *
 * time_after(a,b) returns true if the time a is after time b.
 *
 * Do this with "<0" and ">=0" to only test the sign of the result. A
 * good compiler would generate better code (and a really good compiler
 * wouldn't care). Gcc is currently neither.
 */
#define time_after(a,b)  \
 (typecheck(unsigned long, a) && \
  typecheck(unsigned long, b) && \
  ((long)(b) - (long)(a) < 0))
#define time_before(a,b) time_after(b,a)

#define time_after_eq(a,b) \
 (typecheck(unsigned long, a) && \
  typecheck(unsigned long, b) && \
  ((long)(a) - (long)(b) >= 0))
#define time_before_eq(a,b) time_after_eq(b,a)

另外,kernel中有兩種時間的structure,struct timeval和struct timespec。
#ifndef _STRUCT_TIMESPEC
#define _STRUCT_TIMESPEC
struct timespec {
 __kernel_time_t tv_sec;   /* seconds */
 long  tv_nsec;  /* nanoseconds */
};
#endif

struct timeval {
 __kernel_time_t  tv_sec;  /* seconds */
 __kernel_suseconds_t tv_usec; /* microseconds */
};
早期以timeval為主,後來因為精密度的需求,有了timespec的誕生。kernel也提供了和jiffies的轉換函數。更多的轉換可以參考linux/jiffies.h
unsigned long timespec_to_jiffies(const struct timespec *value);
void jiffies_to_timespec(const unsigned long jiffies,
    struct timespec *value);
unsigned long timeval_to_jiffies(const struct timeval *value);
void jiffies_to_timeval(const unsigned long jiffies,
          struct timeval *value);


Linux Kernel(3.1)- procfs之vector方式寫入


相信很多人有讀寫過/proc/sys/kernel/printk來控制printk的level,於是乎我就仿照了kernel/sysctl.c的do_proc_dointvec()寫了一個這樣的code,我的write_proc_t就是在做do_proc_dointvec()當中的write。kernel因為沒有豐富的library,所以作這樣的事情得小繞一下。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/ctype.h>

MODULE_LICENSE("GPL");

static int int_vec[] = {1, 1, 1};

static int write_proc(struct file *file, const char __user *buf,
                       unsigned long count, void *data)
{
    int *i, vleft, neg, left = count;
    char __user *s = buf;
    char tmpbuf[128], *p;
    size_t len;
    unsigned long ulval;

    i = int_vec;
    vleft = sizeof(int_vec)/sizeof(int_vec[0]);

    for(;left && vleft--; i++) {
        while(left) {
            char c;
            if (get_user(c, s)) {
                return -EFAULT;
            }
            if (!isspace(c)) {
                break;
            }
            left--;
            s++;
        }
        if (!left) {
            break;
        }
        neg = 0;
        len = left;
        if (len > sizeof(tmpbuf) - 1) {
            len = sizeof(tmpbuf) - 1;
        }
        if (copy_from_user(tmpbuf, s, len)) {
            return -EFAULT;
        }
        tmpbuf[len] = 0;
        p = tmpbuf;
        if (*p == '-' && left > 1) {
            neg = 1;
            p++;
        }
        if (*p < '0' || *p > '9') {
            break;
        }
        ulval = simple_strtoul(p, &p, 0);
        len = p - tmpbuf;
        if ((len < left) && *p && !isspace(*p)) {
            break;
        }
        *i = neg ? -ulval : ulval;
        s += len;
        left -= len;
    }
    return count;
}

static int read_proc(char *page, char **start, off_t off,
                       int count, int *eof, void *data)
{
    int *i, vleft;
    char *p;

    i = (int *) int_vec;
    vleft = sizeof(int_vec)/sizeof(int_vec[0]);

    for (p = page, i = int_vec; vleft--; i++) {
        p += sprintf(p, "%d\t", *i);
    }
    *(p++) = '\n';
    *eof = 1;
    return (p - page);
}

static int __init init_modules(void)
{

    struct proc_dir_entry *ent;

    ent = create_proc_entry("brook_vec", S_IFREG | S_IRWXU, NULL);
    if (!ent) {
        printk("create proc child failed\n");
    } else {
        ent->write_proc = write_proc;
        ent->read_proc = read_proc;
    }
    return 0;
}

static void __exit exit_modules(void)
{
    remove_proc_entry("brook_vec", NULL);
}

module_init(init_modules);
module_exit(exit_modules);



2009年12月26日 星期六

kernel探索之linux/typecheck.h


#ifndef TYPECHECK_H_INCLUDED
#define TYPECHECK_H_INCLUDED

/*
 * Check at compile time that something is of a particular type.
 * Always evaluates to 1 so you may use it easily in comparisons.
 */
#define typecheck(type,x) \
({ type __dummy; \
 typeof(x) __dummy2; \
 (void)(&__dummy == &__dummy2); \
 1; \
})

/*
 * Check at compile time that 'function' is a certain type, or is a pointer
 * to that type (needs to use typedef for the function type.)
 */
#define typecheck_fn(type,function) \
({ typeof(type) __tmp = function; \
 (void)__tmp; \
})

#endif  /* TYPECHECK_H_INCLUDED */

typecheck(type, x)用於檢查x是不是"type"的資料型態,如果不是,compiler會出現warning: comparison of distinct pointer types lacks a cast提醒programmer。

typecheck_fn(type, function)用於檢查"function"是不是和"type"有相同的資料型態,如果不是,compiler會出現warning: initialization from incompatible pointer type提醒programmer。


2009年12月24日 星期四

Linux Kernel(6)- miscdev


有時候我們需要寫一些"小的驅動程式",而早期的UNIX/Linux需要註冊major/minor number,即便可能只需要1個minor number,往往卻佔住major number底下的所有minor number,於是在Linux 2.0有了miscellaneous character drivers的誕生,misc driver使用major number 10,然後使用者如果需要這樣的"小驅動程式",便可以指明minor number即可。

使用misc device必須include <linux/miscdevice.h>,裡面包含了許多的官方的minor number,您可以挑選您適合的minor number,裡面也包含了兩個API,misc_register()/misc_deregister()。
一般您只要填好struct miscdevice的內容,再使用misc_register()進行註冊,或者使用misc_deregister()進行移除即可。
struct miscdevice  {
 int minor;
 const char *name;
 const struct file_operations *fops;
 struct list_head list;
 struct device *parent;
 struct device *this_device;
 const char *devnode;
};
 minor:就是您要註冊的minor number。
 name:device的name。
 fops:file operations。
 其他的就不用理會了。

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

#define DEV_BUFSIZE 1024

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

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

static struct miscdevice brook_miscdev = {
    .minor      = 11,
    .name       = "brook_dev",
    .fops       = &dev_fops,
};

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

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

static ssize_t
dev_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    printk("%s():\n", __FUNCTION__);
    *pos = 0;
    return 0;
}

static ssize_t
dev_write(struct file *filp, const char __user *buf,
        size_t count, loff_t *pos)
{
    printk("%s():\n", __FUNCTION__);
    return count;
}

static int __init init_modules(void)
{
    int ret;

    ret = misc_register(&brook_miscdev);
    if (ret != 0) {
        printk("cannot register miscdev on minor=11 (err=%d)\n",ret);
    }

    return 0;
}

static void __exit exit_modules(void)
{
    misc_deregister(&brook_miscdev);
}

module_init(init_modules);
module_exit(exit_modules);



2009年12月23日 星期三

vbindiff - Visual Binary Diff


VBinDiff是一套視窗化的二進位diff工具,可以將檔案內容以16和ASCII方式顯示(和早期的PCTOOL感覺很像)。


官方網站:
http://www.cjmweb.net/vbindiff/


2009年12月20日 星期日

Linux Kernel(4.2)- seq_file之single page


對於只有一頁的輸出,seq_file中的sart()/next()/stop()就顯得多餘,通常指需要一個show(),所以,seq_file也提供單頁的版本single_open(),以下範例為fs/proc/cmdline.c:
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int cmdline_proc_show(struct seq_file *m, void *v)
{
 seq_printf(m, "%s\n", saved_command_line);
 return 0;
}

static int cmdline_proc_open(struct inode *inode, struct file *file)
{
 return single_open(file, cmdline_proc_show, NULL);
}

static const struct file_operations cmdline_proc_fops = {
 .open  = cmdline_proc_open,
 .read  = seq_read,
 .llseek  = seq_lseek,
 .release = single_release,
};

static int __init proc_cmdline_init(void)
{
 proc_create("cmdline", 0, NULL, &cmdline_proc_fops);
 return 0;
}
module_init(proc_cmdline_init);
和seq_file的不同在於,因為只有一頁所以不需要sart()/next()/stop(),就只剩下show(),再來就是要用single_open()取代seq_open()。而relese也是要使用single_release()取代seq_release()。


2009年12月16日 星期三

Linux Kernel(5)- ioctl


(V)將介紹file operations中的ioctl。ioctl的prototype為:
int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
ioctl藉由cmd來判斷後面所接的參數為何,而早期的ioctl號碼並沒有規則,所以很容易重複,後來為了避免重複,採行編碼方式,將cmd拆成幾個部份,包含:
type
  即magic number,可以根據Document/ioctl/ioctl-number.txt挑選一個。
number
  為sequential number或者稱為ordinal number,讓user自行定義,只要自己不重複即可。
direction
  傳輸的方向,不外乎NONOE/READ/WRITE等等。
size
  即參數的size。

因為ioctl藉由cmd來判斷user想要的指令為何,以及後面所帶的參數為何,所以免不了的就會有一個switch/case來判斷,這也算是ioctl的特色吧。
怎麼定義ioctl的command以及如何解譯ioctl的command,我想直接拿ioctl.h來說明。
#define _IOC(dir,type,nr,size) \
         (((dir)  << _IOC_DIRSHIFT) | \
         ((type) << _IOC_TYPESHIFT) | \
         ((nr)   << _IOC_NRSHIFT) | \
         ((size) << _IOC_SIZESHIFT))

/* used to create numbers */
#define _IO(type,nr)            _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)      _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOW(type,nr,size)      _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOWR(type,nr,size)     _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define _IOR_BAD(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

/* used to decode ioctl numbers.. */
#define _IOC_DIR(nr)            (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
#define _IOC_TYPE(nr)           (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
#define _IOC_NR(nr)             (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
#define _IOC_SIZE(nr)           (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
在定義ioctl的command時,我們會根據資料傳輸的方向使用_IO(不需要傳輸資料)/_IOR(讀取)/_IOW(寫入)/_IOWR(讀寫),type是我們挑選的magic number,nr即number,是流水號,size,就是the size of argument,下面是我們的範例brook_ioctl.h:

#ifndef IOC_BROOK_H
#define IOC_BROOK_H

#define BROOK_IOC_MAGIC     'k'
#define BROOK_IOCSETNUM     _IOW(BROOK_IOC_MAGIC,  1, int)
#define BROOK_IOCGETNUM     _IOR(BROOK_IOC_MAGIC,  2, int)
#define BROOK_IOCXNUM       _IOWR(BROOK_IOC_MAGIC, 3, int)
#define BROOK_IOC_MAXNR     3

#endif
這邊定義三個ioctl的command,分別為設定數值(BROOK_IOCSETNUM),取得數值(BROOK_IOCGETNUM)和交換數值(BROOK_IOCXNUM)。

以下是我的module:
#include <linux/init.h>
#include <linux/module.h>

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

#include "ioc_brook.h"

MODULE_LICENSE("GPL");

#define DEV_BUFSIZE         1024


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

static int 
  dev_open(struct inode*, struct file*);
static int 
  dev_release(struct inode*, struct file*);
static int
  dev_ioctl(struct inode*, struct file*, unsigned int, unsigned long);

static void __exit exit_modules(void);

struct file_operations dev_fops = {
    .owner   = THIS_MODULE,
    .open    = dev_open,
    .release = dev_release,
    .ioctl   = dev_ioctl
};

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

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

static int brook_num = 0;
static int 
dev_ioctl(struct inode *inode, struct file *filp,
          unsigned int cmd, unsigned long args)
{
    int tmp, err = 0, ret = 0;

    if (_IOC_TYPE(cmd) != BROOK_IOC_MAGIC)
        return -ENOTTY;
    if (_IOC_NR(cmd) > BROOK_IOC_MAXNR)
        return -ENOTTY;

    if (_IOC_DIR(cmd) & _IOC_READ) {
        err = !access_ok(VERIFY_WRITE, (void __user*)args, _IOC_SIZE(cmd));
    } else if (_IOC_DIR(cmd) & _IOC_WRITE) {
        err = !access_ok(VERIFY_READ, (void __user *)args, _IOC_SIZE(cmd));
    }
    if (err)
        return -EFAULT;

    switch (cmd) {
        case BROOK_IOCSETNUM:
            // don't need call access_ok() again. using __get_user().
            ret = __get_user(brook_num, (int __user *)args); 
            printk("%s(): get val = %d\n", __FUNCTION__, brook_num);
            break;
        case BROOK_IOCGETNUM:
            ret = __put_user(brook_num, (int __user *)args);
            printk("%s(): set val to %d\n", __FUNCTION__, brook_num);
            break;
        case BROOK_IOCXNUM:
            tmp = brook_num;
            ret = __get_user(brook_num, (int __user *)args);
            if (!ret) {
                ret = __put_user(tmp, (int __user *)args);
            }
            printk("%s(): change val from %d to %d\n",
                       __FUNCTION__, tmp, brook_num);
            break;
        default: /* redundant, as cmd was checked against MAXNR */
            return -ENOTTY;
    }
    return 0;
}

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

    ret = alloc_chrdev_region(&dev, 0, 1, "brook");
    if (ret < 0) {
        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 = kmalloc(sizeof(struct cdev), GFP_KERNEL);
    if (dev_cdevp == NULL) {
        printk("kmalloc 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);

在dev_ioctl()先檢視command的type(magic number)和number(sequential number)是否正確,接著在根據command的read/write特性,使用access_ok()檢驗該位址是否合法,後面就是ioctl慣有的switch/case了,根據不同的case執行不同的command和解釋後面所攜帶的參數。

底下是我的application:
#include <stdio.h>
#include <sys/ioctl.h>
#include <sys/types.h>

#include <sys/stat.h>
#include <fcntl.h>

#include "ioc_brook.h"

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

    if (argc < 2) {
        printf("Usage: prog \n");
        return -1;
    }

    fd = open(argv[1], O_RDWR);
    if (fd < 0) {
        printf("open %s failed\n", argv[1]);
        return -1;
    }

    ret = 10;
    if (ioctl(fd, BROOK_IOCSETNUM, &ret) < 0) {
        printf("set num failed\n");
        return -1;
    }

    if (ioctl(fd, BROOK_IOCGETNUM, &ret) < 0) {
        printf("get num failed\n");
        return -1;
    }
    printf("get value = %d\n", ret);

    ret = 100;
    if (ioctl(fd, BROOK_IOCXNUM, &ret) < 0) {
        printf("exchange num failed\n");
        return -1;
    }
    printf("get value = %d\n", ret);

    return 0;
}

在app.c中,將開啟上面註冊的device,並且設定數值(BROOK_IOCSETNUM),讀取數值(BROOK_IOCGETNUM),和交換數值(BROOK_IOCXNUM)。



2009年12月15日 星期二

如何利用kvm/qemu練習linux module


"如何利用kvm/qemu練習linux module"將介紹如何編譯一個bzImage在kvm上面執行,我們將掛上一個initramfs當我們的root filesystem,除了沒有實體的裝置以外,其實可以看成一個embedded linux了。我也是利用這種方式撰寫module的文章,因為module一寫不好,kernel就會crash了,透過vm,就不怕kernel crash了。
編譯kernel
brook@ubuntu:~$ mkdir linux
brook@ubuntu:~$ cd linux/
brook@ubuntu:~/linux$ apt-get source linux-image-2.6.31-16-generic
Reading package lists... Done
Building dependency tree       
Reading state information... Done
NOTICE: 'linux' packaging is maintained in the 'Git' version control system at:
http://kernel.ubuntu.com/git-repos/ubuntu/ubuntu-karmic.git
Need to get 81.0MB of source archives.
Get:1 http://tw.archive.ubuntu.com karmic-updates/main linux 2.6.31-16.53 (dsc) [3,781B]
0% [Waiting for headers]    
Fetched 81.0MB in 3min 29s (387kB/s)
gpgv: Signature made Tue 08 Dec 2009 11:50:10 AM CST using DSA key ID 17063E6D
gpgv: Can't check signature: public key not found
dpkg-source: warning: failed to verify signature on ./linux_2.6.31-16.53.dsc
dpkg-source: info: extracting linux in linux-2.6.31
dpkg-source: info: unpacking linux_2.6.31.orig.tar.gz
dpkg-source: info: applying linux_2.6.31-16.53.diff.gz
brook@ubuntu:~/linux$ cd linux-2.6.31
brook@ubuntu:~/linux/linux-2.6.31$ cp /boot/config-2.6.31-16-generic .config
brook@ubuntu:~/linux/linux-2.6.31$ make oldconfig
brook@ubuntu:~/linux/linux-2.6.31$ make menuconfig

接下來要設定我們的initramfs的目錄,先make menuconfig,接著選"General setup" -> "Initramfs source file(s)",接著輸入目錄"/home/brook/linux/rootfs",注意該選項的上一個選項"Initial RAM ilesystem and RAM disk (initramfs/initrd) support"有被enabled。如果要使用的是initrd則"Initramfs source file(s)"就留空白,使用initrd的好處是,initrd有任何改變都不需要重新compile kernel。

rootfs的基本設定
brook@ubuntu:~/linux$ mkdir rootfs
brook@ubuntu:~/linux$ cd rootfs
brook@ubuntu:~/linux/rootfs$ mkdir dev
brook@ubuntu:~/linux/rootfs$ mkdir tmp
brook@ubuntu:~/linux/rootfs$ mkdir bin
brook@ubuntu:~/linux/rootfs$ mkdir sbin
brook@ubuntu:~/linux/rootfs$ mkdir etc
brook@ubuntu:~/linux/rootfs$ mkdir lib
brook@ubuntu:~/linux/rootfs$ mkdir proc
brook@ubuntu:~/linux/rootfs$ mkdir sys
brook@ubuntu:~/linux/rootfs$ mkdir usr
brook@ubuntu:~/linux/rootfs$ apt-get install busybox-static
brook@ubuntu:~/linux/rootfs$ cp /bin/busybox bin
brook@ubuntu:~/linux/rootfs$ cd bin
brook@ubuntu:~/linux/rootfs/bin$ ln -s busybox -s mkdir
brook@ubuntu:~/linux/rootfs/bin$ ln -s busybox -s mknod
brook@ubuntu:~/linux/rootfs/bin$ ln -s busybox -s mount
brook@ubuntu:~/linux/rootfs/bin$ ln -s busybox -s rm
brook@ubuntu:~/linux/rootfs/bin$ ln -s busybox -s sh
brook@ubuntu:~/linux/rootfs/bin$ cd ../sbin
brook@ubuntu:~/linux/rootfs/sbin$ ln -s ../busybox -s init
brook@ubuntu:~/linux/rootfs/sbin$ ln -s ../busybox -s mdev
brook@ubuntu:~/linux/rootfs/$ find . |cpio -H newc -o > ../initrd

如果使用的是initrd則需要用cpio壓縮這個目錄,如果是initramfs就不用最後一個指令了。

接著當然還要寫一下init script,負責開機後的一些基本設定,其內容如下
#!/bin/sh
#Mount things needed by this script
mount -t tmpfs -o size=64k,mode=0755 tmpfs /dev
mkdir /dev/pts
mount -t devpts devpts /dev/pts
/bin/mount -t proc proc /proc
/bin/mount -t sysfs sysfs /sys

#Create all the symlinks to /bin/busybox
/bin/busybox --install -s

#Create device nodes
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s

exec /sbin/init
最後make bzImage即可。

接著執行kvm/qemu吧
brook@ubuntu:~/linux/linux-2.6.31$ kvm -no-acpi -kernel arch/x86_64/boot/bzImage -net nic,model=pcnet -net tap,ifname=tap0,script=no

如果是initrd則需要多一個參數給kvm/qemu。
brook@ubuntu:~/linux/linux-2.6.31$ kvm -no-acpi -kernel arch/x86_64/boot/bzImage -initrd /home/brook/initrd -net nic,model=pcnet -net tap,ifname=tap0,script=no



2009年12月13日 星期日

Linux Kernel(4.1)- seq_file之範例(fp/proc/devices.c)


(IV .1)是seq_file的實例說明,將Linux中的fp/proc/devices.c拿出來當範例並且予以說明。
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>

static int devinfo_show(struct seq_file *f, void *v)
{
    int i = *(loff_t *) v;

    if (i < CHRDEV_MAJOR_HASH_SIZE) {
        if (i == 0)
            seq_printf(f, "Character devices:\n");
        chrdev_show(f, i);
    }
#ifdef CONFIG_BLOCK
    else {
        i -= CHRDEV_MAJOR_HASH_SIZE;
        if (i == 0)
            seq_printf(f, "\nBlock devices:\n");
        blkdev_show(f, i);
    }
#endif
    return 0;
}


static void *devinfo_start(struct seq_file *f, loff_t *pos)
{
    if (*pos < (BLKDEV_MAJOR_HASH_SIZE + CHRDEV_MAJOR_HASH_SIZE))
        return pos;
    return NULL;
}

static void *devinfo_next(struct seq_file *f, void *v, loff_t *pos)
{
    (*pos)++;
    if (*pos >= (BLKDEV_MAJOR_HASH_SIZE + CHRDEV_MAJOR_HASH_SIZE))
        return NULL;
    return pos;
}

static void devinfo_stop(struct seq_file *f, void *v)
{
    /* Nothing to do */
}

static const struct seq_operations devinfo_ops = {
    .start = devinfo_start,
    .next  = devinfo_next,
    .stop  = devinfo_stop,
    .show  = devinfo_show
};

static int devinfo_open(struct inode *inode, struct file *filp)
{
    return seq_open(filp, &devinfo_ops);
}

static const struct file_operations proc_devinfo_operations = {
    .open  = devinfo_open,
    .read  = seq_read,
    .llseek  = seq_lseek,
    .release = seq_release,
};

static int __init proc_devices_init(void)
{
    proc_create("devices", 0, NULL, &proc_devinfo_operations);
    return 0;
}
module_init(proc_devices_init);
首先,這邊只有module_init(),所以只能載入,不能unload。而載入的點就是create一個proc檔 "devices",並且註冊其file operations "proc_devinfo_operations",在"proc_devinfo_operations"可以發現是一個seq_file的架構,所以我們就會想到start()/next()/stop()/show()等function應該負責的功能。
首先會看到start()即"devinfo_start()",可以看出pos代表的是第幾個device,而pos最大為block+char的總和。
"devinfo_next()"應該負責移動pos,所以可以看出只有做了(*pos)++。
由於"devinfo_start()"並沒有和系統要求任何的resource,所以"devinfo_stop()"就不需要有任何cleanup的動作。
而"devinfo_show()"則是各別呼叫"chrdev_show()"和"blkdev_show()"來顯示char device和block device。



2009年12月6日 星期日

telnet之中文(ubuntu)


今天用ubuntu的"Terminal"上ptt,卻出現亂碼,原來是"Character Encoding"預設是"UTF-8",將其改成"BIG5"就OK啦,post文章中文也不成問題了。




2009年12月5日 星期六

Linux Kernel(4)- seq_file


對於如何維護procfs中的position是有些困擾的(超過一頁的顯示時),所以後來有了seq_file,提供了iterator的interface來讀取資料,最大的好處是,position的定義就可以由programmer自行決定,比如position N是讀取第N行之類的。我們將改寫Linux Modules(III)- procfs的範例一來作為我們seq_file的例子。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/uaccess.h>

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);

首先,我們要實作start(),其主要功能就是回傳一個指向要讀取位置(position)的iterator。在我們的範例中,由於position是由0開始,而我們打算由line 1開始列印。
void * (*start) (struct seq_file *m, loff_t *pos);

接著我們要實作next(),主要功能為移動iterator到下一個位置去。在我們的範例中的iterator是指第N行,所以一到下一個iteraot就是把行數加一。
void * (*next) (struct seq_file *m, void *v, loff_t *pos);

最後要實作stop(),主要功能為當完成讀取後要執行的function,簡單來說就是cleanup。在我們的範例中,就是釋放start()所要求的resource。
void (*stop) (struct seq_file *m, void *v);

顯示的callback function為show(),要注意的是,在顯示資料的function必須使用seq_printf(),參數和printk()一樣,這樣就完成所以的function的實做了,接下來就是和procfs綁在一起了。
int (*show) (struct seq_file *m, void *v);

首先我們必須宣告struct seq_operations,並且填入我們剛剛實作的seq operations。
static struct seq_operations seq_ops = {
    .start = seq_start,
    .next  = seq_next,
    .stop  = seq_stop,
    .show  = seq_show
};

並且呼叫seq_open()將file和seq_operatio綁在一起。
static int proc_open(struct inode *inode, struct file *file)
{
    return seq_open(file, &seq_ops);
}

最後在填入file_operation,由於我們使用的是seq_file,所以,read()/llseek()/release()都只要使用seq_file提供的operations即可。
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
};