Linux系统编程学习笔记(一)File I/O

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的不同点:

  1. 使用timespec而不是timeval结构作为timeout参数,timespec使用秒和纳秒,理论上更高级,但是实际上即使是微妙也不是可靠地。
  2. pselect不会修改timeout参数,所以timeout不需要重新初始化。
  3. 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优点:

  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){//写到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在内存中保存了从磁盘文件系统中最近访问的数据。利用局部性原理和预先读技术,减少磁盘的访问次数。

参考:

  1. 《Linux system programming》
  2. 《Unix system programing》
  3. 《Advanced Programming in the Unix Environment》
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值