您的位置:首页 > 其它

文件高级---IO处理,文件锁,存储映射

2016-02-24 16:51 351 查看
socket阻塞与非阻塞,同步与异步

1. 同步,就是我调用一个功能,该功能没有结束前,我死等结果。

2. 异步,就是我调用一个功能,不需要知道该功能结果,该功能有结果后通知我(回调通知)

3. 阻塞, 就是调用我(函数),我(函数)没有接收完数据或者没有得到结果之前,我不会返回。

4. 非阻塞, 就是调用我(函数),我(函数)立即返回,通过select通知调用者

同步IO和异步IO的区别就在于:数据拷贝的时候进程是否阻塞!

阻塞IO和非阻塞IO的区别就在于:应用程序的调用是否立即返回!

对于举个简单c/s 模式:

同步:提交请求->等待服务器处理->处理完毕返回这个期间客户端浏览器不能干任何事

异步:请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕

小结:同步就是当一个进程发起一个函数(任务)调用的时候,一直等待直到函数(任务)完成,而进程继续处于激活状态。而异步情况下是当一个进程发起一个函数(任务)调用的时候,不会等函数返回,而是继续往下执行当,函数返回的时候通过状态、通知、事件等方式通知进程任务完成。

阻塞是当请求不能满足的时候就将进程挂起,而非阻塞则不会阻塞当前进程,即阻塞与非阻塞针对的是进程或线程而同步与异步所针对的是功能函数。

IO处理模型

1)阻塞I/O(blocking I/O)

在这个模型中,应用程序(application)为了执行这个read操作,会调用相应的一个system call,将系统控制权交给kernel,然后就进行等待(这其实就是被阻塞了)。kernel开始执行这个system call,执行完毕后会向应用程序返回响应,应用程序得到响应后,就不再阻塞,并进行后面的工作。

2)非阻塞I/O (nonblocking I/O)

在linux下,应用程序可以通过设置文件描述符的属性O_NONBLOCK,I/O操作可以立即返回,但是并不保证I/O操作成功。也就是说,当应用程序设置了O_NONBLOCK之后,执行write操作,调用相应的system call,这个system call会从内核中立即返回。但是在这个返回的时间点,数据可能还没有被真正的写入到指定的地方。也就是说,kernel只是很快的返回了这个 system call(只有立马返回,应用程序才不会被这个IO操作blocking),但是这个system call具体要执行的事情(写数据)可能并没有完成。而对于应用程序,虽然这个IO操作很快就返回了,但是它并不知道这个IO操作是否真的成功了,为了知道IO操作是否成功,一般有两种策略:一是需要应用程序主动地循环地去问kernel(这种方法就是同步非阻塞IO);二是采用I/O通知机制,比如:IO多路复用(这种方法属于异步阻塞IO)或信号驱动IO(这种方法属于异步非阻塞IO)。

3) I/O复用(select 和poll) (I/O multiplexing)

和之前一样,应用程序要执行read操作,因此调用一个system call,这个system call被传递给了kernel。但在应用程序这边,它调用system call之后,并不等待kernel的返回结果而是立即返回,虽然立即返回的调用函数是一个异步的方式,但应用程序会被像select()、poll和epoll等具有复用多个文件描述符的函数阻塞住,一直等到这个system call有结果返回了,再通知应用程序。也就是说,“在这种模型中,IO函数是非阻塞的,使用阻塞 select、poll、epoll系统调用来确定一个
或多个I/O 描述符何时能操作。”所以,从IO操作的实际效果来看,异步阻塞IO和第一种同步阻塞IO是一样的,应用程序都是一直等到IO操作成功之后(数据已经被写入或者读取),才开始进行下面的工作。不同点在于异步阻塞IO用一个select函数可以为多个描述符提供通知,提高了并发性。举个例子:假如有一万个并发的read请求,但是网络上仍然没有数据,此时这一万个read会同时各自阻塞,现在用select、poll、epoll这样的函数来专门负责阻塞同时监听这一万个请求的状态,一旦有数据到达了就负责通知,这样就将之前一万个的各自为战的等待与阻塞转为一个专门的函数来负责与管理。与此同时,异步阻塞IO和第二种同步非阻塞IO的区别在于:同步非阻塞IO是需要应用程序主动地循环去询问是否有操作数据可操作,而异步阻塞IO是通过像select和poll等这样的IO多路复用函数来同时检测多个事件句柄来告知应用程序是否可以有数据操作。

4)信号驱动I/O (signal driven I/O (SIGIO))

