Linux系统编程-文件IO

 man 共有九卷,系统编程也就是第二卷的内容,第五卷是文件格式和规范

open函数

函数原型

参数pathname文件名

参数flags为访问方式的宏:O_RDONLY(只读),O_WRONLY(只写),O_RDWR(读写)这三个是必须加的

O_APPEND(追加),O_CREAT(创建),O_EXCL(是否存在),O_TRUNC(截断,普通文件写操作截断为0),O_NONBLOCK(非阻塞,设置之后后面任何IO操作都不会阻塞)

参数mode_t mode是一个八进制数,0777

返回值是一个文件描述符

案例

open打开文件

文件要已存在

open创建文件,文件存在打开,不存在创建.

 open文件存在打开,截断(清空)文件,不存在创建

 open中的mode权限是 权限 & ~umask(指定权限和umask取反相与)得到最后结果

第一个0是代表八进制,其余是权限,前两个取反都是1,任何数与1相与都是本身.

最后一个是2取反就是 r-x 我们指定的权限最后一位是4也就是 r--

        r-x & r-- = r-- 也就是创建文件对于其他用户只有读权限

open函数出错

 fd输出的是-1表示失败

 文档就是错误返回-1,并设置errno

我们可以通过返回的errno,并使用sererrno(errno)显示详细信息

 

read和write函数

函数原型

read

 ssize_t 有符号整形, fd 文件描述符, buf 读取空间, size_t(无符号整形) count 读取空间的大小(字节数)

成功返回读取到的字节数,当返回0时代表读取到文件结尾

失败返回-1,设置errno

write

参数和read一样,缓存区是const表示写入时不能更改, count 写入的字节数

成功返回写入的字节数

失败返回-1,设置errno

案例

实现cp拷贝

cp aaa bbb (aaa赋值bbb)

#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <string>

using namespace std;

int main(int argc,char** argv)
{
    // 命令行参数argv[0]是文件名
    int fd = open(argv[1], O_RDONLY);
    if(fd == -1)
    {
        //标准错误可以自动显示errno信息
        //cerr是ostream类自己的错误信息
        perror("int fd = open(argv[1], O_RDONLY);");
        exit(1);
    }
    // 文件不存在创建文件 文件存在截断文件
    int fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0664);
    if(fd2 == -1)
    {
        perror("int fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0664);");
        exit(1);
    }

    int read_count;
    char buf[1024];

    // 读取成功返回读取到的字节数, 读取到文件尾部返回0, 返回-1表示失败
    while((read_count = read(fd,buf,sizeof(buf))) != 0)
    {
        if(read_count == -1)
        {
            perror("while((read_count = read(fd,buf,sizeof(buf))) != 0)");
            exit(1);
        }

        // 向文件中写入读取到的字节
        // read_count 读到多少写入多少
        write(fd2,buf,read_count);
    }

    //关闭文件和打开文件一起写,不然真的会忘!!!
    close(fd);
    close(fd2);

    return 0;
}

复习一下C++文件读写

#include <iostream>
#include <string>
#include <fstream>

using namespace std;

int main(int argc,char** argv)
{
	fstream fin(argv[1], ios::in);
	if (!fin)
	{
		cerr << "fstream fin(argv[1], ios::in)";
		exit(1);
	}

	// 文件不存在创建,文件存在清空
	fstream fout(argv[2], ios::out);
	if (!fout)
	{
		cerr << "fstream fout(argv[2], ios::out)";
		exit(1);
	}

	string str;

	//getline可以读取空格但是会舍弃换行符
	while (getline(fin,str))
	{
		fout << str << endl;
	}

	fin.close();
	fout.close();

	return 0;
}

其实我这里体现不出来,主要是键盘不舒服不爱敲.

直接说结论C++的函数会比系统函数要快

无论哪种方式,都要经过内核在由内核写到磁盘.系统函数一次写入1024字节,而C++的函数一次写入4096字节,这是因为系统函数是系统级别的缓冲(每次都直接写入到内核一次写1024字节,系统默认的最佳IO默认一般是4k,也就是说写满4k在写入到磁盘(这个不准确,因为内核怎么写入到磁盘我也不知道,但是就是这个意思))而C++函数有自己函数的缓存(也就是用户级缓冲)(它是在自己的缓存中直接写够4K=4096byte,然后一次性的交给内核,在由内核写入磁盘)

而从用户到内核的时间,就是C++函数比系统函数快的时间(当然了这个不只是C++才有的,不负责任的说主流的语言都是这样)

系统函数和库函数存在的是使用场景不同,而不是谁的效率高

