Linux系统编程——文件

Linux文件从哪来?

在这里插入图片描述
Linux 的文件既可以是真实保存到存储介质的文件,也可以是自身内核提供的虚拟文件,还可以是设备节点。

内核提供的虚拟文件系统在/sys目录下
在这里插入图片描述

Linux 文件基本函数

open 函数

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

返回值:

  • 成功,返回文件描述符,我们后面对于文件的读写,关闭等都通过文件描述符来操作。
  • 失败,返回-1

参数说明:

  • pathname:文件的路径名,如果只写文件名,就默认当前目录,如果在文件名加上路径,就按照绝对路径来打开文件。
  • flags:表示打开文件后用的操作
    • O_RDONLY:只读模式 0x 0000 0000
    • O_WRONLY:只写模式 0x 00000001
    • O_RDWR:可读可写 0x 00000002
      以上这三个常数中应当只指定一 个。下列常数是可选择的:使用或运算符加载第二个参数后面
      • O_APPEND 表示追加,如果原来文件里面有内容,则这次写入会写在文件的最末尾。
      • O_CREAT 表示如果指定文件不存在,则创建这个文件
      • O_EXCL 表示如果要创建的文件已存在,则出错,同时返回-1,并且修改 errno 的值。
      • O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。
      • O_NOCTTY 如果路径名指向终端设备,不要把这个设备用作控制终端
  • Mode:一定是在flags中使用了O_CREAT标志,mode记录待创建的文件的访问权限

文件描述符

  1. 对于内核而言,所有打开文件都由文件描述符引用。文件描述符是一个非负整数。当打开一个现存文件或者创建一个新文件时,内核向进程返回一个文件 描述符。当读写一个文件时,用open和creat返回的文件描述符标识该文件,将其作为参数传递给read和write。
    按照惯例,UNIX shell使用文件描述符0与进程的标准输入相结合,文件描述符1与标准输出相结合,文件描述符2与标准错误输出相结合。STDIN_FILENO、STDOUT _FILENO、STDERR_FILENO这几个宏代替了0、1、这几个魔数。

  2. 文件描述符,这个数字在一个进程中表示一个特定含义,当我们open—个文件时,操作系统在内存中构建了一些数据结构来表示这个动态文件,然后返回 给应用程序一个数字作为文件描述符,这个数字就和我们内存中维护的这个动态文件的这些数据结构绑定上了,以后我们应用程序如果要操作这个动态文件,只需要用这个文件描述符区分。

  3. 文件描述符的作用域就是当前进程,出了这个进程文件描述符就没有意义了。

  • 可执行文件即进程(gcc编译而来)。因为前三个文件描述符都被占用了,所以open返回的是3,表示打开成功
  • read和write第一个参数可以是文件描述符
 int n_read = read(0,buf,10);
 int n_write=write(1,buf,sizeof(buf));

close 函数

int close(int fd);

写入成功后要关闭文件,此时光标就在文件头

write 函数

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

函数说明:write() 会把参数 buf 所指的内存写入 count 个字节到参数 fd 所指的文件内。

返回值:

  • 成功则返回实际写入的字节数(len)。
  • 失败则返回-1,错误代码存入 errno 中

参数说明:

  • fd: 文件描述符
  • *buf: 写入的数据的首地址
  • count: 写入数据字节数

read 函数

ssize_tread(int fd,void * buf, size_t count);

函数说明:从打开的 fd 设备或文件中读取 count 个字节到 buf 中

返回值:

  • 成功返回读取的字节数,
  • 失败返回-1并设置 errno,如果在调 read 之前光标已到达文件末尾,则这次 read 返回0

参数说明:

  • fd: 文件描述符
  • *buf: 读入数据的首地址
  • count: 读入数据的字节数

注意:count常用sizeof

  • sizeof与strlen区别:sizeof传递的是数据类型,srtlen函数传递的是字符指针,计算的是该指针指向的数据类型的长度

lseek 函数

off_t lseek(int fd, off_t offset, int whence); 

函数说明:将文件读写指针相对whence移动offset个字节

返回值:成功则返回文件读写距离文件开头的字节大小,出错返回 -1

参数说明:

  • fd : 文件描述符
  • offset :偏移量
  • Whence :
    • SEEK_SET: 参数 offset 即为新的读写位置
    • SEEK_CUR: 以目前的读写位置往后增加 offset 个偏移量
    • SEEK_END: 将读写位置指向文件尾后再增加 offset 个位移量,当 whence 值为SEEK_CUR或SEEK_END时,参数 offset 允许负值的出现。

注:返回值是相对文件头的字节数,可以将光标定位到末尾,然后调用该函数得到该文件的字符个数

creat函数

int creat (const char *filename,mode_t mode )

参数说明:

  • filename::文件的路径名,如果只写文件名,就默认当前目录,如果在文件名加上路径,就按照绝对路径来打开文件。
  • mode:创建模式 //可读可写可执行

