1. 非阻塞I/O
对于一个给定的描述符,有两种为其指定非阻塞I/O的办法:
i. 如果调用open获得描述符,则可制定O_NONBLOCK标志;
ii. 对于已经打开的一个描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志。
<span style="font-size:18px;">#include "apue.h"
#include <errno.h>
#include <fcntl.h>
char buf[500000];
int main(void)
{
int ntowrite, nwrite;
char *ptr;
ntowrite = read(STDIN_FILENO, buf, sizeof(buf));
fprintf(stderr, "read %d bytes\n", ntowrite);
set_fl(STDOUT_FILENO, O_NONBLOCK); //set nonblocking
ptr = buf;
while(ntowrite > 0){
errno = 0;
nwrite = write(STDOUT_FILENO, ptr, ntowrite);
fprintf(stderr, "nwrite = %d, errno = %d\n", nwrite, errno);
if(nwrite > 0){
ptr += nwrite;
ntowrite -= ntowrite;
}
}
clr_fl(STDOUT_FILENO, O_NONBLOCK); //clear nonblocking
exit(0);
}</span>
2. 记录锁
i. fcntl记录锁
如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性写锁,则不能再对它加任何锁。
<span style="font-size:18px;"><span style="font-size:18px;">#include "apue.h"
#include <fcntl.h>
static void lockabyte(const char *name, int fd, off_t offset)
{
if(writew_lock(fd, offset, SEEK_SET, 1) < 0)
err_sys("%s: writew_lock error", name);
printf("%s: got the lock, byte %lld\n", name, (long long)offset);
}
int main(void)
{
int fd;
pid_t pid;
//create a file and write two bytes in it
if((fd = creat("templock", FILE_MODE)) < 0)
err_sys("creat error");
if(write(fd, "ab", 2) != 2)
err_sys("write error");
TELL_WAIT();
if((pid = fork()) < 0){
err_sys("fork error");
} else if(pid == 0){
lockabyte("child", fd, 0);
// sleep(2);
TELL_PARENT(getppid());
WAIT_PARENT();
lockabyte("child", fd, 1);
} else{
// sleep(5);
lockabyte("parent", fd, 1);
// sleep(5);
TELL_CHILD(pid);
WAIT_CHILD();
lockabyte("parent", fd, 0);
}
exit(0);
}
/**************************************************
child: got the lock, byte 0
parent: got the lock, byte 1
child: writew_lock error: Resource deadlock avoided
parent: got the lock, byte 0
**************************************************/
</span></span>
ii. 锁的隐含继承和释放
关于记录锁的自动继承和释放有3条规则
锁与进程与文件相关联;由fork产生的子进程不继承父进程所设置的锁;在执行exec之后,新程序可以继承原执行程序的锁。
iii. 建议性锁和强制性锁
建议性锁并不能阻止对数据库文件有写权限的任何其他进程写这个数据库文件;强制性锁会让内核检查每一个open,read和write,验证调用进程是否违背正在访问的文件上的一把锁。
<span style="font-size:18px;"><span style="font-size:18px;">#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int fd;
pid_t pid;
char buf[5];
struct stat statbuf;
if(argc != 2){
fprintf(stderr, "usage: %s filename\n", argv[0]);
exit(1);
}
if((fd = open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
err_sys("open error");
if(write(fd, "abcdef", 6) != 6)
err_sys("write error");
//turn on set-group-ID and turn off group-execute
if(fstat(fd, &statbuf) < 0)
err_sys("fstat error");
if(fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
err_sys("fchmod error");
TELL_WAIT();
if((pid = fork()) < 0)
err_sys("fork error");
else if(pid > 0){
//write lock entire file
if(write_lock(fd, 0, SEEK_SET, 0) < 0)
err_sys("write_lock error");
TELL_CHILD(pid);
if(waitpid(pid, NULL, 0) < 0)
err_sys("waitpid error");
}
else{
WAIT_PARENT();
set_fl(fd, O_NONBLOCK);
if(read_lock(fd, 0, SEEK_SET, 0) != -1)
err_sys("child: read_lock succeeded");
printf("read_lock of already-locked region returns %d\n", errno);
if(lseek(fd, 0, SEEK_SET) == -1)
err_sys("lseek error");
if(read(fd, buf, 2) < 0)
err_ret("read failed (mandatory locking works)");
else
printf("read OK (no mandatory locking), buf = %2.2s\n", buf);
}
exit(0);
}
/********************************************
read_lock of already-locked region returns 11
read OK (no mandatory locking), buf = ab
********************************************/
</span></span><pre name="code" class="cpp"><span style="font-size:18px;">#include "apue.h"
ssize_t readn(int fd, void *ptr, size_t n)
{
size_t nleft;
ssize_t nread;
nleft = n;
while(nleft > 0){
if((nread = read(fd, ptr, nleft)) < 0){
if(nleft == n)
return -1;
else
break;
} else if(nread == 0){
break;
}
nleft -= nread;
ptr += nread;
}
return n - nleft;
}
ssize_t writen(int fd, void *ptr, size_t n)
{
size_t nleft;
ssize_t nwrite;
nleft = n;
while(nleft > 0){
if((nwrite = write(fd, ptr, nleft)) < 0){
if(nleft == n)
return -1;
else
break;
}
else if(nwrite == 0){
break;
}
nleft -= nwrite;
ptr += nwrite;
}
return n - nleft;
}
</span>
3. I/O多路选择
i. select函数和pselect函数
int select(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fe_set *restrict exceptfds, struct timeval *restrict tvptr); //返回准备就绪的描述符数;若超时,返回0;若出错,返回-1。
tvptr = NULL,永远等待; tvptr->tv_sec = 0 && tvptr->tv_usec == 0,不等待;当有具体值时,等待具体的时间。
对描述符集的处理和判断
int FD_ISSET(int fd, fd_set *fdset); //判断是否在描述符集中,在的话,返回非0;
int FD_CLR(int fd, fd_set *fdset); //清除描述符集的某一位
int FD_SET(int fd, fd_set *fdset); //开启描述符集的某一位
int FD_ZERO(fd_set *fdset); //把描述符集的每一位都清空
int pselect(int maxfdp1, fd_set *restrict readfds, fd_set *restrict writefds, fe_set *restrict exceptfds, const struct timespec *restrict tsptr, const sigset_t *restrict sigmask); //返回准备就绪的描述符数;若超时,返回0;若出错,返回-1。
ii. poll函数
int poll(struct pollfd fdarray[], nfds_t nfds, int timeout); //返回值的意义跟select一样
timeout = -1, 永远等待;timeout = 0,不等待;有具体值时,等待该值的毫秒数
4. 异步I/O
POSIX的异步I/O(???)
5. readv函数和writev函数
ssize_t readv(int fd, const struct iovec *iov, int iovcnt);
ssize_t writev(int fd, const struct iovec *iov, int iovcnt);
//作用:在一次函数调用中读写多个非连续缓冲区
6. readn函数和writen函数
ssize_t readn(int fd, void *buf, size_t nbytes);
ssize_t writen(int fd, void *buf, size_t nbytes);
//分别读写指定的N字节数据,并处理返回值小于要求值的情况;
<span style="font-size:18px;">#include "apue.h"
ssize_t readn(int fd, void *ptr, size_t n)
{
size_t nleft;
ssize_t nread;
nleft = n;
while(nleft > 0){
if((nread = read(fd, ptr, nleft)) < 0){
if(nleft == n)
return -1;
else
break;
} else if(nread == 0){
break;
}
nleft -= nread;
ptr += nread;
}
return n - nleft;
}
ssize_t writen(int fd, void *ptr, size_t n)
{
size_t nleft;
ssize_t nwrite;
nleft = n;
while(nleft > 0){
if((nwrite = write(fd, ptr, nleft)) < 0){
if(nleft == n)
return -1;
else
break;
}
else if(nwrite == 0){
break;
}
nleft -= nwrite;
ptr += nwrite;
}
return n - nleft;
}
</span>
7. 存储映射I/O
能将一个磁盘文件映射到存储空间中的一个缓冲区上,当从缓冲区读取数据时,就相当于读文件中的字节。(在不使用read和write的情况下执行I/O)
void *mmap(void *addr, size_t len, int port, int flag, off_t off); //若成功返回映射区的起始地址;若出错返回MAP_FAILED
//mprotect可以更改一个现有映射的权限
int mprotect(void *addr, size_t len, int prot);
int msync(void *addr, size_t len, int prot); //如果共享映射中的页已修改,那么可以调用msync将该页冲洗到被映射的文件中
int munmap(void *addr. size_t len); //解除映射区
#include <fcntl.h>
#include <sys/mman.h>
#define COPYINCR (1024 * 1024 * 1024)
int main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
size_t copysz;
struct stat sbuf;
off_t fsz = 0;
if(argc != 3)
err_quit("usage: %s <fromfile> <tofile>", argv[0]);
if((fdin = open(argv[1], O_RDONLY)) < 0)
err_sys("can't creat %s for reading", argv[1]);
if((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
err_sys("can't creat %s for writing", argv[2]);
if(fstat(fdin, &sbuf) < 0)
err_sys("fstat error");
if(ftruncate(fdout, sbuf.st_size) < 0)
err_sys("ftruncate error");
while(fsz < sbuf.st_size){
if((sbuf.st_size - fsz) > COPYINCR)
copysz = COPYINCR;
else
copysz = sbuf.st_size - fsz;
if((src = mmap(0, copysz, PROT_READ, MAP_SHARED, fdin, fsz)) == MAP_FAILED)
err_sys("mmap error for input");
if((dst = mmap(0, copysz, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, fsz)) == MAP_FAILED)
err_sys("mmap error for output");
memcpy(dst, src, copysz);
munmap(src, copysz);
munmap(dst, copysz);
fsz += copysz;
}
exit(0);
}