Linux系统编程--IO系统调用

本文详细介绍了Linux系统中的I/O操作,包括open()打开文件、read()和write()读写文件、close()关闭文件、lseek()定位读写、fsync()同步IO和select()实现I/O多路复用的基本概念、函数详解和示例。
摘要由CSDN通过智能技术生成

一、I/O系统调用

1.open() 打开文件

1.1 所需基础知识

Linux遵循一切皆是文件的理念,因此,很多的交互工作都是通过读取和写入文件来完成。
文件必须被打开才能被访问。文件可以以只读方式或者只写方式打开,或者两者兼有。一个打开的文件通过唯一的文件描述符进行引用,该描述符是打开文件的元数据至其本身的映射在Linux内核中,这个描述符,用一个整数表示 (int) ,简写为fd。文件描述符在用户空间中共享,允许用户程序用文件描述符直接访问文件。

1.2. open() 详解

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

//调用成功返回文件描述符,失败返回-1

int open (const char* name, int flags);

int open (const char* name, int flags, mode_t mode);

flags参数必须是一下之一:

参数含义
O_RDONLY只读
O_WRONLY只写
O_RDWR读写模式

flags参数可以和以下一个或多个值进行按位或运算,用以修改打开文件请求的行为。

参数含义
O_APPEND追加模式打开,文件位置指针将被置于文件末尾
O_ASYNC当指定文件可写或者可读时产生一个信号(默认SIGIO)
O_CREAT当指定的name文件不存在时,将由内核来创建
O_DIRECT打开文件用于直接I/O
O_DIRECTORY如果name不是目录,open()调用将会失败,这个标志用于opendir()内部使用
O_EXCL和O_CREAT一起给出的时候,如果name给定的文件已经存在,则open()调用失败,用来防止文件创建时出现竞争
O_LARGEFILE给定文件打开时使用64位偏移量,这样大于2G的文件也能被打开
O_NOFOLLOW如果name是一个符号链接,open()调用会失败
O_NONBLOCK如果可以,文件将在非堵塞模式下打开
O_SYNC打开文件用于同步IO
O_TRUNC如果文件存在,且为普通文件,并允许写,将文件的长度截断为0

1.3 示例代码

以打开 /etc/services 文件为例

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

int main()
{

	int fd;
	fd = open("/etc/services",O_RDONLY);
  	if(fd == -1)
 	{
  	 cerr << "open() failed";
  	 return 0;
 	}

  else
	{

	cout << "open success" << endl;
	cout << "fd = " << fd << endl;
	}

	close(fd);
	return 0;

}

效果图:
在这里插入图片描述

2.read() 读取文件

2.1.基础知识

ssize_t 其实是有符号整数类型
size_t 是无符号整数类型

2.2.read() 详解

#include <unistd.h>

ssize_t read (int fd, void* buf, size_t len);

2.2.1.功能:

该系统调用从有fd指向的文件的当前偏移量至多读len个字节到buf中。成功返回写入buf中的字节数。出错则返回-1,并设置errno

2.2.2.返回结果情况:

1. 调用返回一个等于len的值。
所有len个被读取字节存储在buf中。结果和预期一致。

以 读取 /etc/services 文件为例,读取文件中20个字节

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

int main()
{

	int fd;
	
	fd = open("/etc/services",O_RDONLY);  //以只读方式打开文件

	if(fd == -1)		//文件打开失败,并提示错误
	{
		cerr << " open failed ";
		return 0;

	}

	char* buf;
	ssize_t length;
	length = read(fd, buf, 20);
	
	if(length == -1)  //读文件失败
	{
	
		cerr << "read failed" << endl;
		close(fd);
        return 0;

	}

	else
	cout << "读取字节数 = " << length << endl;  //读文件成功
	close(fd);
	return 0;	

}

效果图:
在这里插入图片描述

2.调用返回一个大于0,但是小于len的值。

  • 读数据期间,被一个信号打断了读取的过程。
  • 读取的数据,本身就少于指定的字节数。
  • 读取的过程,出现了一个错误。

测试样例 : 测试文件数据本身少于指定读取字节数的情况。文件本身有abcdef,而指定读取20字节数据。

