`

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>
#inlcude <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的不同点:
1、使用timespec而不是timeval结构作为timeout参数,timespec使用秒和纳秒,理论上更高级,但是实际上
即使是微妙也不是可靠地。
2、pselect不会修改timeout参数,所以timeout不需要重新初始化。
3、select没有sigmask参数。
pselect解决一下竞争问题:
signal可能会设置一些全局的flag,调用select之前要检查flag.这样,如果信号在检查flag和调用select之间到来,
修改了flag,那么程序将会永远的阻塞。pselect解决了这个问题。
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("%s 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有点:
1、poll不需要用户计算最大文件描述符+1作为第一个参数
2、poll对于文件描述符比较大的,更高效,select使用bit map,需要比较每一个bit
3、poll文件描述符集合大小是静态的,一个固定大小的pollfd数组。
4、select文件描述符结合参数作为返回值被重新构造,所以每次需要重新初始化,poll使用了
分开的input(events filed)和out(revents fields),使得pollfd数组可以被重用。
5、select中的timeout在返回的时候没有被定义,所以可移植的代码需要重新初始化,pselect没有这个问题。
select优点:
1、select更具有可移植性,有些UNIX不支持poll
2、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){
		perror("Failed in writing to file");
		return 1;
	}
	return 0;
}

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相同,但是他一直阻塞到请求的满足
例子:
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在内存中保存了从磁盘文件系统中最近访问的数据。利用局部性原理和预先读技术,减少磁盘的访问次数。

参考:
1、《Linux system programming》
2、《Unix system programing》
3、《Advanced Programming in the Unix Environment》
0
1
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics