同步化、同步及异步操作

“同步”和“异步”这两个概念强调的是I/O操作在返回之前是否需要等待某个时间,而“同步化”强调的是必须发生何种事件。比如写入操作的四种情况如下:

  1. “同步化”+“同步”:操作会等到数据被刷入磁盘后返回,比如文件以O_SYNC模式打开;
  2. “同步化”+“异步”:虽然操作会在请求插入队列的时候就返回,但是该操作会保证数据刷入磁盘
  3. “异步化”+“同步”:数据写入内核缓冲中才返回,但是该操作只会影响内核缓冲区,这是通常方式;
  4. “异步化”+“异步”:操作插入请求队列就马上返回了。
为什么会需要异步I/O操作?
  1. 操作的时候不想被阻塞,那么是不是可以认为I/O操作的开销为0?
  2. 把插入队列、I/O提交内核、接受操作完成通知等动作分开。
在要进行AIO的时候离不开的一个结构就是aiocb,具体的定义如下:
struct aiocb{
int aio_fildes; /* File desriptor. */
int aio_lio_opcode; /* Operation to be performed. */
int aio_reqprio; /* Request priority offset. */
volatile void *aio_buf; /* Location of buffer. */
size_t aio_nbytes; /* Length of transfer. */
struct sigevent aio_sigevent; /* Signal number and value. */

/* Internal members. */
struct aiocb *__next_prio;
int __abs_prio;
int __policy;
int __error_code;
__ssize_t __return_value;
#ifndef __USE_FILE_OFFSET64
__off_t aio_offset; /* File offset. */
char __pad[sizeof (__off64_t) - sizeof (__off_t)];
#else
__off64_t aio_offset; /* File offset. */
#endif
char __unused[32];
};
下面是一个非正规的例子(不能体现AIO的特性,只是看下如何编程以及应该注意的地方):
int main(){
int fd, ret;
struct aiocb my_aiocb;
bzero((char*)&my_aiocb, sizeof(struct aiocb));

fd = open("test", O_RDONLY);
if(fd < 0){
printf("打开文件失败!\n");
}else{
printf("打开文件成功!\n");
}

my_aiocb.aio_buf = malloc(BUFSIZE+1);
if(my_aiocb.aio_buf <= 0){
printf("分配缓存失败!\n");
}else{
printf("分配缓存成功!\n");
}

my_aiocb.aio_fildes = fd;
my_aiocb.aio_nbytes = BUFSIZE;
my_aiocb.aio_offset = 0;

ret = aio_read(&my_aiocb);
if(ret < 0){
printf("异步读取失败!\n");
}else{
printf("异步读取成功!\n");
}

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

if((ret = aio_return(&my_aiocb)) > 0){
printf("数据到达用户缓冲区!\n");
}else{
printf("读取失败!\n");
}
return 0;
}
需要注意的是要调用bzero把不用的位清零,不然的话插入请求队列的时候会失败(也许在实现的时候会检查各个标志位并检查各标志位的合法性以及标志之间是否产生冲突,类似do_fork?)。EINPROGRESS应该包括头文件errno.h。
在程序中可以使用suspend函数实现挂起操作,如果所等待的数组中有一个完成,那么挂起状态就结束,下面是该用法简单的一个示例:
int main(){
int ret;
struct aiocb aio, *ptr;
ptr = &aio;
bzero((char*)&aio, sizeof(aio));
aio.aio_buf = malloc(BUFSIZE+1);
aio.aio_fildes = STDIN_FILENO;
aio.aio_nbytes = BUFSIZE;
aio.aio_offset = 0;

ret = aio_read(&aio);

ret = aio_suspend(&ptr, 1, NULL);
printf("挂起结束,现在的状态是:%d\n", aio_error(&aio));

//while(aio_error(&aio) == EINPROGRESS);
if((ret = aio_return(&aio)) > 0){
printf("%s", (char*)aio.aio_buf);
}
return 0;
}
使用信号作为AIO请求通知的时候需要用到的一个数据结构是sigaction,控制进程接到信号时候的动作:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_t sa_mask;
int sa_flags;
void (*sa_restorer)(void);
}
在需要改变的时候可以调用下面的函数:
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
该结构中各个属性的用法如下:
  1. sa_handler:不要和sa_sigaction同时设置,处理信号的时候只需要一个参数:signo,是默认的处理函数;
  2. sa_sigaction:flags设置SA_SIGINFO的时候的处理函数。处理函数的第一个参数同样是signo,第二个参数为siginfo_t包含了一些信号的信息,第三个参数是void*;
  3. sa_mask:指定信号,在处理这些信号的时候应该被阻塞?
  4. sa_flags:指定信号处理进程的行为:
    1. SA_SIGINFO,这个是最常用的了,设置该值就调用sa_sigaction函数;
    2. SA_NOCLDSTOP,忽略SIGSTOP信号(这个不是默认忽视的嘛);
    3. SA_NOCLDWAIT,在接到SIGSTOP信号的时候不把子进程编程“僵尸进程”;
    4. SA_RESETHAND,重置控制为默认状态;
    5. SA_ONSTACK,优先使用sigaltstack设置的栈;
    6. SA_RESTART,提供与BSD的信号相兼容的行为;
    7. SA_NODEFER,处理信号的时候也不会阻塞信号的接受?
  5. sa_restorer已经被废弃。