文件描述符

 一个进程最多可以打开1024个文件(0-1023),系统会自动选择可用的最小的文件描述符

阻塞,非阻塞

常规文件不会阻塞,只有网络文件和设备文件才会阻塞,/dev/tty终端文件 /dev设备目录
阻塞是文件的属性,并不是函数的,文件属性可以修改.

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

using namespace std;

int main(int argc, char** argv)
{
	char buf[9];
	int read_count;

	// 读取标准输入 如果没有输入会造成阻塞
    read_count = read(STDIN_FILENO, buf, 9);
	if (read_count == -1)
	{
		perror("if (read_count = read(STDIN_FILENO, buf, 9) == -1)");
		exit(1);
	}

	// 写入标准输出
	write(STDOUT_FILENO, buf, read_count);

	return 0;
}

 没有输入会一直停留在这个界面

非阻塞处理(死循环演示效果)

当设备或网络文件设置非阻塞时,返回-1且errno=EAGIN或EWOULDBLOCK(这两个值是一样的)说明文件中无数据,而不是打开失败

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

using namespace std;

int main(int argc, char** argv)
{
	// /dev/tty 是终端 
	int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
	if (fd == -1)
	{
		perror("int fd = open( / dev / tty, O_RDONLY | O_TRUNC);");
		exit(1);
	}

	int read_count;
	char buf[9];

	while (true)
	{
		read_count = read(fd, buf, 9);
		if (read_count == -1 && errno == EAGAIN)
		{
			cout << "文件无数据" << endl;
			sleep(5);
		}
		else if (read_count == -1 && errno != EAGAIN)
		{
			perror("read_count = read(fd, buf, 64);");
			exit(1);
		}
		else
		{
			write(STDOUT_FILENO, buf, read_count);
			close(fd);

			break;
		}
	}

	return 0;
}

哦,对了,代码头文件是我一直用的一个程序改的,因为懒嘛.具体的头文件前面我都说过不影响哈.

 read返回-1,且errno是EAGAIN则显示无数据,停止五秒,等待输入,如果没有输入,依然显示无数据

有输入显示数据并跳出循环.

如果返回是-1,errno不是EAGAIN,则显示错误信息并结束程序.

非阻塞处理(只是处理,而非解决问题,解决这个问题的方法是响应模式.这个目前先不谈)

上面设置非阻塞时,无限期等待,显然不可取.(阻塞的话更是无限期的等待了)

这个时候需要考虑的时普通文件为什么没有非阻塞选项.

普通文件在读取时,无论文件有多大,终于读完的时候,而设备(网络)文件会一直等待(也就是阻塞).

处理这个问题,常用的就是超时.其实就是等待一定时间,如果没有数据,则退出程序.进行后续操作.

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

using namespace std;

int main(int argc, char** argv)
{
	// /dev/tty 是终端 
	int fd = open("/dev/tty", O_RDONLY | O_NONBLOCK);
	if (fd == -1)
	{
		perror("int fd = open( / dev / tty, O_RDONLY | O_TRUNC);");
		exit(1);
	}

	int read_count;
	char buf[9];
	int count = 3;

	while (count)
	{
		count--;

		read_count = read(fd, buf, 9);
		if (read_count == -1 && errno == EAGAIN)
		{
			cout << "文件无数据" << endl;
			sleep(2);
		}
		else if (read_count == -1 && errno != EAGAIN)
		{
			perror("read_count = read(fd, buf, 64);");
			exit(1);
		}
		else
		{
			write(STDOUT_FILENO, buf, read_count);
			close(fd);

			break;
		}
	}

	if (count == 0)
	{
		cout << "超时" << endl;
	}

	return 0;
}

 fcntl(改变一个已经打开文件的访问控制属性)

 这个函数十分强大,内容特别多.这里只介绍两个F_GETFL和F_SETFL两个命令

参数 int cmd,就是这两个命令.

失败返回-1

成功返回一个位图(就是要给二进制位表,每一个位都有各自的涵义)

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

using namespace std;

