Linux——基础IO(1)

前言

铺垫:文件

1.之前我们讲过文件=内容+属性

磁盘中创建一个空文件也要占空间(就算内容为空,文件属性也占空间)

文件操作=文件内容的操作+文件属性的操作

有可能在操作文件的过程中既改变内容又改变属性

2.访问文件之前,都得先打开文件

修改文件是通过执行代码的方式完成修改,文件必须加载到内存之中**(打开文件)****——冯诺依曼体系决定:CPU只能从内存中对数据做读写**

3.是谁在打开文件?

进程在打开文件(程序被执行,进程被调度,fopen打开文件)

通常我们打开文件、访问文件、关闭文件都是进程在进行相关操作;C语言接口:fopen、fclose、fread、fwrite

一个进程可以打开多个文件

4.系统中不是所有的文件都被进程打开,没有被打开的文件在磁盘中

被打开文件——内存文件

未打开文件——磁盘文件

5.在一定时间段内,系统中存在多个进程,也可能同时存在更多的被打开的文件

OS要管理多个被进程打开的文件

如何管理呢?

先描述,再组织

内核中一定有描述被打开文件的结构体并用其定义对象

1.C语言文件操作

以写入模式打开文件

#include<stdio.h>

int main()
{
	FILE* fp = fopen("test.txt", "w");
	if (fp == NULL)
	{
		perror("fopen");
		return 1;
	}

	const char* str = "hello";
	fputs(str,fp);

	fclose(fp);

	return 0;
}

将写入的那部分注释掉(fputs),再执行此程序去打开文件test.txt会发现原本写入的字符不见了

以w方式打开的文件,该文件会被自动清空,再写入

很像之前学过的输出重定向>,重定向的本质就是一种写入嘛

既然像重定向,那么有像追加重定向的也很合理吧

FILE* fp = fopen("test.txt", "a");

以"a“方式打开就是追加appending(在结尾出写入不清空)

chdir

注意:

以写入方式打开文件,若无此文件会在当前路径自动创建

什么是当前路径?

ls /proc/(进程pid) -al 查看文件进程详细信息

找到这个进程有个cwd的属性

进程在启动的时候,会自动记录自己启动时所在的路径

chdir可以更改进程工作路径,改完创建的文件会到别的路径底下,且进程执行完前文件大小不会改变,执行完成后刷新缓冲区才会显示

chdir("路径");

以读模式打开文件

#include<stdio.h>
#include<unistd.h>
int main()
{
	FILE* fp = fopen("test.txt", "r");
	if (fp == NULL)
	{
		perror("fopen error!");
		return 1;
	}
	char buf[64];
	const char* msg = "hello bit!\n";
	while (1)
	{
		char* r = fgets(buf, sizeof(buf), fp);
		if (!r)
			break;
		printf("%s\n", buf);
	}
	fclose(fp);
	return 0;
}

此外还有fputc、fwrite、fprintf、scanf、fscanf…

2.stdin、stdout、stderr

之前我们学过一个概念:

Linux一切皆文件,显示器也是(向显示器写入也要先打开文件)

之前我们用的时候却没主动打开默认就能打印、读取

这说明了:进程在运行的时候都会默认打开三个输入输出流:标准输入流、标准输出流以及标准错误流对应C语言中的stdin、stdout、stderr

标准输入对应的是键盘设备,标准输出、标准错误对应的是显示器设备

3.系统文件IO

之前我们有发过这样一张图:

OS不允许进程直接访问硬件,访问硬件必须通过操作系统

访问文件不仅有C语言上的文件接口,OS必须提供对应的访问文件的系统调用接口

所以C标准库中的文件IO接口一定封装了系统调用

以下是一些系统调用接口介绍

1.open

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname,int flags,mode_t mode);


pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
参数:
O_RDONLY: 只读打开
O_WRONLY: 只写打开
O_RDWR : 读,写打开
这三个常量,必须指定一个且只能指定一个
O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
O_APPEND: 追加写

返回值:
成功:新打开的文件描述符
失败:-1

open("log.txt",O_WRONLY);

如果没有此文件,报错:无此文件或目录

open("log.txt",O_WRONLY|O_CREAT);

如果没有此文件,新创建一个,但文件的权限会乱码

flags这些大写的字母能联想到什么?宏

为何用一个或者多个常量进行“或”运算,构成flags?

其实是以位图的方式传参

#define ONE 1
#define TWO (1<<1)
#define THREE (1<<2)
#define FOUR (1<<3)
#define FIVE (1<<4)
 
