会计考友 发表于 2012-8-4 12:07:07

Linux系统管理:内核等待队列机制介绍2

flag = 0;
   
    wp = rp = buf;
   
    result = register_chrdev( 54,"buf",&buf_fops );
   
    if ( result < 0 ) {
   
    printk( "buf: cannot get major 54 " );
   
    return result;
   
    }
   
    return 0;
   
    init_buf() 做的事就是去注册一个 character device driver.在注册一个 character device driver 之前,必须要先准备一个型别为 file_operations 结构的变量,file_operations 里包含了一些 function pointer.driver 的作者必须自己写这些 function.并将 function address 放到这个结构里。如此一来,当 user 去读取这个 device 时,kernel 才有办法去呼叫对应这个 driver 的 function.其实,简要来讲。character device driver 就是这么一个 file_operations 结构的变量。file_operations 定义在 这个档案里。它的 prototype 在 kernel 2.2.1 与以前的版本有些微的差异,这点是需要注意的地方。
   
    register_chrdev() 看名字就大概知道是要注册 character device driver.第一个参数是此 device 的 major number.第二个是它的名字。名字你可以随便取。第三个的参数就是一个 file_operations 变量的地址。init_module() 必须要传回 0,module 才会被加载。
   
    在 cleanup_module() 的部分,我们也是只呼叫 buf_clean() 而已。它做的事是 unregister 的动作。
   
    if ( unregister_chrdev( 54,"buf" ) ) {
   
    printk( "buf: unregister_chrdev error " );
   
    }
   
    也就是将原本记录在 device driver table 上的资料洗掉。第一个参数是 major number.第二个则是此 driver 的名称,这个名字必须要跟 register_chrdev() 中所给的名字一样才行。
   
    现在我们来看看此 driver 所提供的 file_operations 是那些。
   
    static struct file_operations buf_fops = {
   
    NULL, /* lseek */
   
    buf_read,
   
    buf_write,
   
    NULL, /* readdir */
   
    NULL, /* poll */
   
    NULL, /* ioctl */
   
    NULL, /* mmap */
   
    buf_open, /* open */
   
    NULL, /* flush */
   
    buf_release, /* release */
   
    NULL, /* fsync */
   
    NULL, /* fasync */
   
    NULL, /* check_media_change */
   
    NULL, /* revalidate */
   
    NULL /* lock */
   
    };

会计考友 发表于 2012-8-4 12:07:08

Linux系统管理:内核等待队列机制介绍2

在此,我们只打算 implement buf_read(),buf_write(),buf_open,和 buf_release()等 function 而已。当 user 对这个 device 呼叫 open() 的时候,buf_open() 会在最后被 kernel 呼叫。相同的,当呼叫 close(),read(),和 write() 时,buf_release(),buf_read(),和 buf_write() 也都会分别被呼叫。首先,我们先来看看 buf_open()。
   
    static int buf_open( struct inode *inode,struct file *filp )
   
    MOD_INC_USE_COUNT;
   
    return 0;
   
    }
   
    buf_open() 做的事很简单。就是将此 module 的 use count 加一。这是为了避免当此 module 正被使用时不会被从 kernel 移除掉。相对应的,在 buf_release() 中,我们应该要将 use count 减一。就像开启档案一样。有 open(),就应该要有对应的 close() 才行。如果 module 的 use count 在不为 0 的话,那此 module 就无法从 kernel 中移除了。
   
    static int buf_release( struct inode *inode,struct file *filp )
   
    {
   
    MOD_DEC_USE_COUNT;
   
    return 0;
   
    }
   
    接下来,我们要看一下buf_read()和buf_write()。
   
    static ssize_t buf_read( struct file *filp,char *buf,size_t count,
   
    loff_t *ppos )
   
    {
   
    return count;
   
    }
   
    static ssize_t buf_write( struct file *filp,const char *buf,
   
    size_t count,loff_t *ppos )
   
    {
   
    return count;
   
    }
   
    在此,我们都只是回传 user 要求读取或写入的字符数目而已。在此,我要说明一下这些参数的意义。filp 是一个 file 结构的 pointer.也就是指我们在 /dev 下所产生的 buf 档案的 file 结构。当我们呼叫 read() 或 write() 时,必须要给一个 buffer 以及要读写的长度。Buf 指的就是这个 buffer,而 count 指的就是长度。至于 ppos 是表示目前这个档案的 offset 在那里。这个值对普通档案是有用的。也就是跟 lseek() 有关系。由于在这里是一个 drvice.所以 ppos 在此并不会用到。有一点要小心的是,上面参数 buf 是一个地址,而且还是一个 user space 的地址,当 kernel 呼叫 buf_read() 时,程序在位于 kernel space.所以你不能直接读写资料到 buf 里。必须先切换 FS 这个 register 才行。

会计考友 发表于 2012-8-4 12:07:09

Linux系统管理:内核等待队列机制介绍2

Makefile
   
    P = buf
   
    OBJ = buf.o
   
    INCLUDE = -I/usr/src/linux/include/linux
   
    CFLAGS = -D__KERNEL__ -DMODVERSIONS -DEXPORT_SYMTAB -O $(INCLUDE)
   
    -include /usr/src/linux/include/linux/modversions.h
   
    CC = gcc
   
    $(P): $(OBJ)
   
    ld -r $(OBJ) -o $(P)。o
   
    .c.o:
   
    $(CC) -c $(CFLAGS) $