sample.txt 文件内容

abcdef
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

int main()
{

	int fd;
	
	fd = open("sample.txt",O_RDONLY);  //以只读方式打开文件

	if(fd == -1)		//文件打开失败,并提示错误
	{
		cerr << " open failed ";
		return 0;

	}

	char* buf;
	ssize_t length;
	length = read(fd, buf, 20);
	
	if(length == -1)  //读文件失败
	{
	
		cerr << "read failed" << endl;
		close(fd);
        return 0;

	}

	else
	cout << "读取字节数 = " << length << endl;  //读文件成功

	close(fd);
	return 0;	

}

效果图:
在这里插入图片描述
文件内容为abcdef ,6字节的数据,为什么读取字节数为7?

我们打印一下读取的数据
在这里插入图片描述

内容也正确,是6个字节。为什么显示7? 难道是最后的 ‘\0’ 占了一个字节吗?

3.调用返回0 这标志着EOF。没有可以读入的数据

测试:
读一个空文件。

代码: a.txt 为空文件

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

int main()
{

	int fd;
	
	fd = open("a.txt",O_RDONLY);

	if(fd == -1)
	{
		cerr << " open failed ";
		return 0;

	}

	char* buf;
	ssize_t length;
	length = read(fd, buf, 20);
	
	if(length == -1)
	{
	
		cerr << "read failed" << endl;
		close(fd);
        return 0;

	}

	else
	cout << "读取字节数 = " << length << endl;
    cout << "内容 " << buf << endl;
	close(fd);
	return 0;	

}

效果图:
在这里插入图片描述
4.调用返回-1
在这里插入图片描述

2.3. 读入所有字节

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>

using namespace std;

int main()
{

	int fd;
	
	fd = open("/etc/services",O_RDONLY);

	if(fd == -1)
	{
		cerr << " open failed ";
		return 0;

	}

	char* buf;
	ssize_t ret;
	int len = 10240;  //指明读取的大小

	while(len != 0 && (ret = read(fd, buf, len) != 0))
	{
	
		if(ret == -1)
		{
			if(errno == EINTR) 
				continue;

			cerr << "read failed" << endl;
        	break;

		}

		else
		{
			len -= ret;
			buf += ret;
			
		}
		
	}
	close(fd);
	cout << "len=" << len << endl; //测试程序是否真正的读好几次。
	return 0;	

}

效果图:
在这里插入图片描述

3.write() 写文件

3.1. 基础背景知识

一个write() 调用从由文件描述符fd引用文件的当前位置开始,将buf中至多count个字节写入文件中。不支持定位的文件(像字符设备)总是从“开头”开始写。
成功时,返回写入字节数,并更新文件位置。错误时,返回-1,并将errno设置为相应的值。

3.2.write() 详解

#include <unistd.h>

ssize_t write(int fd, const void* buf, size_t count);

3.3.示例代码

测试: 从 /etc/services 文件中读取100字节,并把这100字节写入到a.txt 文件中。

text 1.不以追加模式写

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

using namespace std;

int main()
{

	int fd;
	fd = open("/etc/services", O_RDONLY);
	if(fd == -1)
	{
		cerr << "open()" << endl;
		return 0;
	}

	char* buf;
	int len = read(fd,buf,100);
	if(len == -1)
	{
		cerr << "read()" << endl;
		close(fd);
		return 0;
	}
	
	int fd1;
	fd1 = open("a.txt",O_WRONLY);
    if(fd1 == -1)
	{
	
		cerr <<" open(a.txt)" << endl;
		close(fd);
		return 0;

	}

	int count = write(fd1,buf,len);
	if(count == -1)
	{
		perror("write()");
		close(fd);
		return 0;
	}
	else if(count != len)
	{
		cerr << "write() 写入字数不对";
		close(fd);
		close(fd1);
		return 0;	
	}
	
	cout << "ok" << endl;
	return 0;
}

效果图:
在这里插入图片描述

text2.追加模式进行写,也就是以追加模式打开文件

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

using namespace std;

