`

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
分享到:
评论

相关推荐

    Linux系统编程学习笔记

    ### Linux系统编程学习笔记 #### 一、IO **1.1 标准I/O (stdio)** - **fopen/fclose**: `fopen` 用于打开或创建一个文件,并返回一个指向该文件的 `FILE *` 类型的指针。`fclose` 用于关闭由 `FILE *` 指向的文件...

    Linux系统编程笔记.docx

    【Linux系统编程笔记】 在Linux系统编程中,掌握基本的命令和系统操作是至关重要的。以下是一些核心知识点: 1. **Linux命令基础** - `date`:用于显示系统当前时间,可以用来获取系统时间戳。 - `cat /etc/...

    JAVA JDK学习笔记i\o部分

    ### JAVA JDK 学习笔记 i/o 部分 #### 输入/输出 (I/O) 概述 在程序设计中,输入/输出(Input/Output,简称I/O)是指计算机程序与外部设备(如硬盘、视频设备、网络主机等)之间进行数据交换的过程。由于涉及的...

    Linux学习笔记——入门资料

    Linux学习笔记——入门资料 Linux,作为一款开源、免费的操作系统,因其稳定性和安全性而备受开发者和系统管理员的青睐。这份“Linux学习笔记”旨在帮助初学者快速掌握Linux的基础知识和操作技能,从而轻松入门。 ...

    Linux服务器Shell编程学习笔记linux操作系统 电脑资料.pdf

    Linux服务器Shell编程是系统管理员和开发者在日常工作中必备的技能之一。Shell脚本是一种通过Shell解释器执行的文本文件,可以包含一系列命令,用于自动化任务和管理系统。在Linux操作系统中,常用的Shell包括bash、...

    LINUX与UNIX_Shell编程指南V1.0_学习笔记.docx

    这份学习笔记将深入探讨Shell编程的基础,包括文件权限与安全,这是理解Linux和Unix系统管理的关键。 首先,我们关注文件权限。在Linux和Unix中,每个文件和目录都有三个基本的权限:读(r)、写(w)和执行(x)。...

    Linux_shell编程学习笔记

    Linux Shell编程是Linux系统管理与自动化任务中的重要一环,主要通过编写脚本来实现对操作系统进行交互和控制。本文将详细解析Linux Shell编程中的几个关键概念:正则表达式、find命令、grep命令以及sed命令。 1. *...

    Linux编程精髓 部分笔记

    ### Linux编程精髓部分知识点 #### 用户级内存管理 在Linux编程中,用户级内存管理是极为重要的一个方面,它涉及...以上是对“Linux编程精髓部分笔记”所涉及知识点的详细解释和总结,希望对学习Linux编程有所帮助。

    shell脚本编程学习笔记汇总

    shell脚本编程学习笔记汇总 本文档总结了 Linux shell 脚本编程的学习笔记,涵盖了 shell 脚本的定义、编写、权限、存放位置、函数、变量、IF 控制语句、命令退出状态等知识点。 一、shell脚本的定义 shell 脚本是...

    Linux学习笔记(强悍总结值得一看)

    这些只是Linux学习笔记的一部分,完整的笔记还包括更多关于文件系统操作、权限管理、进程控制、网络配置、脚本编程等多个方面的内容。掌握这些基础技能是成为Linux运维人员的必备条件,通过不断的实践和学习,可以更...

    linux0.11 内核学习笔记

    这份“Linux0.11 内核学习笔记”详细解读了这个早期内核的结构、工作原理以及相关的编程技术。下面,我们将深入探讨其中的关键知识点。 一、内核架构 Linux 0.11内核采用单内核设计,所有的系统服务都集中在一个可...

    嵌入式linux学习笔记

    本学习笔记旨在为初学者提供一个全面了解和掌握嵌入式Linux的基础平台。 一、嵌入式系统概述 嵌入式系统是集成在硬件设备中,用于特定功能的计算机系统。它们通常比个人电脑更小、功耗更低,且针对性强。嵌入式...

    Linux Kernel学习笔记

    Linux Kernel 学习笔记主要涵盖了操作系统核心的多个关键领域,包括存储器寻址、设备驱动程序开发、字符设备驱动程序、PCI设备、内核初始化优化宏、访问内核参数的接口、内核初始化选项、内核模块编程、网络子系统等...

    201808达内大数据Linux阶段学习课后笔记

    笔记会讨论如何分析系统性能,例如使用`vmstat`, `iostat`, `sar`, `free`等工具,以及如何调整内核参数来优化I/O、内存和CPU使用。 最后,笔记可能会涉及一些高级主题,如Linux容器技术(Docker)、虚拟化(KVM或...

    linux入门学习笔记

    ### Linux 入门学习笔记 #### 一、Linux 安装与配置 ##### 1. Linux 的安装方式 - **虚拟机安装**:通过虚拟化技术,在现有操作系统上模拟一个完整的计算机环境,安装 Linux。 - **安装虚拟机软件**: - **...

    linux c学习笔记

    Linux C 学习笔记 在深入探讨Linux C编程之前,我们先理解一下C语言和Linux操作系统的基本概念。C语言是一种强大的、高效的编程语言,被广泛用于系统编程、嵌入式开发以及各种软件开发中。而Linux则是一个开源的、...

    linux 学习笔记 java

    Linux学习笔记,特别是对于新手来说,是一条通往操作系统深度理解的必经之路。Linux系统以其开源、稳定和高效的特点在IT行业中占据着重要的位置。Java作为广泛应用的编程语言,经常需要在Linux环境下运行,而Tomcat...

Global site tag (gtag.js) - Google Analytics