令一个aiocb中需要设置的数据结构是sigevent:
struct sigevent {    
int sigev_notify;
int sigev_signo;
union sigval sigev_value;
void (*sigev_notify_function) (union sigval);
void *sigev_notify_attributes;
};
该结构中各属性的含义如下:
  1. sigev_notify指定了通知的方法:
    1. SIGEV_NONE:现在的进程就是要通知的?
    2. SIGEV_SIGNAL:信号回调通知
    3. SIGEV_THREAD:线程回调通知
  2. sigev_signo:通知信号;
  3. sigval:通知所传送的数据,可是指针或整数;
  4. sigev_notify_function:线程回调通知的函数地址;
  5. sigev_notify_sttributes:线程函数的特性;
下面是一个使用信号作为AIO通知的简单例子:
#define BUFSIZE 10

void do_sth(int signo, siginfo_t *info, void *context){
struct aiocb * ptr;
int ret;
printf("信号处理函数!\n");
if(info->si_signo == SIGIO){
ptr = (struct aiocb*)info->si_value.sival_ptr;
if(aio_error(ptr)==0){
ret = aio_return(ptr);
}
}
}

int main(){
int ret, i;
struct aiocb in;
struct sigaction sigact;

sigemptyset(&sigact.sa_mask);
sigact.sa_flags = SA_SIGINFO;
sigact.sa_sigaction = do_sth;

bzero((char*)&in, sizeof(struct aiocb));

in.aio_fildes = STDIN_FILENO;
in.aio_buf = malloc(BUFSIZE+1);
in.aio_nbytes = BUFSIZE;
in.aio_offset = 0;
in.aio_lio_opcode = LIO_READ;

in.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
in.aio_sigevent.sigev_signo = SIGIO;
in.aio_sigevent.sigev_value.sival_ptr = &in;

ret = sigaction(SIGIO, &sigact, NULL);//改变进程接到信号后的处理方式
ret = aio_read(&in);
sleep(10);
printf("main函数结束!\n");

return 0;
}
在使用线程回调通知的一个简单的例子如下:
#define BUFSIZE 10

void do_sth(sigval_t sigval){
struct aiocb * ptr = (struct aiocb*)sigval.sival_ptr;
int ret;
printf("信号处理函数,线程ID是:%d\n", (int)pthread_self());
if(aio_error(ptr) == 0){
ret = aio_return(ptr);
}
}

int main(){
int ret, i;
struct aiocb in;
struct sigaction sigact;

bzero((char*)&in, sizeof(struct aiocb));

in.aio_fildes = STDIN_FILENO;
in.aio_buf = malloc(BUFSIZE+1);
in.aio_nbytes = BUFSIZE;
in.aio_offset = 0;
in.aio_lio_opcode = LIO_READ;

in.aio_sigevent.sigev_notify = SIGEV_THREAD;
in.aio_sigevent.sigev_notify_function = do_sth;
in.aio_sigevent.sigev_notify_attributes = NULL;
in.aio_sigevent.sigev_value.sival_ptr = &in;

printf("main函数线程的id:%d\n", (int)pthread_self());

ret = aio_read(&in);
sleep(10);
printf("main函数结束!\n");

return 0;
}
程序运行的具体原理还需要自己动手去测试。
posted @ 2011-10-12 14:34  GG大婶  阅读(1858)  评论(0编辑  收藏  举报