int main()
{


	const char* buf = "abcdefghijklmnopqrstuvwxyz";
	
	int fd1;
	fd1 = open("a.txt",O_WRONLY |O_APPEND);
    if(fd1 == -1)
	{
	
		cerr <<" open(a.txt)" << endl;
		return 0;

	}

	int count = write(fd1,buf,strlen(buf));
	if(count == -1)
	{
		perror("write()");
		close(fd1);
		return 0;
	}
	else if(count != strlen(buf))
	{
		cerr << "write() 写入字数不对";
		close(fd1);
		return 0;	
	}
	
	cout << "ok" << endl;
	return 0;
}

效果图:
在这里插入图片描述

3.4.注意点

当一个write()调用返回时,内核已将所提供的缓冲区数据复制到了内核缓冲区中。但没有保证数据已写到了目的文件。你可以强制文件缓存写回,甚至可以将所有的写操作同步。

3.4.1.同步IO
1. fsync() 和fdatasync()

最简单的确认数据写入磁盘的方法是使用fsync()系统调用,

#include <unistd.h>

int fsync(int fd);

调用fsync()可以保证fd对应文件的脏数据回写到磁盘上。

该调用回写数据以及建立的时间戳和inode中的其它属性等元数据。在驱动器确认数据已经全部写入之前不会返回。

#include <unistd.h>

int fdatasync(int fd);

这个系统调用完成的事情和fsync()一样,区别在于它仅写入数据。不保证元数据同步到磁盘上。故此可能快一些。一般来说这就够用了。

返回值情况
成功时,两个调用都返回0.失败时,都返回-1.并将errno设置为一下三个值之一:

名称含义
EBADF给定的文件描述符不是一个可以写入的合法描述符
EINVAL给定的文件描述符对应的对象不支持同步
EIO在同步时发生了一个底层I/O错误
2.sync()

sync()系统调用可以用来对磁盘上所有的缓冲区进行同步。尽管效率不高,但仍然被广泛使用。

#include <unistd.h>

void sync();

该函数没有参数,也没有返回值。它总是成功返回,并确保所有的缓冲区(包括数据和元数据)都可以写入磁盘。

4.close() 关闭文件

程序完成对某个文件的操作后,可以使用close()系统调用将文件描述符和对应的文件解除关联。

#include <unistd.h>

//调用成功返回0.错误返回-1,并设置errno为相应值
int close(int fd); 

5.lseek() 查找

lseek()系统调用能够对给定文件描述符引用的文件位置设置指定值。除了更新文件位置,没有其它的行为,并无论如何不初始化任何I/O

#include <sys/types.h>
#include <unistd.h>

off_t lseek(int fd, off_t pos, int origin);

lseek() 的行为依赖于初始参数,可以为一下值之一:

名称含义
SEEK_CUR当前文件位置fd设置为当前值加上pos,pos可以为负值,零或正值
SEEK_END当前文件位置fd设置为当前文件长度加上pos,pos可以为负值,零或正值
SEEK_SET当前文件位置fd设置为pos。

调用成功时返回新文件位置。错误时返回-1,并设置适当的errno值。
在这里插入图片描述

6.定位读写

Linux提供了两种read()和write()的变体来代替lseek(),每个调用都以需要读写的文件位置为参数。完成时,不修改文件位置。

#define _XOPEN_SOURCE 500

#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);

错误码
成功时,两个调用返回读或写的字节数。出错时,二者均返回-1

6.1代码示例

示例文件 a.txt,内容为

abcdefghigklmnopqrstuvwxyz
ABCDEFGHIGKLMNOPQRSTUVWXYZ

1.测试pread

距文件开始位置5的位置开始读26个字节

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

using namespace std;

int main()
{

	int fd;
	
	fd = open("a.txt",O_RDONLY);

	if(fd == -1)
	{
		cerr << " open failed ";
		return 0;

	}

	char* buf;
	ssize_t length;
	length = pread(fd, buf, 26,5);
	
	if(length == -1)
	{
	
		cerr << "pread failed" << endl;
        return 0;

	}

	else
	cout << "读取字节数 = " << length << endl;
	cout << buf << endl;
	return 0;	

}

