第十四章 高级IO
一、引言
本章内容包括非阻塞IO、记录锁、系统V流机制、I/O多路转接(select 和 poll函数)、readv和writev函数以及存储映射I\O,这些都称为高级I/O。
二、非阻塞I/O
非阻塞I/O使我们可以调用open、read和write这样的I/O操作,并使这些操作不会永远阻塞。如果这种操作不能完成,则调用立即出错返回,表示该操作如继续执行将阻塞。
对于一个给定的描述符有两种方法对其指定非阻塞I/O:
(1)如果调用open获得描述符, 则可指定O_NONBLOCK标志;
(2)对于一个已经打开的描述符,则可调用fcntl,由该函数打开O_NONBLOCK文件状态标志。
实例:长的非阻塞write
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
void set_fl(int fd, int flags);
void clr_fl(int, int);
char buf[50000];
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);
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 -= nwrite;
}
}
clr_fl(STDOUT_FILENO, O_NONBLOCK);
exit(0);
}
void set_fl(int fd, int flags)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0)
{
err_sys("fcntl F_GETFL error");
}
val |= flags;
if (fcntl(fd, F_SETFL, val) < 0)
{
err_sys("fcntl F_SETFL error");
}
}
void clr_fl(int fd, int flags)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0)
{
err_sys("fcntl F_GETFL error");
}
val &= ~flags;
if (fcntl(fd, F_SETFL, val) < 0)
{
err_sys("fcntl F_SETFL error");
}
}
此程序是一个非阻塞I/O的实例,它从标准输入读500000字节,并试图将它们写到标准输出上。该程序现将标准输出设置为非阻塞的,然后用for循环进行输出,每次write调用的结果都在标准出错上打印。函数clr_fl类似于set_fl,但与set_fl的功能相反,它清除一个或多个标志位。
三、记录锁
进程有时需要确保它正在单独写一个文件,为了向进程提供这种功能,商用UINIX系统提供了记录锁机制。
记录锁的功能是:当一个进程正在读或修改文件的某个部分时,它可以组织其他进程修改同意文件区。
四、fantl记录锁
int fcntl(int fileds, int cmd, ... /*struct lock *flockptr*/);
对于记录锁,cmd是F_GETLK, F_SETLK,或F_SETLKW,第三个参数是一个指向flock结构的直针:
struct flock{
short l_type;
off_t l_start;
short l_whence;
off_t l_len;
pid_t l_pid;
};
多个进程在一个给定的自字节上可以有一把共享的读锁,但是一个给定的字节上只能有一个进程独用的一把写锁,进一步而言,如果在一个给定字节上已经有一把或多把读锁,则不能在该字节上再加写锁;如果在一个字节上已经有一把独占性的写锁,则不能对它再加任何读锁。
F_GETLK:判断由flockkptr所描述的锁是否会被另外一把锁排斥(阻塞)。
F_SETLK:设置由flockptr所描述的锁。
F_SETLKW:这是F_SETLK的阻塞版本。
实例:加锁或解锁一个文件区域的函数:
&&
#include "apue.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 = tupe;
lock.l_start = offset;
lock_whence = whence;
lock_l.len = len;
return (fcntl(fd, cmd, &lock));
}
&&
实例:测试一个锁状态的函数
&&
#include "apue.h"
#include <fcntl.h>
pid_t
lock_test(int fd, int type, off_t offset, int whence, off_t len)
{
struct flock flock;
lock.l_type = type;
lock.l_start = offset;
lock.l_whence = whence;
lock.l_len = len;
if (fcntl(fd, F_GETLK) < 0)
{
err_sys("fcntl error");
}
if (lock.l_type == FUNLCK)
{
return 0;
}
return (lock.l_pid);
}
&&
实例:死锁检测实例
如果两个进程相互等待对方持有并且锁定的资源时,则这两个就处于死锁状态。
&&
#include "apue.h"
#include <fcntl.h>
#include "tellwait.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));
}
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 %ld\n", name, offset);
}
int main(void)
{
int fd;
pid_t pid;
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);
TELL_PARENT(getppid());
WAIT_PARENT();
lockabyte("child", fd, 1);
}
else
{
lockabyte("parent", fd, 1);
TELL_CHILD(pid);
WAIT_CHILD();
lockabyte("parent", fd, 0);
}
exit(0);
}
&&
关于记录锁的自动继承和释放有三条规则:
(1)锁与进程和文件两方面有关。
(2)由fork产生的子进程不继承父进程所设置的锁。
(3)在执行exec后,新程序可以继承原执行程序的锁。
实例:在文件整体上加锁
&&
#include <unistd.h>
#include <fcntl.h>
int lockfile(int fd)
{
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_start = 0;
fl.l_whence = SEEK_SET;
fl.l_len = 0;
return(fcntl(fd, f_SETLK, &fl));
}
&&
六:在文件尾端处加锁
负的长度值表示在指定偏移量之前的字节数。
七、建议性锁和强制性锁
考虑数据库访问例程库。如果该库中所有函数都以一致的方法处理记录锁,则称使用这些函数访问数据库的任何进程集为合作进程。
强制性锁使内核对每一个open、read和write系统调用都进行检查,检查调用进程对正在访问的文件受否违背了某一把锁的作用。
实例:确定是否支持强制性锁
&&
#include "apue.h"
#include <errno.h>
#include <fcntl.h>
#include <sys/wait.h>
#include "tellwait.h"
int lock_reg(int fd, int cmd, int type, off_t offset, int whence, off_t len);
void set_fl(int fd, int flags);
void clr_fl(int fd, int flags);
int main(int argc, char *argv[])
{
int fd;
pid_t pid;
char buf[5];
struct stat statbuf;
if (argc != 2)
{
fprintf(stderr, "uasge: %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");
}
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)
{
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);
}
}
else
{
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");
}
}
exit(0);
}
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));
}
void set_fl(int fd, int flags)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0)
{
err_sys("fcntl F_GETFL error");
}
val |= flags;
if (fcntl(fd, F_SETFL, val) < 0)
{
err_sys("fcntl F_SETFL error");
}
}
void clr_fl(int fd, int flags)
{
int val;
if ((val = fcntl(fd, F_GETFL, 0)) < 0)
{
err_sys("fcntl F_GETFL error");
}
val &= ~flags;
if (fcntl(fd, F_SETFL, val) < 0)
{
err_sys("fcntl F_SETFL error");
}
}
&&
八、STREAMS
流是系统V提供的构造内核设备驱动程序和网络协议包的一种通用方法。
流在用户进程和设备驱动程序之间提供了一条全双工通路。流无需和实际硬件设备直接会话,流也可以用来构造伪设备驱动程序。
在流首之下可以压入处理模块。这可以用ioctl命令实现。用第三章说明的函数访问流,它们是:open、close、read、write和ioctl。在VR3内核中增加了3个支持流的新函数(getmsg、putmsg和poll),在SVR4中又加了两个处理流内不同优先级波段休息的函数(getpsmg和putpmasg)。
(1)STREAMS的所有输入和输出都基于消息。流首和用户进程使用read、write、ioctl、getmsg、getpmsg、 putpmsg和putmsg交换消息。在流首、各处理模块和设备驱动程序之间,消息可以顺流而下,也可以逆流而 上。控制信息和数据由strbuf结构指定:
struct strbuf
{
int maxlen;
int len;
char *buf;
};
在我们所使用的函数中,只涉及三种消息类型,它们是:
M_DATA (I/O的用户数据);
M_PROTO(协议控制信息);
M_PCPROTO(最高优先级协议控制信息)。
(2)putymsg和putpmsg
用于将STREAMS消息写至流中,这两个函数的区别是:后者允许对指定的消息指定一个优先级波段。
(3)STREAMS ioctl操作
ioctl函数能做其它I/O函数不处理的事情,STREAMS系统就继承了这种传统。
实例:isastream函数
#include <stropts.h>
&&
#include <unistd.h>
int isastream(int fd)
{
return(ioctl(fd, I_CANPUT, 0) != 1);
}
&&
实例:测试isastream函数
&&
#include "apue.h"
#include <fcntl.h>
#include <stropts.h>
#include <unistd.h>
int isastream(int fd)
{
return(ioctl(fd, I_CANPUT, 0) != -1);
}
int main(int argc, char *argv[])
{
int i, fd;
for (i = 1; i < argc; i++)
{
if ((fd = open(argv[i], O_RDONLY)) < 0)
{
err_ret("%s: can't open", argv[i]);
continue;
}
if (isastream(fd) == 0)
{
err_ret("%s: not a stream", argv[i]);
}
else
{
err_msg("%s: streams device", argv[i]);
}
}
exit(0);
}
&&
编译APUE:apue.2e
http://www.cnblogs.com/feiling/archive/2012/02/15/2353286.html
实例:列表流中的模块名
如果ioctl的参数request是L_LIST, 则系统返回已压入该流所有模块的名字,包括最顶端的驱动程序。
&&
#include "apue.h"
#include <fcntl.h>
#include <stropts.h>
int main(int argc, char *argv[])
{
int fd, i, nmods;
struct str_list list;
if (argc != 2)
{
err_quit("usage: %s <pathname>", argv[0]);
}
if ((fd = open(argv[1], O_RDONLY)) < 0)
{
err_sys("can't open %s", argv[i]);
}
if (isastream(fd) == 0)
{
err_quit("%s is not a stream", argv[1]);
}
if ((nmods = ioctl(fd, I_LIST, (void *) 0)) < 0)
{
err_sys("I_LIST error for nmods");
}
printf("#modules = %d\n", nmods);
list.sl_modlist = calloc(nmods, sizeof(struct str_mlist));
if (list.sl_modlist == NULL)
{
err_sys("calloc error");
}
list.sl_nmods = nmods;
if (ioctl(fd, I_LIST, &list) < 0)
{
err_sys("I_LIST error for list");
}
for (i = 1; i <= nmods; i++)
{
printf(" %s: %s\n", (i == nmods) ? "driver" : "module", list.sl_modlist++->l_name);
}
exit(0);
}
&&
(4)写(write)至STREAMS设备
(5)写模式
可以用两个ioctl命令取得和设置一个流的写模式。如果将request设置为I_GWRPORT,第三个参数设置为指向一 个整型变量的指针,则该流的当前写模式在该整型量中返回。
(6)getmsg和getpmsg函数
使用read、getmsg和getpmsg函数从流首读STREAMS消息。
(7)读模式
实例:用getmsg将标准输入复制到标准输出
&&
#include "apue.h"
#include <stropts.h>
#define BUFFSIZE 4096
int main(void)
{
int n, flag;
char ctlbuf[BUFFSIZE], datbuf[BUFFSIZE];
struct strbuf ctl,dat;
ctl.buf = ctlbuf;
ctl.maxlen = BUFFSIZE;
dat.buf = datbuf;
dat.maxlen = BUFFSIZE;
for ( ; ; )
{
flag = 0;
if ((n = getmsg(STDIN_FILENO, &ctl, &dat, &flag)) < 0)
{
err_sys("getmsg error");
}
fprintf(stderr, "flag = %d, ctl.len = %d, dat.len = %d\n", flag, ctl.len, dat.len);
if (dat.len == 0)
{
exit(0);
}
else if (dat.len > 0)
{
if (write(STDOUT_FILENO, dat.buf, dat.len) != dat.len)
{
err_sys("write error");
}
}
}
}
&&
九、I/O多路转接
poll、pselect和select这三个函数使我们能够执行I/O多路转接。
在所有依从POSIX的平台上,select函数使我们可以执行I/O多路转接。
poll函数类似于select,但是其程序员接口则有所不同。
十、readv和writev函数
readv和write函数用于在一次函数调用中读、写多个非连续缓冲区。
十一、readn和writen函数
readn和writen的功能是读、写指定的n字节数据,并处理返回值小于要求值的情况。这两个函数只是按需多次调用read和write直至读、写了n字节数据。
实例:readn和writen函数
&&
#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, const void *ptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
nleft = n;
while(n < 0)
{
if ((nwritten = write(fd, ptr, nleft)) < 0)
{
if (nleft == n)
{
return(-1);
}
else
{
break;
}
}
nleft -= nwritten;
ptr += nwritten;
}
return(n - left);
}
&&
十二、存储映射I/O
存储映射I/O使一个磁盘文件与存储空间中的一个缓冲区相映射。于是当从缓冲区中取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应字节就自动的写入文件。这样就可以在不使用read和write的情况下执行I/O.
实力:用存储映射I/O复制一个文件(类似于cp(1)命令)
&&
#include "apue.h"
#include <fcntl.h>
#include <sys/mman.h>
int main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
struct stat statbuf;
if (argc != 3)
{
err_quit("usage: %s <fromfile> <tofile>", argv[0]);
}
if ((fdin = open(argv[1], O_RDONLY)) < 0)
{
err_sys("can't open %s for reading", argv[1]);
}
if ((fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE)) < 0)
{
err_sys("can't create %s for writing", argv[2]);
}
if (fstat(fdin, &statbuf) < 0)
{
err_sys("fstat error");
}
if (lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1)
{
err_sys("lseek error");
}
if (write(fdout, "", 1) != 1)
{
err_sys("write error");
}
if ((src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED)
{
err_sys("mmap error");
}
if ((dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED)
{
err_sys("mmap error for output");
}
memcpy(dst, src, statbuf.st_size);
exit(0);
}
&&