常见的创建模式:

宏表示数字作用
S_IRUSR4可读
S_IWUSR2可写
S_IXUSR1可执行
S_IRWXU1可读可写可执行

ioctl

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:

int ioctl(int fd, ind cmd,)
  • 其中fd是用户程序打开设备时使用open函数返回的文件标示符,cmd是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。
  • ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。

mmap

mmap将文件的内容映射给应用程序,应用程序通过访问内存的形式访问,该文件可以是普通文件,也可以是设备文件

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/mman.h>

/*
 * ./copy 1.txt 2.txt
 * argc    = 3
 * argv[0] = "./copy"
 * argv[1] = "1.txt"
 * argv[2] = "2.txt"
 */
int main(int argc, char **argv)
{
	int fd_old, fd_new;
	struct stat stat;
	char *buf;
	
	/* 1. 判断参数 */
	if (argc != 3) 
	{
		printf("Usage: %s <old-file> <new-file>\n", argv[0]);
		return -1;
	}

	/* 2. 打开老文件 */
	fd_old = open(argv[1], O_RDONLY);
	if (fd_old == -1)
	{
		printf("can not open file %s\n", argv[1]);
		return -1;
	}

	/* 3. 确定老文件的大小 */
	if (fstat(fd_old, &stat) == -1)
	{
		printf("can not get stat of file %s\n", argv[1]);
		return -1;
	}

	/* 4. 映射老文件 */
	buf = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd_old, 0);
	if (buf == MAP_FAILED)
	{
		printf("can not mmap file %s\n", argv[1]);
		return -1;
	}

	/* 5. 创建新文件 */
	fd_new = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
	if (fd_new == -1)
	{
		printf("can not creat file %s\n", argv[2]);
		return -1;
	}

	/* 6. 写新文件 */
	if (write(fd_new, buf, stat.st_size) != stat.st_size)
	{
		printf("can not write %s\n", argv[2]);
		return -1;
	}

	/* 5. 关闭文件 */
	close(fd_old);
	close(fd_new);
	
	return 0;
}


文件编程的一般步骤

  1. 在Linux中要操作一个文件,一般是先。pen打开一个文件,得到文件描述符,然后对文件进行读写操作(或其他操作),最后是close关闭文件即可。
  2. 强调一点:我们对文件进行操作时,一定要先打开文件,打开成功之后才能操作,如果打开失败,就不用进行后边的操作了,最后读写完成后,一定要关 闭文件,否则会造成文件损坏。
  3. 文件平时是存放在块设备中的文件系统文件中的,我们把这种文件叫静态文件,当我们去open打开一个文件时,linux内核做的操作包括:内核在进程中 建立一个打开文件的数据结构,记录下我们打开的这个文件;内核在内存中申请一段内存,并且将静态文件的内容从块设备中读取到内核中特定地址管理存放(叫动态文件)。
  4. 打开文件以后,以后对这个文件的读写操作,都是针对内存中的这一份动态文件的,而并不是针对静态文件的。当然我们对动态文件进行读写以后,此时 内存中动态文件和块设备文件中的静态文件就不同步了,当我们close关闭动态文件时,close内部内核将内存中的动态文件的内容去更新(同步)块设备中的静态文件
  5. 为什么这么设计,不直接对块设备直接操作。
    块设备本身读写非常不灵活,是按块读写的,而内存是按字节单位操作的,而且可以随机操作,很灵活。

文件系统的初识

在这里插入图片描述

实现 cp 指令

带参数的main函数

int main(int argc,char *argv[ ])
  • 其中argc和argv就是main函数的形参,他们是程序的“命令行参数”,
  • argc(arguement count的缩写,表示参数个数),
  • argv(arguement vector的缩写,表示参数向量),它是一个指针数组,数组的每个元素都是指针,指向命令行中的字符首地址。第二个参数char *argv[ ]也可替换成char **argv,它是一个指向指针的指针,同样能达到相同的效果。

注意:在使用带参数的main函数时,第一个形参必须是int型,用来接收形参个数,第二个参数必须是字符指针数组,用来接收从操作系统命令行传来字符的地址。


cp指令使用时,首先给出cp命令,若将源文件a.c复制成目标文件b.c,因此一共有3个参数,即argc的值为3。argv里面共有三个地址,argv[0]指向cp,argv[1]指向a.c地址,argv[2]指向b.c地址。

实现步骤:

  1. 打开被复制的源文件src.c

  2. 读取源文件src.c中的内容到读取缓冲器readBuf中

  3. 打开或者创建一个目标文件src.c,该文件是用于将复制的内容写入的地方

  4. 将读取缓冲器readBuf中的内容写入到目标文件src.c中

  5. 关闭源文件和目标文件,防止文件受损

