1、非阻塞IO
•
一些操作会导致调用进程阻塞,如读取没有数据的管道、写入没有空间的管道
…
•
这种行为并不总是我们期望的,有时我们需要非阻塞
IO
:
•
操作不能完成时,函数调用将立即返回,同时设置
errno
:操作将阻塞
•
指定非阻塞的
2
种方式:
•
1
、调用
open
时,指定
O_NONBLOCK
标志
•
2
、使用
fcntl
设置一个给定描述符的阻塞标志
2、记录锁(文件锁)
•
#include <
fcntl.h
>
•
int
fcntl
(
int
filedes
,
int
cmd
, ... /*
struct
flock *
flockptr
*/ );
•
cmd
参数取值:
F_GETLK, F_SETLK, F_SETLKW
•
struct
flock {
•
short
l_type
; /* F_RDLCK, F_WRLCK, or F_UNLCK */
•
off_t
l_start
; /* offset in bytes, relative to
l_whence
*/
•
short
l_whence
; /* SEEK_SET, SEEK_CUR, or SEEK_END */
•
off_t
l_len
; /* length, in bytes; 0 means lock to EOF */
•
pid_t
l_pid
; /* returned with F_GETLK */
•
};
•
锁类型:读锁与写锁的限制关系
•
为得到一个读锁,描述符必须以读方式打开;为得到一个写锁,以写方式打开
•
死锁的检测:内核会检查死锁的情况,让其中一个进程返回错误,另外一个进程成功
•
允许文件锁越过文件结尾
3、记录锁(续)—锁的隐含继承与释放
•
1
、锁与进程和文件两者关联
进程结束后,全部文件锁释放
任何一个描述符被关闭后,当前进程施加在这个文件上的全部锁释放
•
2
、锁不会被
fork
产生的子进程继承
•
3
、通过
exec
执行一个新程序时,锁被继承
但是如果设置了描述符的close-on-exec标志,那么exec之后,锁被释放
4、记录锁(续)--建议锁与强制锁
•
如果所有进程都按照协商一致的方式加锁访问文件,那么建议锁完全能够胜任
•
强制锁机制中,内核检查每一个
open
、
read
、
write
,核实进程没有违背被访问文件上施加的锁
•
注意不是所有的操作系统都支持强制锁
•
如何激活强制锁?打开
设置
-
组
-ID
位,关闭
组执行
位
•
表现为:
-
rwx
-r-
S
---
•
关注一下其中的大写
S
,注意与小写
s
的区别
•
例子,程序为文件施加强制锁,然后换个窗口用
cat
命令查看文件,现象如何?
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
int lck(int fd)
{
struct flock lck;
lck.l_type = F_WRLCK;
lck.l_start = 0;
lck.l_whence = SEEK_SET;
lck.l_len = 0;
if( fcntl(fd, F_SETLK, &lck) < 0 )
{
return(-1);
}
return(0);
}
int main(int argc, char *argv[])
{
int fd;
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, 0777)) < 0 )
{
perror("open");
exit(0);
}
if( write(fd, "abcde\n", 6) != 6 )
{
perror("write");
exit(0);
}
/* turn on set-group-ID and turn off group-execute */
if( fstat(fd, &statbuf) < 0 )
{
perror("fstat");
exit(0);
}
if( fchmod(fd, (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0 )
{
perror("fchmod");
exit(0);
}
if( lck(fd) < 0 )
{
perror("fcntl");
close(fd);
exit(0);
}
if( fork() == 0 )
{
if( lck(fd) < 0 )
{
perror("child fcntl");
close(fd);
exit(0);
}
exit(0);
}
sleep(30);
}
5、IO多路复用技术
•
思考:如何兼顾读取两个描述符?
•
1
、轮询,浪费
CPU
时间,在多任务系统中尽量避免
•
2
、多进程,需要考虑进程同步,增加编程复杂度
•
3
、异步
IO
,不是所有的系统都支持,另外避免不了轮询
•
#include <sys/
select.h
>
•
int
select
(
int
maxfdp1,
fd_set
*restrict
readfds
,
fd_set
*restrict
writefds
,
fd_set
*restrict
exceptfds
,
struct
timeval
*restrict
tvptr
);
•
调用时,我们可以告知内核:
•
我们关心哪些描述符、对每个描述符关注哪些情况、想等待多长时间
•
返回时,内核告知我们:
•
处于就绪状态的描述符总数、每种情况下各有哪些描述符就绪
6、select函数
•
首先关注最后一个参数:
struct
timeval
*restrict
tvptr
,指定超时时间
•
struct
timeval
{
•
long
tv_sec
; /* seconds */
•
long
tv_usec
; /* and microseconds */
•
};
•
考虑
3
种情形:
•
1
、
tvptr
== NULL
,无限期等待,直至成功返回或被信号中断
•
2
、
tvptr->tv_sec == 0 && tvptr->tv_usec == 0
,压根不等待,不阻塞
•
3
、
tvptr
->
tv_sec
!= 0 ||
tvptr
->
tv_usec
!= 0
,指定超时时间,超时后
select
返回
0
•
中间的三个参数:
fd_set
*
readfds
, *
writefds
, *
exceptfds
•
每一个都是指针,指向描述符的集合,可以为
NULL
(表示不关心对应的情况)
•
4
个相关的宏:
•
#include <sys/
select.h
>
•
void
FD_ZERO
(
fd_set
*
fdset
);
•
void
FD_CLR
(
int
fd
,
fd_set
*
fdset
);
•
void
FD_SET
(
int
fd
,
fd_set
*
fdset
);
•
int
FD_ISSET
(
int
fd
,
fd_set
*
fdset
);
•
第一个参数
int
maxfdp1
,表示:
•
所有三个集合中最大的描述符
+1
,换句话说,就是所关心的描述符的数目
•
select
函数的返回值:
•
1
)
-1
表示错误发生(捕获到一个信号,此时描述符集合将保持不变)
•
2
)
0
表示超时,此时所有描述符集合被清
0
•
3
)正数,表示准备好的描述符的数目
•
什么是
“
准备好
”
?
•
1
)读取
readfds
中的描述符不会阻塞时
•
2
)写入
writefds
中的描述符不会阻塞时
•
3
)
exceptfds
中的描述符发生异常时
•
4
)三个集合中,普通文件的描述符总是准备好
•
注意:描述符自身阻塞与否,并不影响
select
是否阻塞
/************************************************
select多路IO复用,同时检查多个打开的描述符的状态
************************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
int main(void)
{
int n, nfds;
char buf[32] = {0};
fd_set readfds;
struct timeval tv;
FD_ZERO(&readfds); /* 清空描述符集合 */
FD_SET(0, &readfds); /* 添加描述符 0 */
/* FD_CLR从集合中删去 fd */
tv.tv_sec = 5;
tv.tv_usec = 0; /* 微秒 */
printf("Type a word, if you don't in 5 seconds ");
printf("I'll use \"YES\":");
fflush(stdout);
nfds = select(1, &readfds, NULL, NULL, &tv);
printf("[%d]\n", nfds);
if( nfds <= 0 )
{
strcpy(buf, "YES");
}
else
{
/* 判断 0是否可读 */
if( FD_ISSET(0, &readfds) )
{
n = read(0, buf, sizeof(buf));
buf[n > 0 ? n - 1 : 0] = '\0';
}
}
printf("\nThe final word is: %s\n", buf);
exit(0);
}
•
了解:
poll
函数用另外一种方式实现了多路
IO
•
#include <
poll.h
>
•
int
poll
(
struct
pollfd
fdarray
[],
nfds_t
nfds
,
int
timeout);
/**********************************************
poll多路IO复用,同时检查多个打开的描述符的状态
**********************************************/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/time.h>
#include <poll.h>
int main(void)
{
int n, nfds;
char buf[32] = {0};
struct pollfd fds;
printf("Type a word; if you don't in 5 seconds ");
printf("I'll use \"YES\":");
fflush(stdout);
fds.fd = 0;
fds.events = POLLIN | POLLPRI;
fds.revents = 0;
nfds = poll(&fds, 1, 5000); /* 超时时间单位毫秒 */
printf("[%d]\n", nfds);
if( nfds <= 0 )
{
strcpy(buf, "YES");
}
else
{
/* 检查0 是否可读 */
if( fds.revents & POLLIN )
{
n = read(0, buf, sizeof(buf));
buf[n > 0 ? n - 1 : 0] = '\0';
}
}
printf("\nThe final word is: %s\n", buf);
exit(0);
}
7、readv和writev函数
•
#include <sys/
uio.h
>
•
ssize_t
readv
(
int
filedes
,
const
struct
iovec
*
iov
,
int
iovcnt
);
•
ssize_t
writev
(
int
filedes
,
const
struct
iovec
*
iov
,
int
iovcnt
);
•
这两个函数分别允许我们在单个系统调用中读写多个非连续的缓冲区
•
因为减少了系统调用的次数,性能将得以提升
8、
readn
和
writen
函数
•
这两个函数不是系统自带的
•
当读取或写入管道、网络、终端时,需要考虑下面两种特性:
•
1
、读操作可能返回比预期少的数据
•
2
、写操作可能写入比预期少的数据
•
所以需要考虑循环处理,示例:
ssize_t /* Read "n" bytes from a descriptor */
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); /* error, return -1 */
else
break; /* error, return amount read so far */
}
else if( nread == 0 )
{
break; /* EOF */
}
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
ssize_t /* Write "n" bytes to a descriptor */
writen(int fd, const void *ptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
nleft = n;
while( nleft > 0 )
{
if( (nwritten = write(fd, ptr, nleft)) < 0 )
{
if( nleft == n )
return(-1); /* error, return -1 */
else
break; /* error, return amount written so far */
}
else if( nwritten == 0 )
{
break;
}
nleft -= nwritten;
ptr += nwritten;
}
return(n - nleft); /* return >= 0 */
}
9、 内存映射 IO
•
内存映射
IO
技术允许将磁盘上的文件映射到内存中的一个区域,这样:
•
1
、从内存中读取,相当于读取文件内容
•
2
、写入内存,相当于写入文件
•
#include <sys/
mman.h
>
•
void *
mmap
(void *
addr
,
size_t
len
,
int
prot
,
int
flag,
int
filedes
,
off_t
off);
•
先看一下后面的图示
•
参数说明:
•
addr
指定映射的起始地址,通常指定为
0
,即让系统自行选择
•
filedes
对应被映射的文件,
len
指定长度,
off
指定偏移
•
prot
指定权限,可以是
PROT_NONE
,或
PROT_READ
,
PROT_WRITE
,
PROT_EXEC
的位或组合,
不能超越打开文件时指定的读写模式
•
flag
指定映射属性,
MAP_SHARED
表示写入映射内存直接影响对应的文件,
MAP_PRIVATE
表示映射内存是文件的一个副本
•
#include <sys/
mman.h
>
•
int
mprotect
(void *
addr
,
size_t
len
,
int
prot
); #
修改一块已映射内存的访问模式
•
int
msync
(void *
addr
,
size_t
len
,
int
flags); #
刷新缓存
•
参数
flags
,可以指定为
MS_ASYNC
或
MS_SYNC
•
int
munmap
(
caddr_t
addr
,
size_t
len
); #
取消映射
•
进程终止时,将自动取消映射;关闭文件描述符并不取消映射
•
例子,通过内存映射方式实现文件拷贝
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
int main(int argc, char *argv[])
{
int fdin, fdout;
void *src, *dst;
struct stat statbuf;
if( argc != 3 )
{
printf("usage: %s <fromfile> <tofile>\n", argv[0]);
exit(0);
}
if( (fdin = open(argv[1], O_RDONLY)) < 0 )
{
printf("can't open %s for reading\n", argv[1]);
exit(0);
}
if( (fdout = open(argv[2], O_RDWR | O_CREAT | O_TRUNC, 0777)) < 0 )
{
printf("can't creat %s for writing\n", argv[2]);
exit(0);
}
if( fstat(fdin, &statbuf) < 0 ) /* need size of input file */
{
printf("fstat error\n");
exit(0);
}
/* set size of output file */
if( lseek(fdout, statbuf.st_size - 1, SEEK_SET) == -1 )
{
printf("lseek error\n");
exit(0);
}
if( write(fdout, "", 1) != 1 )
{
printf("write error\n");
exit(0);
}
if( (src = mmap(0, statbuf.st_size, PROT_READ, MAP_SHARED, fdin, 0)) == MAP_FAILED )
{
printf("mmap error for input\n");
exit(0);
}
if( (dst = mmap(0, statbuf.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fdout, 0)) == MAP_FAILED )
{
printf("mmap error for output\n");
exit(0);
}
memcpy(dst, src, statbuf.st_size); /* does the file copy */
exit(0);
}
•
备注:
“
进程间通信
”
一章,将再次回顾内存映射
IO
技术,用以提供进程间内存共享