系统调用分为两类:低速系统调用和其他。
低速系统调用是可以使进程永远阻塞的一类系统调用
非阻塞I/O使我们可以发出open,read和write这样的I/O操作,并使这些操作永远不会阻塞。如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞
对于一个给定的描述符,有两种为其指定非阻塞I/O的方法
1. 如果调用open获得描述符,则可指定O_NONBLOCK标志
2. 对于已经打开的一个描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志
记录锁的功能是:当第一个进程正在读或修改文件的某个部分时,使用记录锁可以阻止其他进程修改同一文件区。一个更适合的术语可能是字节范围锁,因为它锁定的只是文件中的一个区域。
fcntl记录锁
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*struct flock *flockptr*/);
//返回值:若成功,依赖于cmd,否则,返回-1
cmd是F_GETLK、F_SETLK或F_SETLKW。第三个参数是一个指向flock结构的指针
struct flock
{
short l_type; /* F_RDLCK, F_WRLCK, or F_UNLCK */
short l_whence; /* SEEK_SET, SEEK_CUR, or SEEK_END */
off_t l_start; /* offset in bytes, relative to l_whence */
off_t l_len; /* length, in bytes; 0 means lock to EOF */
pid_t l_pid; /* returned with F_GETLK */
};
对于flock的结构说明如下:
关于加锁和解锁区的说明:
规则:任意多个进程在一个给定的字节上可以有一把共享的锁,但是在一个给定字节上只能有一个进程有一把独占写锁。
关于cmd命令
在设置或释放文件上的锁时,系统按要求组合或分裂相邻区。
请求和释放一把锁的函数:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return (fcntl(fd, cmd, &lock));
}
测试一把锁
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
if (fcntl(fd, F_GETLK, &lock) < 0)
{
perror("fcntl error!");
exit(EXIT_FAILURE);
}
if (lock.l_type == F_UNLCK)
{
return 0;
}
return (lock.l_pid);
}
读锁和写锁测试
读锁
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
void Perror(const char* s)
{
perror(s);
exit(EXIT_FAILURE);
}
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
if (fcntl(fd, F_GETLK, &lock) < 0)
{
perror("fcntl error!");
exit(EXIT_FAILURE);
}
if (lock.l_type == F_UNLCK)
{
return 0;
}
return (lock.l_pid);
}
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return (fcntl(fd, cmd, &lock));
}
int main()
{
//创建文件
int fd = open("./test.tmp",O_RDONLY | O_CREAT | O_EXCL, 0777);
if (fd == -1)
{
printf("file exit!\n");
fd = open("./test.tmp", O_RDONLY, 0777);
}
else
printf("create file success.\n");
pid_t pid = getpid();
printf("the proc pid : %d\n", pid);
pid_t lockpid = lock_test(fd, F_RDLCK, 0, SEEK_SET, 0);
if(lockpid == 0)
printf("check read lockable, ok\n");
else
printf("check read lockable, can't.have write lock, owner pid : %d\n", lockpid);
//set read lock
if (lock_reg(fd, F_SETLK, F_RDLCK, 0,SEEK_SET, 0) < 0)
printf("set read lock failed\n");
else
printf("set read lock success\n");
sleep(60);
return 0;
}
写锁
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
void Perror(const char* s)
{
perror(s);
exit(EXIT_FAILURE);
}
//check lock
pid_t lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
if (fcntl(fd, F_GETLK, &lock) < 0)
{
perror("fcntl error!");
exit(EXIT_FAILURE);
}
if (lock.l_type == F_UNLCK)
{
return 0;
}
return (lock.l_pid);
}
//set lock or free lock
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len)
{
struct flock lock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
return (fcntl(fd, cmd, &lock));
}
int main()
{
int fd = open("./test.tmp", O_WRONLY | O_CREAT | O_EXCL, 0777);
if (fd == -1)
{
printf("file exit \n");
fd = open("./test.tmp", O_WRONLY, 077);
}
else
printf("create file success.\n");
pid_t pid = getpid();
printf("the proc pid:%d\n", pid);
//check write
pid_t lockpid = lock_test(fd, F_WRLCK, 0, SEEK_SET, 0);
if (lockpid == 0)
printf("check write lockable, ok\n");
else
printf("check write lockable, can't. \ have read or write lock,ower pid : %d", lockpid);
//set write lock
if (lock_reg(fd, F_SETLK, F_WRLCK, 0, SEEK_SET, 0) < 0)
printf("set write lock failed\n");
else
printf("set write lock success\n");
sleep(60);
return 0;
}
设置读锁,再设置读锁
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ ./readflock
create file success. the proc pid : 31858 check read lockable, ok set read lock success ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ ./readflock file exit! the proc pid : 31908 check read lockable, ok set read lock success
设置读锁载设置写锁
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ ./readflock
file exit!
the proc pid : 31973
check read lockable, ok
set read lock success
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ ./writeflock
file exit
the proc pid:32018
check write lockable, can't. have read or write lock,ower pid : 31973set write lock failed
锁的隐含继承和释放
在文件尾端加锁
在文件尾端加锁或者解锁时要特别小心,采用SEEK_END和SEEK_CUR是可能不断变化的,所以们最好采用SEEK_SET,使用绝对偏移,否则应时刻记住当前偏移量和文件尾端的位置。
建议性锁和请执行锁(参考:http://www.cppblog.com/mysileng/archive/2012/12/17/196372.html)
1.建议性锁机制是这样规定的:每个使用文件的进程都要主动检查该文件是否有锁存在,当然都是通过具体锁的API,比如fctl记录锁F_GETTLK来主动检查是否有锁存在。如果有锁存在并被排斥,那么就主动保证不再进行接下来的IO操作。如果每一个进程都主动进行检查,并主动保证,那么就说这些进程都以一致性的方法处理锁,(这里的一致性方法就是之前说的两个主动)。但是这种一致性方法依赖于编写进程程序员的素质,也许有的程序员编写的进程程序遵守这个一致性方法,有的不遵守。不遵守的程序员编写的进程程序会怎么做呢?也许会不主动判断这个文件有没有加上文件锁或记录锁,就直接对这个文件进行IO操作。此时这种有破坏性的IO操作会不会成功呢?如果是在建议性锁的机制下,这种破坏性的IO就会成功。因为锁只是建议性存在的,并不强制执行。内核和系统总体上都坚持不使用建议性锁机制,它们依靠程序员遵守这个规定。(Linux默认是采用建议性锁)
2.强制性锁机制是这样规定的: 所有记录或文件锁功能内核执行的。上述提到的破坏性IO操作会被内核禁止。当文件被上锁来进行读写操作时,在锁定该文件的进程释放该锁之前,内核会强制阻止任何对该文件的读或写违规访问,每次读或写访问都得检查锁是否存在。也就是强制性锁机制,让锁变得名副其实,真正达到了锁的效果,而不是像建议性锁机制那样只是个纸老虎。= =!
设置强制性文件锁的方式比较特别:
chmod g+s <filename>
chmod g-x <filename>
这是形象的表示,实际编程中应该通过chmod()函数一步完成。不能对目录、可执行文件设置强制性锁。
当一个进程有多个输入输出时该如何处理?
一个比较好的技术是:I/O多路转换。为了使用这种技术,先构造一张我们感兴趣的描述符的列表,然后调用一个函数,直到这些描述符中的一个已准备好进行I/O时,该函数才返回。poll、pselect和select这3个函数使我们能够执行I/O多路转接。
select函数告诉内核:
从select返回时,内核告诉我们:
#include <sys/select.h>
int select(int maxfdp1, fd_set *restrict readfds,
fd_set *restrict writefds, fd_set *restrict excepyfds
, struct timeval *restrict tvptr);
//返回值:准备就绪的描述符数目;若超时,返回0;若出错,返回-1
#include <sys/select.h>
int FD_ISSET(int fd, fd_set *fdset);//检测是否打开
//返回值:若fd在描述符集中,返回非0值;否则,返回0
void FD_CLR(int fd, fd_set *fdset)//清空一位
void FD_SET(int fd, fd_set *fdset);//设置一位
void FD_ZERO(fd_set *fdset);//清0
select的中间3个参数中的任意一个可以是空指针,这表示对相应条件并不关心。如果所有3个指针都是NULL,则select提供了比sleep更精确的定时器
* maxfdp1:表示要搜寻的最大描述符编号值加1。
* 返回值 :-1表示出错,0表示没有描述符准备好,正值说明已经准备好的描述符数。
pselect是select的变体,函数形式如下:
#include <sys/select.h>
int pselect(intmaxfdp1,fd_set *restrictreadfds,
fd_set *restrictwritefds,fd_set *restrictexceptfds,
const struct timespec *restricttsptr,
const sigset_t *restrictsigmask);
//返回值:准备就绪的描述符数目,若超时返回0,若出错返回1.
poll函数和select类似。
#include <poll.h>
int poll(struct pollfd fdarray[], nfds_y nfds, int timeout);
//返回值:准备就绪的描述符数目:若超时,返回0;若出错,返回-1
struct pollfd{
int fd;//文件描述符
short events;//等待的事件
short revents;//实际发生了的事件
};
合法的事件如下:
POLLIN 有数据可读。
此外,revents域中还可能返回下列事件:
这些事件在events域中无意义,因为它们在合适的时候总是会从revents中返回。
timeout参数制定等待的时间:-1,永远等待;0,不等待;>0,等待timeout毫秒数;
[参考:http://blog.jobbole.com/103290/]
同步: 所谓同步,就是在c端发出一个功能调用时,在没有得到结果之前,该调用就不返回。也就是必须一件一件事做,等前一件做完了才能做下一件事。例如普通B/S模式(同步):提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事
异步:异步的概念和同步相对。当c端一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。例如 ajax请求(异步): 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
以下主要介绍POSIX异步I/O
在异步非阻塞 I/O 中,我们可以同时发起多个传输操作。这需要每个传输操作都有惟一的上下文,这样我们才能在它们完成时区分到底是哪个传输操作完成了。在 AIO 中,这是一个 aiocb(AIO I/O Control Block)结构。这个结构包含了有关传输的所有信息,包括为数据准备的用户缓冲区。在产生 I/O (称为完成)通知时,aiocb 结构就被用来惟一标识所完成的 I/O 操作。这个 API 的展示显示了如何使用它。
API 函数 | 说明 |
---|---|
aio_read | 请求异步读操作 |
aio_error | 检查异步请求的状态 |
aio_return | 获得完成的异步请求的返回状态 |
aio_write | 请求异步写操作 |
aio_suspend | 挂起调用进程,直到一个或多个异步请求已经完成(或失败) |
aio_cancel | 取消异步 I/O 请求 |
lio_listio | 发起一系列 I/O 操作 |
POSIX的异步I/O借口使用AIO控制块来描述I/O操作。aiocb结构体定义了AIO控制块。
struct aiocb{
int aio_fildes;//文件描述符
off_t aio_offset//偏移量
volatile void *aio_buf;//指定开始地址
size_t aio_nbytes;//字节数
int aio_reqprio;//异步I/O请求提示顺序
struct sigevent aio_sigevent;//字段控制
int aio_lio_opcode;//控制通知类型
};
sigevent结构体如下:
struct sigevent{
int sigev_notify;
int sigev_signo;
union sigval sigev_value;
void (*sigev_notify_function)(union sigval);
pthread_attr_t *sigev_notify_attributes;
};
sigev_notify字段控制通知的类型:
在异步I/O之前需要先初始化AIO控制块.读写操作如下:
#include <aio.h>
int aio_read(struct aiocb *aiocb);
int aio_write(struct aiocb *aiocb);
//两个函数的返回值:若成功,返回0;若出错,返回-1
//一个使用aio_read进行异步读的例子
#include <aio.h>
...
int fd, ret;
struct aiocb my_aiocb;
fd = open( "file.txt", 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 );
if (ret < 0) perror("aio_read");
while ( aio_error( &my_aiocb ) == EINPROGRESS ) ;
if ((ret = aio_return( &my_iocb )) > 0) {
/* got ret bytes on the read */
} else {
/* read failed, consult errno */
}
要想强制所有等待中的异步操作不等待而写入持久化的存储中,可以设立一个AIO控制块并调用aio_fsync函数
#include <aio.h>
int aio_fsync(int op, struct aiocb *aiocb);
//返回值:若成功,返回0;若出错,返回-1
为了获得一个异步读、写或者同步操作的完成状态,需要调用aio_error函数
#include <aio.h>
int aio_error(const struct aiocb *aiocb);
如果异步调用成功可以使用aio_return函数获取异步调用的返回值:
#include <aio.h>
ssize_t aio_return(const struct aiocb *aiocb);
如果在完成了所有事务时,还有异步操作未完成时,可以调用aio_suspend函数来阻塞进程,直到操作完成。
#include <aio.h>
int aio_suspend(const struct aiocb *const list[], int nent,
const struct timespec *timeout);
//返回值:若成功,返回0;若出错,返回-1
//使用 aio_suspend 函数阻塞异步 I/O
struct aiocb *cblist[MAX_LIST]
/* Clear the list. */
bzero( (char *)cblist, sizeof(cblist) );
/* Load one or more references into the list */
cblist[0] = &my_aiocb;
ret = aio_read( &my_aiocb );
ret = aio_suspend( cblist, MAX_LIST, NULL );
当还有我们不想再完成的等待中的异步I/O操作时,可以尝试使用aio_cancel函数来取消它们
#include <aio.h>
int aio_cancel(int fd, struct aiocb *aiocb);
AIO 提供了一种方法使用 lio_listio API 函数同时发起多个传输。这个函数非常重要,因为这意味着我们可以在一个系统调用(一次内核上下文切换)中启动大量的 I/O 操作。从性能的角度来看,这非常重要.
lio_listio API 函数的原型如下:
int lio_listio( int mode, struct aiocb *list[], int nent,
struct sigevent *sig );
//使用 lio_listio 函数发起一系列请求
struct aiocb aiocb1, aiocb2;
struct aiocb *list[MAX_LIST];
...
// Prepare the first aiocb
aiocb1.aio_fildes = fd;
aiocb1.aio_buf = malloc( BUFSIZE+1 );
aiocb1.aio_nbytes = BUFSIZE;
aiocb1.aio_offset = next_offset;
aiocb1.aio_lio_opcode = LIO_READ;
...
bzero( (char *)list, sizeof(list) );
list[0] = &aiocb1;
list[1] = &aiocb2;
ret = lio_listio( LIO_WAIT, list, MAX_LIST, NULL );
AIO 通知可以使用信号进行异步通知,也可以使用回调函数进行异步通知。
一个使用信号通知的例子:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <aio.h>
#include <fcntl.h>
void AsyncRead(int s, siginfo_t* info, void* context)
{
struct aiocb* ptr = (struct aiocb*)info->si_value.sival_ptr;
printf("read = %s\n", (char*)ptr->aio_buf);
}
int main()
{
struct aiocb cb;
int fd;
char sbuf[100];
int ret;
struct sigaction act;
fd = open("aiotest.txt", O_RDWR, S_IRUSR|S_IWUSR);
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART | SA_SIGINFO;
act.sa_sigaction = AsyncRead;
sigaction(SIGIO, &act, NULL);
bzero(&cb, sizeof(cb));
cb.aio_fildes = fd;
cb.aio_buf = sbuf;
cb.aio_nbytes = 100;
cb.aio_offset = 0;
cb.aio_sigevent.sigev_value.sival_ptr = &cb;
cb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
cb.aio_sigevent.sigev_signo = SIGIO;
ret = aio_read(&cb);
if (ret == -1)
{
perror("aio_read error.");
exit(EXIT_FAILURE);
}
int i = 0;
while (i != 10)
{
printf("%d\n", i++);
sleep(3);
}
return 0;
}
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ gcc AIONotifyTest.c -lrt
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ echo test aio > aiotest.txt
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ ls aiotest.txt
aiotest.txt
ubuntu@VM-188-113-ubuntu:~/Code/apue/ch14AdvancedI_O$ ./a.out
0
read = test aio
r½. %可能是乱码
1
2
3
4
5
readv和writev函数用于在一次函数调用中读、写多个非连续缓冲区。有时也将这两个函数称为散布读和聚集写.
#include <sys/uio.h>
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
//两个函数的返回值:已读或已写的字节数;若出错,返回-1
这两个函数的第二个参数指向iovec结构数组的一个指针:
struct iovec{
void *iov_base;
size_t iov_len;
};
iovec结构如下:
管道、FIFO以及某些设备(特别是终端和网络)有下列两种性质
readn和writen读写制定的N字节的数据,并处理返回值可能小于要求值的情况。
#include "apue.h"
ssize_t readn(int fd, void *buf, size_t nbytes);
ssize_t writen(int fd, void *buf, size_t nbytes);
//两个函数的返回值:读、写的字节数;若出错,返回-1
存储映射I/O能将一个磁盘文件映射到存储空间中的一个缓冲区上,于是,当从缓冲区中取数据时,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区时,相应字节就自动写入文件。这样,就可以在不使用read和write的情况下执行I/O。
为了使用这种功能,应首先告诉内核将一个给定的文件映射到一个存储区域中。这是由mmap函数实现的
#include <sys/mman.h>
void *mmap(void *addr, size_t len, int prot, int flag, int fd, off_t off);
//返回值:若成功,返回映射区的起始地址;若出错,返回MAP_FAILED
存储映射例子:
调用mprotect可以更改一个现存映射存储区的权限。
#include <sys/mman.h>
int mprotect(void *addr, size_t len, int prot);
返回值:若成功则返回0,若出错则返回-1
如果在共享存储映射区中的页已被修改,那么我们可以调用msync将该页冲洗到被映射的文件中。msync函数类似于fsync,但作用于存储映射区。
#include <sys/mman.h>
int msync(void *addr, size_t len, int flags);
返回值:若成功则返回0,若出错则返回-1
进程终止时,或调用了munmap之后,存储映射区就被自动解除映射。关闭文件描述符filedes并不解除映射区。
#include <sys/mman.h>
int munmap(caddr_t addr, size_t len);
返回值:若成功则返回0,若出错则返回-1
munmap不会影响被映射的对象,也就是说,调用munmap不会使映射区的内容写到磁盘文件上。对于MAP_SHARED区磁盘文件的更新,在写到存储映射区时按内核虚存算法自动进行。在解除了映射后,对于MAP_PRIVATE存储区的修改被丢弃。
[1] http://blog.jobbole.com/103290/
[2] http://www.ibm.com/developerworks/cn/linux/l-async/index.html
[3] http://blog.csdn.net/gotosola/article/details/7412409
[4] http://www.cnblogs.com/nufangrensheng/p/3559664.html
[5] http://www.cppblog.com/mysileng/archive/2012/12/17/196372.html
[6] http://blog.jobbole.com/103290