代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include<string.h>
int main(int argc,char **argv)
{
        int fdSrc;
        int fdDes;
 
        char *readBuf=NULL;
        if(argc!=3){
                printf("param error\n");
                exit(-1);
        }
        fdSrc=open(argv[1],O_RDWR);
 
        int size=lseek(fdSrc,0,SEEK_END);
        readBuf=(char *)malloc(sizeof(char)*size+8);
        lseek(fdSrc,0,SEEK_SET);
        int n_read=read(fdSrc,readBuf,size);
 
        fdDes=open(argv[2],O_RDWR|O_CREAT|O_TRUNC,0600);
 
        int n_write=write(fdDes,readBuf,strlen(readBuf));
 
        close(fdSrc);
        close(fdDes);
 
        return 0;
}

C语言的文件指令与C库的文件指令的区别

转自:总结open与fopen的区别

1. 来源

  • open是UNIX系统调用函数(包括LINUX等),返回的是文件描述符(File Descriptor),它是文件在文件描述符表里的索引。
  • fopen是ANSIC标准中的C语言库函数,在不同的系统中应该调用不同的内核api。返回的是一个指向文件结构的指针。

2. 移植性

  • fopen是C标准函数,因此拥有良好的移植性;而open是UNIX系统调用,移植性有限。如windows下相似的功能使用API函数CreateFile。

3. 适用范围

  • open返回文件描述符,而文件描述符是UNIX系统下的一个重要概念,UNIX下的一切设备都是以文件的形式操作。如网络套接字、硬件设备等。当然包括操作普通正规文件(Regular File)。
  • fopen是用来操纵普通正规文件(Regular File)的。

4. 文件IO层次

  • 如果从文件IO的角度来看,前者属于低级IO函数,后者属于高级IO函数。低级和高级的简单区分标准是:谁离系统内核更近。低级文件IO运行在内核态,高级文件IO运行在用户态。

5. 缓冲

  1. 缓冲文件系统
    缓冲文件系统的特点是:在内存开辟一个“缓冲区”,为程序中的每一个文件使用;当执行读文件的操作时,从磁盘文件将数据先读入内存“缓冲区”,装满后再从内存“缓冲区”依此读出需要的数据。执行写文件的操作时,先将数据写入内存“缓冲区”,待内存“缓冲区”装满后再写入文件。由此可以看出,内存“缓冲区”的大小,影响着实际操作外存的次数,内存“缓冲区”越大,则操作外存的次数就少,执行速度就快、效率高。一般来说,文件“缓冲区”的大小随机器 而定。fopen, fclose, fread, fwrite, fgetc, fgets, fputc, fputs, freopen, fseek, ftell, rewind等。

  2. 非缓冲文件系统
    缓冲文件系统是借助文件结构体指针来对文件进行管理,通过文件指针来对文件进行访问,既可以读写字符、字符串、格式化数据,也可以读写二进制数据。非缓冲文件系统依赖于操作系统,通过操作系统的功能对文件进行读写,是系统级的输入输出,它不设文件结构体指针,只能读写二进制文件,但效率高、速度快,由于ANSI标准不再包括非缓冲文件系统,因此建议大家最好不要选择它。open, close, read, write, getc, getchar, putc, putchar等。

6. 总结

  • 就是open无缓冲,fopen有缓冲。前者与read, write等配合使用, 后者与fread,fwrite等配合使用。

  • 使用fopen函数,由于在用户态下就有了缓冲,因此进行文件读写操作的时候就减少了用户态和内核态的切换(切换到内核态调用还是需要调用系统调用API:read,write);而使用open函数,在文件读写时则每次都需要进行内核态和用户态的切换;表现为,如果顺序访问文件,fopen系列的函数要比直接调用open系列的函数快;如果随机访问文件则相反。

怎么知道函数的用法

Linux 下有 3 大帮助方法:help、man、info

  • help 只能用于查看某个命令的用法,
  • man 手册既可以查看命令的用法,还可以查看函数的详细介绍等等,它含有 9 大分类

1 Executable programs or shell commands // 命令
2 System calls (functions provided by the kernel) // 系统调用,比如 man 2 open
3 Library calls (functions within program libraries) // 函数库调用
4 Special files (usually found in /dev) // 特殊文件, 比如 man 4 tty
5 File formats and conventions eg /etc/passwd // 文件格式和约定, 比如 man 5 passwd
6 Games // 游戏
7 Miscellaneous (including macro packages and conventions), e.g. man(7), groff(7) // 杂项
8 System administration commands (usually only for root) // 系统管理命令
9 Kernel routines [Non standard] // 内核例程

例如查看 open 函数的用法
执行:man 2 open

在 man 命令中可以及时按“h”查看帮助信息了解快捷键。常用的快捷键是:

f 往前翻一页
b 往后翻一页
q 退出
/patten 往前搜
?patten 往后搜

  • info 手册比 man 手册编写得要更全面,但 man 手册使用起来更容易些。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值