void Print(int flag)
{
    if(flag & ONE) printf("1\n");
    if(flag & TWO) printf("2\n");
    if(flag & THREE) printf("3\n");
    if(flag & FOUR) printf("4\n");
    if(flag & FIVE) printf("5\n");
}
 
int main()
{
    Print(ONE);
    printf("----------------------\n");
    Print(TWO);
    printf("----------------------\n");
    Print(ONE|TWO);
    printf("----------------------\n");
    Print(THREE|FOUR|FIVE);
    printf("----------------------\n");
    Print(ONE|TWO|THREE|FOUR|FIVE);
}

这些宏的特点是每个二进制为只有一个1,而Print函数中if语句判断的就是对应的比特位哪个位为1

open中的flags其实和这一个道理

​
#define O_RDONLY       0000
#define O_WRONLY       0001
#define O_RDWR         0010
#define O_CREAT        0100

​
int open(arg1, arg2, arg3)
{
	if (arg2&O_RDONLY)
    {
		//O_RDONLY
	}
	if (arg2&O_WRONLY)
    {
		//O_WRONLY
	}
	if (arg2&O_RDWR)
    {
		//O_RDWR
	}
	if (arg2&O_CREAT)
    {
		//O_CREAT
	}
	//...
}

上面我们提到open(“log.txt”,O_WRONLY|O_CREAT);新创建的文件权限会乱码

打开曾经不存在的文件,mode给初始权限,不给初始权限会乱码

也就是说第三个参数是权限码

open("log.txt",O_WRONLY|O_CREAT,0666);

这个mode受系统umask影响,可以调接口umask(0);自己设

若想创建出来文件的权限值不受umask的影响,则需要在创建文件前使用umask函数将文件默认掩码设置为0。

2.close

#include<unistd.h>

int close(int fd);//关闭文件 fd是文件描述符

  • 关闭文件成功返回0;
  • 关闭文件失败返回-1。

3.write

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

#include<unistd.h>

功能:将buf位置开始向后count字节的数据写入文件描述符为fd的文件当中

写入成功,返回写入数据的字节个数
写入失败,返回-1

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
	int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);
	if (fd < 0)
    {
		perror("open");
		return 1;
	}
    printf("fd:%d\n",fd);
	const char* str = "hello\n";
	for (int i = 0; i < 10; i++)
    {
		write(fd, str, strlen(str));
	}
	close(fd);
	return 0;
}

4.read

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

功能:从文件描述符为fd的文件读取count字节的数据到buf位置当中

读取成功,返回读取数据的字节个数

读取失败,返回-1

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
	int fd = open("log.txt", O_RDONLY);
    if (fd < 0){
		perror("open");
		return 1;
	}

    printf("fd:%d\n",fd);
    char buffer[128];
	ssize_t s = read(fd, buffer, sizeof(buffer)-1);
	if (s>0)
    {
        buffer[s]='\n';
        printf("%s",buffer);
	}
	close(fd);
	return 0;
}

4.文件描述符fd

上面的系统调用接口很多都使用了文件描述符fd,那么什么是文件描述符呢?

不知你有没有发现,我在上面系统调用接口write、read的代码中偷偷打印了fd

而他们的起始位置都是3

而打开多个文件,fd1、fd2、fd3…起始fd也是从3开始

那么0、1、2哪去了?还记得我们之前讲的stdin、stdout、stderr吗?

没错0、1、2就是被他们占用了

0,1,2,3…n有点像什么?数组下标

FILE* 是什么?

int fd = open();

//系统调用接口用fd接收

FILE *fp = fopen();

C语言接口用fp接收

C标准库自己封装的结构体,因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
所以C库当中的FILE结构体内部,必定封装了fd。

stdin封0、stdout封1、stderr封2

5.文件管理

上面我们说到C语言封装系统调用接口。为什么C语言要封装呢?

不封装就只能使用系统调用接口,换个系统就不能用了。封装了可移植(跨平台)性

回到刚才那个像下标的fd上

一个进程可以打开多个文件,操作系统要对这些文件做管理->先描述,再组织

操作系统对进程的管理也是先描述、再组织,核心是PCB-task_struct,那么对于文件来说,肯定也会存在这样一个结构体用来描述文件,所以进程与文件之间的联系就变成了struct task_struct与struct XXX的联系。

其中

struct file * fd_array[NR_OPEN_DEFAULT]

是个结构体指针数组,而文件fd就是此数组的下标

