Linux系统编程学习笔记(一)File I/O
File I/O
1、打开文件
int open(const char *name, int flags);
int open(const char *name,int flags, mode_t mode);
flags:必须是O_RDONLY,O_WRONLY,ORDWR之一加上其他选项:
O_APPEND:append方式
O_ASYNC:终端和socket文件可用,默认产生SIGIO信号
O_CREAT:不存在则创建,已经存在则不起作用如果没有O_EXCL
O_DIRECT:作为直接I/O打开。
O_DIRECTORY:打开目录,供内部opendir使用
O_EXCL:如果和O_CREAT一起使用,如果文件存在,则失败。
O_LARGEFILE: 使用64-bit的offset,打开超过2G的文件
O_NOCTTY:name终端打开不是控制终端
O_NOFOLLOW: 如果是软链则失败。
O_NONBLOCK:open和其他操作都不阻塞。只用于FIFOs
O_SYNC:同步I/O,写操作都同步到磁盘上
O_TRUNC:如果文件已经存在,清空在写。
mode:与flags的O_CREAT结合。表示新创建文件的权限。
错误返回 -1
2、creat
int creat(const char *name,mode_t mode);
Ken Thompson童鞋把create写成了creat,是他设计Unix中最大的遗憾,呵呵。
等价于open的flags是O_WRONLY | O_CREAT | O_TRUNC
错误返回 -1
3、read:
ssize_t read(int fd,void *buf, size_t len);
考虑EOF, 被信号中断,错误。 如果想读取len bytes (至少遇到EOF),需要
ssize_t ret;
while (len != 0 && (ret = read (fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)//被信号中断
continue;
perror ("read");
break;
}
len -= ret;
buf += ret;
}
Nonblocking读: 不阻塞,立即返回,并指示没有数据可以读,并return -1,设置errno为EAGAIN
char buf[BUFSIZ];
ssize_t nr;
start:
nr = read (fd, buf, BUFSIZ);
if (nr == -1) {
if (errno == EINTR)//被信号中断
goto start; /* oh shush */
if (errno == EAGAIN)//暂时还没数据
/* resubmit later */
else
/* error */
}
4、write:
ssize_t write(int fd,const void *buf, size_t count);
和read类似,检查部分写的问题: 对于普通文件,write保证不会出项部分写的问题,但是其他类型文件比如socket, 可能出现,所以可以用一个循环。
ssize_t ret, nr;
while (len != 0 && (ret = write (fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror ("write");
break;
}
len -= ret;
buf += ret;
}
Linux采用延迟写,当把buf的内容拷贝到内核buffer就返回了,所以无法保证能够正确写到目的地。可以使用同步I/O。
5、同步IO:
int fsync (int fd);
使用fsync确保metadata和数据写入磁盘。
int fdatasync(int fd); //POSIX optional implements
确保把数据写入磁盘,但不包括metadata,通常这个足够了。
void sync (void);
所有的buffer写入磁盘。
O_SYNC作为open参数:
fd = open (file, O_WRONLY | O_SYNC);
Direct I/O:传入O_DIRECT flag到open( )。 直接从用户的buffer拷贝到device,bypassing 页cache 文件的offset必须是设备扇区的整数倍,通常是512,2.4内核需要和文件系统逻辑块对其(4KB)
6、关闭文件:
int close(int fd);
error return -1;
关闭文件并不能保证文件已经写入磁盘中。
当打开的文件被unlink之后,只有关闭并将inode从内存中删除之后才会物理的删除该文件。
如果被信号中断,可能导致关闭失败,可以采用下面方法:\
#include <errno.h>
#include <unistd.h>
int r_close(int fd){
int retval;
while(retval = close(fd),retval == -1 && errno == EINTR) ;
return retval;
}
7、seeking with lseek
#include<sys/types.h>
#include<unistd.h>
off_t lseek(int fd,off_t pos,int origin);
origin:
SEEK_CUR:相对于当前位置,之后的位置为:current_pos + pos;
SEEK_END:相对于文件末尾,之后的位置为:length of file + pos;
SEEK_SET:直接设置为pos,之后的位置为:pos
seek out of the file size,然后再进行写操作会产生holes,但是holes不占用空间。带有holes的
文件被称为sparse files。
8、Poistional read and write:
#include<unistd.h>
ssize_t pread(int fd, void *buf,size_t count, off_t pos);
ssize_t pwrite(int fd, const void *buf, size_t count, off_t pos);
从pos位置开始读或者写。
和使用read/write之前加上lseek一样,但可以防止竞争条件。在lseek之后其他线程修改pos。
9、Truncating Files:
Linux提供了两个系统调用来truncating一个文件到指定的长度。
#include <unistd.h>
#include <sys/types.h>
int ftruncate(int fd, off_t len);
int ftruncate(const char *path, off_t len);
这两个操作都不改变文件的当前postion.
10、Multiplexed I/O
Linux提供了三个multiplexed I/O:select, poll, 和 epoll
select提供了同步multiplexing I/O:
同步意味着会一直等待调用成功,返回值。如果没有相应的事件发生就会一直阻塞。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
FD_CLR(int fd, fd_set *set);//从set中清除fd
FD_ISSET(int fd, fd_set *set);//判断fd是否在set中
FD_SET(int fd, fd_set *set);//将fd添加到set中
FD_ZERO(fd_set *set);//fd_set很多系统实现为bit arrays,将所有的文件描述符从fd_set中清空
调用select将会被阻塞直到所给的文件描述准备好I/O操作或则给定的timeout超时。
n 为 文件描述符集合中的最大值+1 调用者需要自行计算
readfds 被监控是否可以读
writefds 被监控是否可以写
exceptfds 被监控是否发生异常
timeout 超时时间,Linux返回时会被修改成剩余的时间。
timeval的定义:
#include <sys/time.h>
struct timeval {
long tv_sec; /*second */
long tv_usec; /* microsecods */
};
成功返回准备好I/O操作的文件描述符数,指定了timeout,则可能返回0表示超时,error返回-1
例子:
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define TIMEOUT 5
#define BUF_LEN 1024
int main(void){
struct timeval tv;
fd_set readfds;
int ret;
FD_ZERO(&readfds);
FD_SET(STDIN_FILENO,&readfds);//标准输入可读
tv.tv_sec = TIMEOUT;
tv.tv_usec = 0;
ret = select(STDIN_FILENO + 1,
&readfds,
NULL,
NULL,
&tv);
if(ret == -1){
perror("select");
return 1;
}else if(!ret){
printf("%d seconds elapse.\n", TIMEOUT);
return 0;
}
if(FD_ISSET(STDIN_FILENO, &readfds)){
char buf[BUF_LEN + 1];
int len;
len = read(STDIN_FILENO,buf,BUF_LEN);
if(len == -1){
perror("read");
return 1;
}
if(len){
buf[len] = '\0';
printf("read: %s\n", buf);
return 0;
}
}
fprintf(stderr,"This should not happen!\n");
return 1;
}
可以将select做为a portable way to sleep使用,用来睡眠指定时间
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 500;
select(0, NULL, NULL, NULL, &tv);
pselect:
#include<sys/select.h>
int pselect(int n, fd_set *readfds, fd_set *writefds,fd_set *exceptfds,
const struct timespec *timeout, const sigset_t *sigmask);
pselect和select的不同点:
- 使用timespec而不是timeval结构作为timeout参数,timespec使用秒和纳秒,理论上更高级,但是实际上即使是微妙也不是可靠地。
- pselect不会修改timeout参数,所以timeout不需要重新初始化。
- select没有sigmask参数。
pselect解决一下竞争问题: signal可能会设置一些全局的flag,调用select之前要检查flag.这样,如果信号在检查flag和调用select之间到来, 修改了flag,那么程序将会永远的阻塞。因为flag原先被设置成可以唤醒select,并且检查flag确认select确实可以被唤醒,但信号改变了flag,就导致select调用时检查flag发现并不OK。pselect解决了这个问题,相当于把检查flag和调用select作为了原子操作。
poll:
#include<sys/poll.h>
int poll(struct pollfd *fds, unsigned int nfds, int timeout);
fds pollfd的数组,用来监控是否可以读写,nfds数组的大小
timeout 超时时间。
pollfd:
#include <sys/poll.h>
struct pollfd{
int fd;/* file descriptor */
short events; /* requested event to watch,is a bitmask */
short revents; /* returned events witnessed,is a bitmask */
};
合法的events:
POLLIN: 有数据可读
POLLRDNORM:有normal data可以读
POLLRDBAND:有priority data可读
POLLPRI:有urgent data可读
POLLOUT:可以无阻塞的写
POLLWRNORM:可以无阻塞的写normal data
POLLWRBAND:可以无阻塞的写priority data
POLLMSG: 一个SIGPOLL message存在\
除了以上revents:
POLLER:文件描述符错误
POLLHUP:文件描述符发生hung up 事件
POLLINVAL:文件描述符不合法
和select相比,不需要显示指定exception fds
POLLIN|POLLPRI == select read event
POLLOUT|POLLWRBAND == select write event
POLLIN == POLLRDNORM | POLLRDBAND
POLLOUT == POLLWRNORM
timeout超时时间millisecond\
例子:
#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>
#define TIMEOUT 5
int main(void){
struct pollfd fds[2];
int ret;
fds[0].fd = STDIN_FILENO;
fds[0].events = POLLIN;//可读
fds[1].fd = STDOUT_FILENO;
fds[1].events = POLLOUT;//可写
ret = poll(fds,2,TIMEOUT * 1000);
if(ret == -1){
perror("poll");
return 1;
}
if( ! ret ){
printf("%d second elapsed.\n",TIMEOUT);
return 0;
}
if(fds[0].revents & POLLIN)
printf("stdin is readable\n");
if(fds[1].revents & POLLOUT)
printf("stdout is writable\n");
return 0;
}
不需要每次重新设置pollfd结构,revents每次会被内核先清空。
ppoll:
ppoll之poll和pselect之select一样,但是ppoll是linux特有的接口。相当于解决了原子性问题。
#include <sys/poll.h>
int ppoll(struct pollfd *fds,nfds_t nfds,const struct timespec *timeout,
const sigset_t *sigmask);
poll VS select:
poll优点:
- poll不需要用户计算最大文件描述符+1作为第一个参数
- poll对于文件描述符比较大的,更高效,select使用bit map,需要比较每一个bit
- poll文件描述符集合大小是静态的,一个固定大小的pollfd数组。
- select文件描述符结合参数作为返回值被重新构造,所以每次需要重新初始化,poll使用了分开的input(events filed)和out(revents fields),使得pollfd数组可以被重用。
- select中的timeout在返回的时候没有被定义,所以可移植的代码需要重新初始化,pselect没有这个问题。
select优点:
- select更具有可移植性,有些UNIX不支持poll
- select提供了更好的timeout机制:精确到微秒。
epoll比poll和select更高级,是Linux专有的接口,这个在第四章中介绍。
12、Redirection
1)dup2
#include <unistd.h>
int dup2(int fd1,int fd2);
dup2关闭fd2如果已经打开,将fd1拷贝到fd2。
重定向的例子:
#include <fcntl.h>
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#define CREATE_FLAGS (O_WRONLY | O_CREAT | O_APPEND)
#define CREATE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
int main(void){
int fd;
fd = open("my.file",CREATE_FLAGS, CREATE_MODE);
if(fd == -1){
perror("Failed to open my.file");
return 1;
}
if(dup2(fd,STDOUT_FILENO) == -1){//重定向到标准输出
perror("Failed to redirect standard output");
return 1;
}
if(close(fd) == -1){
perror("Failed to close the file");
return 1;
}
if(write(STDOUT_FILENO,"OK",2) == -1){//写到file
perror("Failed in writing to file");
return 1;
}
return 0;
}
结果是在myfile里面写入ok。
13、文件控制
fcntl方法是进行获取和修改打开文件描述符flags的一般方法。
#include <fcntl.h>
#include <unsitd.h>
#include <sys/types.h>
int fcntl(int fd,int cmd,/* arg */...);
cmd指定了操作,arg依赖于cmd的其他参数
cmd:
F_DUPFD: 复制文件描述符
F_GETFD:得到文件描述符的flags
F_SETFD:设置文件描述符的flags
F_GETFL:得到文件的status flag和access modes
F_SETFL: 设置文件的status flag和access modes
F_GETOWN:如果fd是一个socket,得到进程或者group ID for out-of-band signals
F_SETOWN:…,设置…
F_GETLK: 获得有arg指定的第一个block的锁
F_SETLK:设置或者清除有arg指定的段锁
F_SETLKW:和F_SETLK相同,但是他一直阻塞到请求的满足\
LK代表lock,LKW代表lock wait
例子:
1、设置无阻塞:
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int setnonblock(int fd){
if((fdflags = fcntl(fd,F_GETFL,0)) == -1)
return -1;
fdflags |= O_NONBLOCK;
if(fcntl(fd, F_SETFL,fdflags) == -1)
return -1;
return 0;
}
14、Kernal Internals:
这部分介绍了Linux内核怎么实现I/O操作,主要关注三个主要的内核子系统:Virtual FileSystem(VFS)、Page Cache、Page Writeback。
1)虚拟文件系统(VFS):作为一种抽象的机制,可以让内核调用文件系统的方法和操作文件系统的数据,而不需要知道文件系统的类型。 程序员不需要关注文件所在的文件系统类型和media,使用read,write系统调用可以操作任意支持文件系统和 media的文件。
2)Page Cache:Page Cache在内存中保存了从磁盘文件系统中最近访问的数据。利用局部性原理和预先读技术,减少磁盘的访问次数。
参考:
- 《Linux system programming》
- 《Unix system programing》
- 《Advanced Programming in the Unix Environment》