C语言内核等待队列机制介绍

2008-02-23 05:06:26来源:互联网 阅读 ()

新老客户大回馈,云服务器低至5折


在此,我们都只是回传 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 才行。

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

clean:
rm -f *.o *~ $(P)

加入上面这个 Makefile,打入 make 之后,就会产生一个 buf.o 的档案。利用 insmod 将 buf.o 载到 kernel 里。相信大家应该都用过 /dev/zero 这个 device。去读取这个 device,只会得到空的内容。写资料到这个 device 里也只会石沈大海。现在您能够去比较 buf 和 zero 这两个 device。两者的行为应该很类似才是。

第三步,我们在第二步中 implement 一个像 zero 的 device driver。我们现在要经由修改他来使用 wait_queue。首先,我们先加入一个 global variable,write_wq,并把他设为 NULL。

struct wait_queue *write_wq = NULL;

然后,在 buf_read() 里,我们要改写成这个样子。

static ssize_t buf_read( struct file *filp,char *buf,size_t count,
loff_t *ppos )
{
int num,nRead;
nRead = 0;
while ( ( wp == rp ) && !flag ) { /* buffer is empty */
return 0;
}

repeate_reading:
if ( rp < wp ) {
num = min( count,( int ) ( wp-rp ) );
}
else {
num = min( count,( int ) ( buffer BUF_LEN-rp ) );
}
copy_to_user( buf,rp,num );
rp = num;
count -= num;
nRead = num;
if ( rp == ( buffer BUF_LEN ) )
rp = buffer;
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;
}
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 出来.

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇: C语言创建自己的设备(一个最最简单的例子)

下一篇: Linux下的多进程编程初步