效果图:
在这里插入图片描述

2.测试pwrite()

从距文件开始位置的26处,写入-Hello,Word-

#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

using namespace std;

int main()
{

	const char* buf = "-Hello,Word-";
	
	int fd1;
	fd1 = open("a.txt",O_WRONLY);
    if(fd1 == -1)
	{
	
		cerr <<" open(a.txt)" << endl;
		
		return 0;

	}

	int count = pwrite(fd1,buf,strlen(buf),26);
	if(count == -1)
	{
	
		perror("write()");
		return 0;
	}
	else if(count != strlen(buf))
	{
		cerr << "write() 写入字数不对";
	
		close(fd1);
		return 0;	
	}
	
	cout << "ok" << endl;
	return 0;
}

效果图:
在这里插入图片描述
如果原来位置上有数据,那么写入的数据会把原来的数据覆盖掉

7.截短文件

7.1 代码详解

#include <sys/types.h>

int ftruncate (int fd, off_t len);
#include <unistd.h>
#include <sys/types.h>

int truncate (const char* path, off_t len);

两个系统调用都将文件截短到len指定的长度。
二者都在成功时返回0,错误时返回-1,并设置errno为相应值。

8.I/O多路复用

8.1.基础知识

在这里插入图片描述

I/O多路复用允许应用在多个文件描述符上同时堵塞,并在其中某个可以读写时收到通知。

I/O多路复用的设计遵循以下原则:

  1. 当任何文件描述符准备好I/O时通知我
  2. 在一个或更多文件描述符就绪前始终处于睡眠状态
  3. 唤醒:哪个准备好了?
  4. 在不阻塞的情况下处理所有I/O就绪的文件描述符
  5. 返回第一步,重新开始

8.2.select() 实现同步I/O多路复用

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

//成功返回时,每个集合只包含对应类型的I/O就绪的文件描述符
// 参数n,等于所有集合中文件描述符的最大值加1.
int select (int n, fd_set* readfds, fd_set* writefds,fd_set* exceptfds,
			struct timeval* timeout);


监测的文件描述符功能
监测 readfds 集合中的文件描述符确认其中是否有可读数据
监测 writefds 集合中的文件描述符确认其中是否有一个写操作可以不阻塞地完成
监测 exceptefds 中的文件描述符确认其中是否出现异常发生或者出现带外数据
指定集合为NULLselect对此不监听
#include <sys/time.h>


struct timeval{
	long tv_sec;  //秒
	long tv_usec; //微秒

}

如果这个参数不是null,即使此时没有文件描述符处于I/O就绪状态,select()调用也将在tv_sec秒tv_usec微秒后返回。

集合中的文件描述符

集合中的文件描述符是通过辅助宏来进行管理。



FD_CLR(int fd, fd_set* set);  	//从指定集合中移除一个文件描述符

FD_ISSET(int fd, fd_set* set); // 判断fd是否在set集合中,

FD_SET(int fd, fd_set* set);  //向指定集合中添加一个文件描述符

FD_ZERO(fd_set* set);        //从指定集合set中移除所有文件描述符

8.2.1.代码示例

等待stdin的输入的阻塞时限设置为5秒钟。由于只监测了一个文件描述符,实际上不上I/O多路复用。

#include <iostream>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

using namespace std;
#define TIMEOUT 5  //等待5秒
#define BUF_SIZE 1024

int main()
{
	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)
	{
		cerr << "select()" << endl;
		return 1;
	} 
	else if(!ret)
	{
		cout << TIMEOUT << 's' << endl;
		return 0;	
	}

   	
	if(FD_ISSET(STDIN_FILENO, &readfds))
	{
		char buf[BUF_SIZE + 1];
		int len;
		len = read(STDIN_FILENO, buf,BUF_SIZE);
		
		if(len == -1)
		{
			cerr << "read()" << endl;
			return 1;
		}
		
		if(len)
		{
			buf[len] ='\0';
			cout << buf << endl;

		}

		return 0;
	}
	
	cerr << "this should not happen!" << endl;	
	
		return 0;

}

效果图

会等待5秒,然后才输出结果

在这里插入图片描述

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值