Linux Direct Disk Access from User Space

30 Jan 2012

1、linux /dev 目录简介

linux下磁盘块设备都挂接在/dev目录下,这个目录同时包括了所有的外接设备,包括块设备和字符设备,我们可以像访问文件一样很方便的访问这些块设备,linux沿袭了Unix的风格,将所有设备都人称一个文件。

设备文件分为两种:

块设备文件(b)

字符设备文件(c)

设备文件一般存放在/dev目录下

/dev/hd[a-t]: IDE设备,hd代表硬盘的意思,a代表第一个设备,hda1中的“1”代表hda的第一个磁盘分区,依次类推。

/dev/sd[a-z]: SCSI设备

/dev/fd[0-7]: 标准软驱

/dev/md[0-31]: 软raid设备

/dev/loop[0-7]: 本地回环设备

/dev/ram[0-15]: 内存

/dev/null: 无限数据接收设备 ,相当于回收站

/dev/zero: 无限零资源

/dev/tty[0-63]: 虚拟终端

/dev/ttyS[0-3]: 串口

/dev/lp[0-3]: 并口

/dev/console: 控制台

/dev/fb[0-31]: framebuffer

/dev/cdrom => /dev/hdc

/dev/modem => /dev/ttyS[0-9]

/dev/pilot => /dev/ttyS[0-9]

/dev/console: 控制台和/dev/tty[0-63]:虚拟终端的区别与联系

/dev/null 外号叫无底洞,你可以向它输出任何数据,它通吃,并且不会撑着!

/dev/zero 是一个输入设备,你可你用它来初始化文件。

2、几种磁盘I/O方式读写扇区

linux磁盘I/O分为同步与异步两种。在此基础上又可以分为阻塞与非阻塞。异步I/O模式因为其更好的性能受到了广泛的应用。

linux实现异步I/O有两种方式,POSIX AIO和linux kernel自带的libaio。POSIX AIO是由glibc实现的,采用线程或者实时信号来通知IO完成,libaio是linux自带的异步I/O库,采用事件的通知机制,实现了更多的用户API。两者都属于异步非阻塞的方式。

接下来简单介绍一下三种I/O方式实现用户态直接读写磁盘扇区:

(1)同步非阻塞

int main()

{

int fd = open(“/dev/sdb”, O_RDWR); //可读可写的方式打开块设备

char c[5];

if(fd<0){

printf(“open errorn”);

exit(-1);

}

lseek(fd, 0, SEEK_SET); //将文件指针移动到文件头

write(fd, “abcd”, 5); //写入五个字符

lseek(fd, 0, SEEK_SET);

read(fd, c, 5);

printf(“%sn”, c);

close(fd);

return 0;

}

程序中简单的运动open、write和read三个函数就可以对块设备/dev/sdb进行读写,同时还可以使用pread/pwrite和readv/writev等来对块设备进行读写,由于lseek和read/write 调用之间,内核可能会临时挂起进程,所以对同步问题造成了问题,调用pread相当于顺序调用了lseek 和 read,这两个操作相当于一个捆绑的原子操作,它的效果就是每次都从文件头开始读写;readv/writev 在一次函数调用中读、写多个非连续缓冲区,但是这些缓冲区已经用iovec表示好了。减少了系统调用的次数。

(2)异步非阻塞

异步非阻塞有libaio和POSIX AIO两种方式。

a) libaio方式

libaio主要提供了接口函数,来方便用户进行异步IO。具体如下:

/* library wrappers */
extern int io_queue_init(int maxevents, io_context_t *ctxp);
/*extern int io_queue_grow(io_context_t ctx, int new_maxevents);*/
extern int io_queue_release(io_context_t ctx);
/*extern int io_queue_wait(io_context_t ctx, struct timespec *timeout);*/
extern int io_queue_run(io_context_t ctx);

/* Actual syscalls */
extern int io_setup(int maxevents, io_context_t *ctxp);
extern int io_destroy(io_context_t ctx);
extern int io_submit(io_context_t ctx, long nr, struct iocb *ios[]);
extern int io_cancel(io_context_t ctx, struct iocb *iocb, struct io_event *evt);
extern int io_getevents(io_context_t ctx_id, long min_nr, long nr, struct io_event *events, struct timespec *timeout);

libaio的异步机制主要是,将IO请求提交后,会维护一个队列,用来接收已经完成的IO请求,然后利用用户自己定义的回调函数来处理IO结束以后的操作。具体示例如下:

#include
#include
#include #include
#include
#include

int srcfd=-1;
int odsfd=-1;

#define AIO_BLKSIZE 1024 //块大小
#define AIO_MAXIO 64 //异步最大IO个数

//写请求完成之后的回调函数
static void wr_done(io_context_t ctx, struct iocb *iocb, long res, long res2)
{
if(res2 != 0)
{
printf(“aio write errorn”);
}
if(res != iocb->u.c.nbytes)
{
printf( “write missed bytes expect %d got %dn”, iocb->u.c.nbytes, res);
exit(1);
}

free(iocb->u.c.buf);
free(iocb);
}