int main(int argc, char** argv)
{
	// 得到文件属性(状态)
	int flag = fcntl(STDIN_FILENO, F_GETFL);
	if (flag == -1)
	{
		perror("int flag = fcntl(STDIN_FILENO, F_GETFL);");
		exit(1);
	}

	// 设置文件属性
	flag |= O_NONBLOCK;  // 添加非阻塞状态
	fcntl(STDIN_FILENO, F_SETFL, flag);
	if (flag == -1)
	{
		perror("fcntl(STDIN_FILENO, F_SETFL, flag);");
		exit(1);
	}

	int read_count;
	char buf[9];
	int count = 3;

	while (count)
	{
		count--;

		read_count = read(STDIN_FILENO, buf, 9);
		if (read_count == -1 && errno == EAGAIN)
		{
			cout << "文件无数据" << endl;
			sleep(2);
		}
		else if (read_count == -1 && errno != EAGAIN)
		{
			perror("read_count = read(fd, buf, 64);");
			exit(1);
		}
		else
		{
			write(STDOUT_FILENO, buf, read_count);

			break;
		}
	}

	if (count == 0)
	{
		cout << "超时" << endl;
	}

	return 0;
}

 效果是一样的

位或

010 |= 001 结果就是011,和1进行按位或运算结果都是1

lseek修改文件偏移量

 off_t 是矢量

offset 偏移量

whence 偏移量起始位置

        SEEK_SET 起始位置
        SEEK_CUR 当前位置
        SEEK_END 末尾位置

成功返回从文件起始位置开始的偏移量

失败返回-1设置errno

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

using namespace std;

int main(int argc, char** argv)
{
	int fd = open(argv[1], O_RDWR | O_CREAT, 0664);
	if (fd == -1)
	{
		perror("int fd = open(argv[1], O_RDWR | O_CREAT);");
		exit(1);
	}

	char write_buf[1024] = "asfjljfkldjfkljf";

	//将内容写入文件
	write(fd, write_buf, strlen(write_buf));

	// 不添加这条语句 会读取不到 文件指针是共用一个的
	lseek(fd, 0, SEEK_SET);

	int read_count;
	char read_buf[1024];

	// 读取文件
	while (read_count = read(fd, read_buf, 1024))
	{
		if (read_count == -1)
		{
			perror("while (read_count = read(fd, ch, 1024))");
			exit(1);
		}
		cout << read_buf << endl;
	}

	close(fd);

	return 0;
}

 写文件的时候,文件指针始终在文件尾部

在读取文件的时候,依然是从文件尾部开始读取的.所以不将文件指针重新设置位置,就会读取不到数据.

这个原因是,没有读取回车(换行符)

lseek可以得到文件大小

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

using namespace std;

int main(int argc, char** argv)
{
	int fd = open("test.txt", O_RDWR | O_CREAT | O_TRUNC, 0664);
	if (fd == -1)
	{
		perror("int fd = open(test.txt, O_RDWR | O_CREAT);");
		exit(1);
	}

	char write_buf[1024] = "asfjljfkldjfkljf";

	//将内容写入文件
	write(fd, write_buf, strlen(write_buf));

	// 返回值说过,将偏移量设置到文件尾部返回的就是文件的大小
	int file_size = lseek(fd, 0, SEEK_END);

	cout << "file size :" << file_size << endl;

	close(fd);

	return 0;
}

lseek还可以拓展文件

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

using namespace std;

int main(int argc, char** argv)
{
	int fd = open("test.txt", O_RDWR);
	if (fd == -1)
	{
		perror("int fd = open(test.txt, O_RDWR);");
		exit(1);
	}

	// 将文件指针设置到文件末尾,在进行偏移,偏移出来的就是拓展出的大小
	int file_size = lseek(fd, 24, SEEK_END);

	cout << "file size :" << file_size << endl;

	close(fd);

	return 0;
}

 不过这是冒充的,真正的拓展文件需要IO操作,

truncate拓展文件

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

using namespace std;

int main(int argc, char** argv)
{
	int flag = truncate("test.txt", 500);
	if (flag == -1)
	{
		perror("int flag = truncate(test.txt, 500);");
		exit(1);
	}

	return 0;
}

ftruncate拓展文件

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

using namespace std;

int main(int argc, char** argv)
{
	int fd = open("test.txt", O_RDWR);
	if (fd == -1)
	{
		perror("int fd = open(test.txt, O_RDWR);");
		exit(1);
	}

	int flag = ftruncate(fd, 1000);
	if (flag == -1)
	{
		perror("int flag = ftruncate(fd, 1000);");
		exit(1);
	}

	return 0;
}

 

 stat(获取文件属性)

 struct stat* statbuf是一个传出参数,(就是C时,改变一个变量用指针,改变一个指针用二级指针)

结构体中的信息,就是一些文件的属性,终端命令ll时的一些信息

 st_mode,可以判断是什么文件

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

using namespace std;