不出意外task_struct中有个指针指向它

进程怎么知道自己打开哪些文件?

打开文件相当于吧文件在磁盘中找到内容+属性加载到内存中

OS内核中创建 struct file初始化属性、方法集、缓冲区链入表中

在struct file_struct 中找到一个数组下标把地址填入

下标返回上层用户(fd)

文件描述符的本质就是数组下标

为什么后续访问文件,用系统调用接口,必用fd呢?

进程自己

write(fd,...) ;

可以在数组中找到文件然后就通过

read(fd,...);

可以把缓冲区的数据拷到文件的缓冲区,刷新到磁盘中(写入);读有数据就读,没数据让操作系统从磁盘拷到缓冲区

6.重定向

如果关闭fd为1的标准输出流,那么打印内容会放在哪里呢?

int main()
{
    close(1);//关闭标准输出流
    open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
    printf("hello linux\n");
}

没打印出来,把打印内容写入文件中

在最开始close(0);把文件fd为0的删了,再分配给log.txt会分到0

close(2)则分到2

可以推断出文件描述符的分配规则:

最小的没有被使用的下标会被分给最新打开的文件

dup2系统调用

#include <unistd.h>
int dup2(int oldfd, int newfd);

将oldfd索引内容拷贝给newfd索引内容

7.FILE结构体以及缓冲区问题

在前面我们提到:

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。
所以C库当中的FILE结构体内部,必定封装了fd

如果有兴趣,可以看看FILE结构体:
typedef struct _IO_FILE FILE; 在/usr/include/stdio.h

在/usr/include/libio.h
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
//缓冲区相关
/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */
struct _IO_marker *_markers;
struct _IO_FILE *_chain;
int _fileno; //封装的文件描述符
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */
#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];
/* char* _save_gptr; char* _save_egptr; */
_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

我们一直在说的缓冲区和内核中的缓冲区没关系(尽管它有)

是语言层面的缓冲区(C语言自带缓冲区)

缓冲区它就是一块内存区域

为什么要有缓冲区?

为了提高使用者的效率

系统调用是有成本的->空间&&时间

缓冲区就是用空间来换时间

OS的缓冲区是当文件写入磁盘之间会有一块缓冲区,而这块缓冲区是由操作系统决定什么时候刷新的

主要讲的是语言层面的缓冲区

什么时候刷新

应用层

1.无刷新,无缓冲

2.行刷新——显示器xxxx\n yyyy

3.全缓冲,全部刷新——普通文件(写满缓冲区再刷新)

特殊:

强制刷新

进程退出时,会自动刷新

如何证明缓冲区存在?看以下代码

#include <stdio.h>
#include<unistd.h>
#include <string.h>
int main()
{
    const char *s1="hello write\n";
    const char *s2="hello fprintf\n";
    const char *s3="hello fwrite\n";\
    write(1,s1,strlen(s1));
    fprintf(stdout,"%s", s2);
    fwrite(s3, strlen(s3), 1, stdout);
    fork();
    return 0;
}

直接运行

重定向到文件里

如果向显示器进行打印(直接运行),刷新方案就是行刷新

如果向文件写入(重定向),对log.txt刷新策略变成全缓冲

网络安全基础入门需要学习哪些知识?

网络安全学习路线

这是一份网络安全从零基础到进阶的学习路线大纲全览,小伙伴们记得点个收藏!

img

阶段一:基础入门

img

网络安全导论

渗透测试基础

网络基础

操作系统基础

Web安全基础

数据库基础

编程基础

CTF基础

该阶段学完即可年薪15w+

阶段二:技术进阶(到了这一步你才算入门)

img

弱口令与口令爆破

XSS漏洞

CSRF漏洞

SSRF漏洞

XXE漏洞

SQL注入

任意文件操作漏洞

业务逻辑漏洞

该阶段学完年薪25w+

阶段三:高阶提升

img

反序列化漏洞

RCE

综合靶场实操项目

内网渗透

流量分析

日志分析

恶意代码分析

应急响应

实战训练

该阶段学完即可年薪30w+

阶段四:蓝队课程

img

蓝队基础

蓝队进阶

该部分主攻蓝队的防御,即更容易被大家理解的网络安全工程师。

攻防兼备,年薪收入可以达到40w+

阶段五:面试指南&阶段六:升级内容

img

需要上述路线图对应的网络安全配套视频、源码以及更多网络安全相关书籍&面试题等内容

同学们可以扫描下方二维码获取哦!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

网络安全技术库

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值