会计考友 发表于 2012-8-4 12:07:10

Linux系统管理:内核等待队列机制介绍2

if ( ( rp != wp ) && ( count > 0 ) )
   
    goto repeate_reading;
   
    flag = 0;
   
    wake_up_interruptible( &write_wq );
   
    return nRead;
   
    }
   
    在前头我有提到,buf 的地址是属于 user space 的。在 kernel space 中,你不能像普通写到 buffer 里一样直接将资料写到 buf 里,或直接从 buf 里读资料。Linux 里使用 FS 这个 register 来当作 kernel space 和 user space 的切换。所以,如果你想手动的话,可以这样做:
   
    mm_segment_t fs;
   
    fs = get_fs();
   
    set_fs( USER_DS );
   
    write_data_to_buf( buf );
   
    set_fs( fs );
   
    也就是先切换到 user space,再写资料到 buf 里。之后记得要切换回来 kernel space.这种自己动手的方法比较麻烦,所以 Linux 提供了几个 function,可以让我们直接在不同的 space 之间做资料的搬移。诚如各位所见,copy_to_user() 就是其中一个。
   
    copy_to_user( to,from,n );
   
    copy_from_user( to,from,n );
   
    顾名思义,copy_to_user() 就是将资料 copy 到 user space 的 buffer 里,也就是从 to 写到 from,n 为要 copy 的 byte 数。相同的,copy_from_user() 就是将资料从 user space 的 from copy 到位于 kernel 的 to 里,长度是 n bytes.在以前的 kernel 里,这两个 function 的前身是 memcpy_tofs() 和 memcpy_fromfs(),不知道为什么到了 kernel 2.2.1之后,名字就被改掉了。至于它们的程序代码有没有更改就不太清楚了。至于到那一版才改的。我没有仔细去查,只知道在 2.0.36 时还没改,到了 2.2.1 就改了。这两个 function 是 macro,都定义在 里。要使用前记得先 include 进来。
   
    相信 buf_read() 的程序代码应当不难了解才对。不知道各位有没有看到,在buf_read() 的后面有一行的程序,就是
   
    wake_up_interruptible( &write_wq );
   
    write_wq 是我们用来放那些想要写资料到 buffer,但 buffer 已满的 process.这一行的程序会将挂在此 queue 上的 process 叫醒。当 queue 是空的时,也就是当 write_wq 为 NULL 时,wake_up_interruptible() 并不会造成任何的错误。接下来,我们来看看更改后的 buf_write()。
   
    static ssize_t buf_write( struct file *filp,const char *buf,size_t count,loff_t *ppos )
   
    {
   
    int num,nWrite;
   
    nWrite = 0;
   
    while ( ( wp == rp ) && flag ) {
   
    interruptible_sleep_on( &write_wq );
   
    }
   
    repeate_writing:
   
    if ( rp > wp ) {
   
    num = min( count,( int ) ( rp - wp ) );
   
    }
   
    else {
   
    num = min( count,( int ) ( buffer + BUF_LEN - wp ) );
   
    }
   
    copy_from_user( wp,buf,num );
   
    wp += num;
   
    count -= num;
   
    nWrite += num;
   
    if ( wp == ( buffer + BUF_LEN ) ) {
   
    wp = buffer;

会计考友 发表于 2012-8-4 12:07:11

Linux系统管理:内核等待队列机制介绍2

}
   
    if ( ( wp != rp ) && ( count > 0 ) ) {
   
    goto repeate_writing;
   
    }
   
    flag = 1;
   
    return nWrite;
   
    }
   
    我们把 process 丢到 write_wq 的动作放在 buf_write() 里。当 buffer 已满时,就直接将 process 丢到 write_wq 里。
   
    while ( ( wp == rp ) && flag ) {
   
    interruptible_sleep_on( &write_wq );
   
    }
   
    好了。现在程序已经做了一些修改。再重新 make 一次,利用 insmod 将 buf.o 载到 kernel 里就行了。接着,我们就来试验一下是不是真正做到 block IO.
   
    # cd /dev
   
    # ls -l ~/WWW-HOWTO
   
    -rw-r--r-- 1 root root 23910 Apr 14 16:50 /root/WWW-HOWTO
   
    # cat ~/WWW-HOWTO > buf
   
    执行到这里,应该会被 block 住。现在,我们再开一个 shell 出来。
   
    # cd /dev
   
    # cat buf
   
    …( contents of WWW-HOWTO ) …skip …
   
    此时,WWW-HOWTO 的内容就会出现了。而且之前 block 住的 shell 也已经回来了。最后,试验结束,可以下
   
    # rmmod buf
   
    将 buf 这个 module 从 kernel 中移除。以上跟各位介绍的就是 wait_queue 的使用。希望能对各位有所助益。
   
    我想对某些人来讲,会使用一个东西就够了。然而对某些人来讲,可能也很希望知道这项东西是如何做出来的。至少我就是这种人。在下面,我将为各位介绍 wait_queue 的 implementation.如果对其 implementation 没兴趣,以下这一段就可以略过不用看了。
页: [1]
查看完整版本: Linux系统管理:内核等待队列机制介绍2