应用程序提交read请求的system call,然后,kernel开始处理相应的IO操作,而同时,应用程序并不等kernel返回响应,就会开始执行其他的处理操作(应用程序没有被IO操作所阻塞)。当kernel执行完毕,返回read的响应,就会产生一个信号或执行一个基于线程的回调函数来完成这次 I/O 处理过程。
从理论上说,阻塞IO、IO复用和信号驱动的IO都是同步IO模型。因为在这三种模型中,IO的读写操作都是在IO事件发生之后由应用程序来完成。而POSIX规范所定义的异步IO模型则不同。对异步IO而言,用户可以直接对IO执行读写操作,这些操作告诉内核用户读写缓冲区的位置,以及IO操作完成后内核通知应用程序的方式。异步IO读写操作总是立即返回,而不论IO是否阻塞的,因为真主的读写操作已经由内核接管。也就是说,同步IO模型要求用户代码自行执行IO操作(将数据从内核缓冲区读入用户缓冲区,或将数据从用户缓冲区写入内核缓冲区),而异步IO机制则是由内核来执行IO操作(数据在内核缓冲区和用户缓冲区之间的移动是由内核在后台完成的)。你可以这样认为,同步IO向应用程序通知的是IO就绪事件,而异步IO向应用程序通知的是IO完成事件。linux环境下,aio.h头文件中定义的函数提供了对异步IO的支持。
5)异步I/O (asynchronous I/O (the POSIX aio_functions))
异步IO与上面的异步概念是一样的, 当一个异步过程调用发出后,调用者不能立刻得到结果,实际处理这个调用的函数在完成后,通过状态、通知和回调来通知调用者的输入输出操作。异步IO的工作机制是:告知内核启动某个操作,并让内核在整个操作完成后通知我们,这种模型与信号驱动的IO区别在于,信号驱动IO是由内核通知我们何时可以启动一个IO操作,这个IO操作由用户自定义的信号函数来实现,而异步IO模型是由内核告知我们IO操作何时完成。为了实现异步IO,专门定义了一套以aio开头的API,如:aio_read.

19 #include <stdio.h>
20 #include <unistd.h>
21 #include <fcntl.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <sys/types.h>
25 #include <sys/time.h>
26 #include <stdlib.h>
27
28 int main(void)
29 {
30     fd_set set;
31     FD_ZERO(&set);
32     FD_SET(STDIN_FILENO, &set);
33
34     struct timeval t;
35     t.tv_sec = 2;
36     t.tv_usec = 0;
37
38     int n;
39     while((n=select(STDIN_FILENO+1, &set,
40                         NULL,NULL,&t)) != -1)
41     {
42         if(n == 0){
43             printf("time out!n");
44         }
45         if(FD_ISSET(STDIN_FILENO, &set)){
46             char buffer[100] = {};
47             size_t num;
48             num = read(STDIN_FILENO, buffer, 100);
49             if(write(STDOUT_FILENO, buffer, num)
50                         != num)
51             {
52                 fprintf(stderr, "write:%sn",
53                             strerror(errno));
54                 exit(1);
55             }
56         }
57         t.tv_sec = 2;
58         t.tv_usec = 0;
59         FD_ZERO(&set);
60         FD_SET(STDIN_FILENO, &set);
61     }
62     FD_CLR(STDIN_FILENO,&set);
63     return 0;
64 }
~


小结:前四种模型--阻塞IO、非阻塞IO、多路复用IO和信号驱动IO都属于同步模式,因为其中真正的IO操作(函数)都将会阻塞进程,只有异步IO模型真正实现了IO操作的异步性。

参考文章:http://blog.csdn.net/jay900323/article/details/18141217
http://www.ibm.com/developerworks/cn/linux/l-async/
文件锁机制

Linux下文件锁操作主要是通过以下两个API接口来完成的。

[cpp] view
plain copy

print?

#include <sys/file.h>

int flock(int fd, int operation);

或者

[cpp] view
plain copy

print?

#include <unistd.h>

#include <fcntl.h>

int fcntl(int fd, int cmd);

int fcntl(int fd, int cmd, long arg);

int fcntl(int fd, int cmd, struct flock *lock);

注:前者主要用于对整个文件的锁操作,后者可以对文件的部分内容进行锁操作。

Linux应用程序编程时应该注意以下几点:

1)文件锁是针对整个文件还是文件的部分内容。

2)进程级文件句柄关闭将会导致文件锁释放。

3)文件内容修改需要注意到glibc的缓冲机制,及时同步数据。

4)flock锁inode,fcntl锁文件描述符,因此flock不支持NFS,兼容性需要注意。

这里将给出进程级和线程级文件锁demo code供参考。

进程级文件锁demo:

[cpp] view
plain copy

print?

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <fcntl.h>

#include <unistd.h>

#define TEST_FOPEN

int main(int argc, char *argv[])

{

/* l_type l_whence l_start l_len l_pid */

struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };

int fd;

#ifdef TEST_FOPEN

FILE *file = NULL;

#endif /* TEST_FOPEN */

fl.l_pid = getpid();

if (argc > 1)

fl.l_type = F_RDLCK;

while(1)