//读请求完成之后的回调函数
static void rd_done(io_context_t ctx, struct iocb *iocb, long res, long res2)
{
/*library needs accessors to look at iocb*/
int iosize = iocb->u.c.nbytes;
char *buf = (char *)iocb->u.c.buf;
off_t offset = iocb->u.c.offset;
int tmp;
char *wrbuff = NULL;

if(res2 != 0)
{
printf(“aio readn”);
}
if(res != iosize)
{
printf( “read missing bytes expect %d got %d”, iocb->u.c.nbytes, res);
exit(1);
}

/*turn read into write*/
tmp = posix_memalign((void **)&wrbuff, getpagesize(), AIO_BLKSIZE);
if(tmp < 0)
{
printf(“posix_memalign222n”);
exit(1);
}

snprintf(wrbuff, iosize + 1, “%s”, buf);

printf(“wrbuff-len = %d:%sn”, strlen(wrbuff), wrbuff);
printf(“wrbuff_len = %dn”, strlen(wrbuff));
free(buf);

io_prep_pwrite(iocb, odsfd, wrbuff, iosize, offset);
io_set_callback(iocb, wr_done);

if(1!= (res=io_submit(ctx, 1, &iocb)))
printf(“io_submit write errorn”);

printf(“nsubmit %d write requestn”, res);
}

void main(int args,void * argv[])
{
int length = sizeof(“abcdefg”);
char * content = (char * )malloc(length);
io_context_t myctx;
int rc;
char * buff=NULL;
int offset=0;
int num,i,tmp;

if(args < 3)
{
printf(“the number of param is wrongn”);
exit(1);
}

if((srcfd=open(argv[1],O_RDWR)) < 0) //打开设备/dev/sdb1
{
printf(“open srcfile errorn”);
exit(1);
}

printf(“srcfd=%dn”,srcfd);

lseek(srcfd,0,SEEK_SET);
write(srcfd,”abcdefg”,length); //写入字母

lseek(srcfd,0,SEEK_SET);
read(srcfd,content,length); //读出字母正常

printf(“write in the srcfile successful,content is %sn”,content);

if((odsfd=open(argv[2],O_RDWR)) < 0) //打开另一个设备/dev/sdb2
{
close(srcfd);
printf(“open odsfile errorn”);
exit(1);
}

memset(&myctx, 0, sizeof(myctx));
io_queue_init(AIO_MAXIO, &myctx); //初始化一个请求队列

struct iocb *io = (struct iocb*)malloc(sizeof(struct iocb));
int iosize = AIO_BLKSIZE;
tmp = posix_memalign((void **)&buff, getpagesize(), AIO_BLKSIZE);
if(tmp < 0)
{
printf(“posix_memalign errorn”);
exit(1);
}
if(NULL == io)
{
printf( “io out of memeoryn”);
exit(1);
}

io_prep_pread(io, srcfd, buff, iosize, offset);

io_set_callback(io, rd_done); //设置读请求完成之后的回调函数

printf(“START…nn”);

rc = io_submit(myctx, 1, &io); //提交IO请求

if(rc < 0)
printf(“io_submit read errorn”);

printf(“nsubmit %d read requestn”, rc);

struct io_event events[AIO_MAXIO];
io_callback_t cb;

num = io_getevents(myctx, 1, AIO_MAXIO, events, NULL); //获取完成的io事件
printf(“n%d io_request completednn”, num);

for(i=0;i
{
cb = (io_callback_t)events[i].data;
struct iocb *io = events[i].obj;

printf(“events[%d].data = %x, res = %d, res2 = %dn”, i, cb, events[i].res, events[i].res2);
cb(myctx, io, events[i].res, events[i].res2); //调用回调函数处理
}

}

b) POSIX AIO方式实现

同样,POSIX也提供了AIO的API接口

API 函数 说明

aio_read 请求异步读操作

aio_error 检查异步请求的状态

aio_return 获得完成的异步请求的返回状态

aio_write 请求异步写操作

aio_suspend 挂起调用进程,直到一个或多个异步请求已经完成(或失败)

aio_cancel 取消异步 I/O 请求

lio_listio 发起一系列 I/O 操作

#include
#include
#include
#include
#include
#include

#define BUFSIZE 512

int main(){

int fd, ofd ,ret;
struct aiocb my_aiocb;

fd = open(“/dev/sdb”,O_RDONLY);
if (fd < 0)
perror(“open”);

/* Zero out the aiocb structure (recommended) */
bzero( (char *)&my_aiocb, sizeof(struct aiocb) );

/* Allocate a data buffer for the aiocb request */
my_aiocb.aio_buf = malloc(BUFSIZE+1);
if (!my_aiocb.aio_buf)
perror(“malloc”);

/* Initialize the necessary fields in the aiocb */
my_aiocb.aio_fildes = fd;
my_aiocb.aio_nbytes = BUFSIZE;
my_aiocb.aio_offset = 0;

ret = aio_read(&my_aiocb);

while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;

if ((ret = aio_return( &my_aiocb ))>0)
perror(“aio_read”);

else {
/* read failed, consult errno */
perror(“aio_read_error”);
}
return 0;

}