int main(int argc, char** argv)
{
	struct stat stat_buf;

	int fd = stat("test.txt", &stat_buf);
	if (fd == -1)
	{
		perror("int fd = stat(test.txt,&stat_buf);");
		exit(1);
	}

	cout << "file size:" << stat_buf.st_size << endl;

	// is是宏函数,返回值是bool(其实系统/库宏函数的返回值基本都是bool值)
	if (S_ISREG(stat_buf.st_mode))
	{
		cout << "regular file" << endl;
	}

	// if宏定义,按位与(任何数和1相与都是本身)
	if ((stat_buf.st_mode & S_IFMT) == S_IFREG)
	{
		cout << "regular file" << endl;
	}

	return 0;
}

S_IFMT是文件掩码,可以理解成st_mode是要个16位的位图,前四位表示文件类型,后九位表示文件权限,中间还有三位是干什么的记不住了....(这个也不准确啊,但是大致就是这个意思!!!)

 但是当文件时链接文件的时候,会穿透直接显示源文件的属性.

stat函数默认可以穿透链接文件,lstat函数不会穿透.

lstat函数参数和stat完全一样,就不再叙述了

access检测文件是否存在或是否拥有某些权限

 成功返回0,失败返回-1设置errno

mode
        R_OK 可读
        W_OK 可写
        X_OK 可执行
        F_OK 文件存在

#include <unistd.h>
#include <iostream>

using namespace std;

int main(int argc,char** argv)
{
    int flag = access("test.txt",F_OK);
    if(!flag)
    {
        cout << "test.txt 存在" << endl;
    }

    return 0;
}

 chmod修改文件访问权限

 mode 参数的值

 成功返回0,失败返回-1设置errno

link创建硬链接

参数就是两个文件

  成功返回0,失败返回-1设置errno

unlink(删除一个文件的目录项)

头文件 #include <unistd.h>

原型 int unlink(const char *pathname);

成功返回0
失败返回-1,设置errno

文件目录项是记录inode,和文件名等的一个结构体

Linux下删除文件就是不断的将st_nlink -1 直到0.没有目录项对应的文件,并且打开该文件的进程关闭,就会被系统释放(时间不确定,是系统自己的机制)其实就是,开打一个文件,没有关闭即使这个文件删除了,进程中这个文件仍然存在于缓冲区

symlink(创建符号链接)

头文件 #include <unistd.h>

int symlink(const char *target, const char *linkpath);

成功返回0
失败返回-1,设置errno

符号链接大小,就是创建的路径几个字符就是多大

readlink 命令可以查看符号链接文件

readlink(获得符号链接所指向的文件名)

#include <unistd.h>

ssize_t readlink(const char *pathname, char *buf, size_t bufsiz);

成功返回指向文件名的字节数
失败返回-1,设置errno

rename(重命名)

#include <stdio.h>

int rename(const char *oldpath, const char *newpath);

成功返回0
失败返回-1,设置errno

getcwd(获得当前工作目录)

#include <unistd.h>

char *getcwd(char *buf, size_t size);

成功返回字符串指针,指针的值和buf一样
失败返回NULL

chdir(改变当前进程工作目录)

#include <unistd.h>

int chdir(const char *path);

成功返回0
失败返回-1,设置errno

opendir(打开一个目录)

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

DIR *opendir(const char *name);

成功返回指向该目录的结构体指针
失败返回NULL

就像C中文件指针,FILE一样,用指针名就行了,无需了解细节

closedir(关闭一个目录)

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

int closedir(DIR *dirp);

成功返回0
失败返回-1,设置errno

readdir(读取目录)

 #include <dirent.h>

struct dirent *readdir(DIR *dirp);

成功返回目录项结构体指针,(循环读取完毕,返回NULL不是设置errno)
失败返回NULL,设置errno

struct dirent
{
    ino_t d_ino;  // inode编号
    off_t d_off;  // 偏移量
    unsigned short d_reclen;  // 文件名有效长度
    unsigned char d_type;  // 类型(vim 打开看到的类似@*/等)
    char d_name[256];  //文件名
};

mkdir(创建目录)

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

int mkdir(const char *pathname, mode_t mode);

成功返回0
失败返回-1,设置errno

mode 文件权限(八进制0777)

rewinddir(回卷目录读写位置到起始)

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

void rewinddir(DIR *dirp);

telldir(获取目录流读取位置)/seekdir(设置下回读取目录的位置)

#include <dirent.h>

long telldir(DIR *dirp);

成功返回与dirp相关的目录当前读写位置(从起始位置的下一个读取位置的偏移量)
失败返回-1,设置errno

void seekdir(DIR *dirp, long loc);

loc一般由telldir函数的返回值来决定

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值