{

#ifdef TEST_FOPEN

if ((file = fopen("lockdemo.c", "rw+")) == NULL) {

perror("fopen");

exit(1);

}

#else

if ((fd = open("lockdemo.c", O_RDWR)) == -1) {

perror("open");

exit(1);

}

#endif /* TEST_FOPEN */

printf("Press <RETURN> to try to get lock: ");

getchar();

printf("Trying to get lock...");

#ifdef TEST_FOPEN

fd = fileno(file);

#endif /* TEST_FOPEN */

fl.l_type = F_WRLCK; /* set to lock same region */

if (fcntl(fd, F_SETLKW, &fl) == -1) {

perror("fcntl");

exit(1);

}

printf("got lock\n");

printf("Press <RETURN> to release lock: ");

getchar();

fl.l_type = F_UNLCK; /* set to unlock same region */

if (fcntl(fd, F_SETLK, &fl) == -1) {

perror("fcntl");

exit(1);

}

printf("Unlocked.\n");

#ifdef TEST_FOPEN

fclose(file);

#else

close(fd);

#endif /* TEST_FOPEN */

}

return 0;

}

运行结果:



线程级文件锁demo:

[cpp] view
plain copy

print?

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <fcntl.h>

#include <unistd.h>

#include <pthread.h>

#define TEST_FOPEN

#define TEST_FLOCK

void* thread_flock(void* ptr)

{

/* l_type l_whence l_start l_len l_pid */

struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0 };

int fd;

int ith = *((int *)ptr);

#ifdef TEST_FOPEN

FILE *file = NULL;

#endif /* TEST_FOPEN */

fl.l_pid = getpid();

while(1)

{

#ifdef TEST_FOPEN

if ((file = fopen("lockdemo.c", "rw+")) == NULL) {

perror("fopen");

exit(1);

}

#else

if ((fd = open("lockdemo.c", O_RDWR)) == -1) {

perror("open");

exit(1);

}

#endif /* TEST_FOPEN */

#ifdef TEST_FOPEN

fd = fileno(file);

#endif /* TEST_FOPEN */

#ifdef TEST_FLOCK

flock(fd, LOCK_EX);

#else

fl.l_type = F_WRLCK; /* set to lock same region */

if (fcntl(fd, F_SETLKW, &fl) == -1) {

perror("fcntl");

exit(1);

}

#endif /* TEST_FLOCK */

printf("[%d] %d --> got lock\n", ith, fd);

sleep(ith);

#ifdef TEST_FLOCK

flock(fd, LOCK_UN);

#else

fl.l_type = F_UNLCK; /* set to unlock same region */

if (fcntl(fd, F_SETLKW, &fl) == -1) {

perror("fcntl");

exit(1);

}

#endif /* TEST_FLOCK */

printf("[%d] %d--> Unlocked.\n", ith, fd);

#ifdef TEST_FOPEN

fclose(file);

#else

close(fd);

#endif /* TEST_FOPEN */

sleep(2);

}

}

int main(int argc, char *argv[])

{

int time1, time2;

pthread_t pid1,pid2;

time1 = 1;

pthread_create(&pid1, NULL, &thread_flock, &time1);

time2 = 3;

pthread_create(&pid2, NULL, &thread_flock, &time2);

while(1)

sleep(10);

return 0;

}

运行结果:



参考文章:http://www.ibm.com/developerworks/cn/linux/l-cn-filelock/index.html
http://www.jb51.net/article/58757.htm http://blog.csdn.net/lida2003/article/details/7267226 http://www.linuxidc.com/Linux/2013-09/90155.htm
存储映射

用存储映射的方式代替read和write更快.因为少了内核与用户空间的来回拷贝所以通常更快

建立内存映射mmap()和解除内存映射munmap()

头文件:#include <unistd.h> #include <sys/mman.h>

定义函数:void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offsize);

函数说明:mmap()用来将某个文件内容映射到内存中,对该内存区域的存取即是直接对该文件内容的读写。

头文件:#include
<unistd.h> #include <sys/mman.h>

定义函数:int munmap(void *start, size_t length);

函数说明:munmap()用来取消参数start 所指的映射内存起始地址,参数length 则是欲取消的内存大小。当进程结束或利用exec 相关函数来执行其他程序时,映射内存会自动解除,但关闭对应的文件描述词时不会解除映射。

返回值:如果解除映射成功则返回0,否则返回-1。错误原因存于errno 中错误代码EINVAL参数 start 或length 不合法。

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
void main(){
int fd;
char *start;
struct stat sb;
fd = open("/etc/passwd", O_RDONLY); /*打开/etc/passwd */
fstat(fd, &sb); /* 取得文件大小 */
start = mmap(NULL, sb.st_size, PROT_READ, M_PRIVATE, fd, 0);
if(start == MAP_FAILED) /* 判断是否映射成功 */
return;
printf("%s", start);
munmap(start, sb.st_size); /* 解除映射 */
close(fd);
}
参考文章:https://www.ibm.com/developerworks/cn/linux/l-cn-zerocopy2/
http://www.ibm.com/developerworks/cn/linux/l-ipc/part5/index2.html http://www.ibm.com/developerworks/cn/aix/library/au-highperform2/ https://www.ibm.com/search/csass/search/?q=%E5%AD%98%E5%82%A8%E6%98%A0%E5%B0%84mmap&dws=cndw&ibm-search.x=0&ibm-search.y=0&ibm-search=Search&sn=dw&lang=zh&cc=CN&ddr=&en=utf&lo=zh&hpp=20
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: