福州网站建设fjfzwl,电子商务有哪些职业,app开发是什么专业,南京浦口网站建设目录 #x1f3c6;一、文件系统 1、open
①对open接口的介绍
②接口使用 2、write接口 3、read接口
#x1f3c6;二、深入理解文件描述符fd
1、fd具体实质 2、文件fd的分配规则 3、fd重定向 ①输出重定向 ②追加重定向
③输入重定向
④文件的引用计数
#x1f3c6;三…目录 一、文件系统 1、open
①对open接口的介绍
②接口使用 2、write接口 3、read接口
二、深入理解文件描述符fd
1、fd具体实质 2、文件fd的分配规则 3、fd重定向 ①输出重定向 ②追加重定向
③输入重定向
④文件的引用计数
三、缓冲区的理解
一、文件系统 1、空文件也要在磁盘占据空间。 2、文件内容属性。 3、文件操作对内容 对属性 or 对内容和属性 4、标定一个文件必须使用文件路径文件名 [唯一性] 5、如果没有指明对应的文件路径默认是在当前路径下进行文件访问 对文件的操作本质是进程对文件的操作 一个文件要被访问就必须先被打开 文件操作是十分重要的不同的语言都有自己独特的文件操作接口。C、C、Java、python、php、shell等语言都有自己独特的文件操作接口。如果我们要全部掌握这些接口成本是很高的而如果我们重新考虑文件的位置以及我们访问文件所必经的路径 所以为了降低学习成本就要掌握系统调用接口。 批量化注释的方式 1、open
①对open接口的介绍
C语言中打开文件的函数是fopen。
而fopen底层是调用了系统的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: 追加写O_TRUNC:清空文件再写入
返回值成功新打开的文件描述符失败-1
open接口的使用方式和fopen不太一样。首先在C语言中fopen成功返回FILE*指针那是C中的概念在操作系统中open接口调用成功则返回file descriptor(文件描述符)它是比C中指针更底层的东西。这一点文件描述符到底还有什么玄妙之处后续再详细介绍。
首先我们来关注open接口的第二个参数flagsflags是标定打开文件的方式。它是用来作标记的比如我们在C中常用bool值做标记但是它只能标记true or false。如果文件显然打开方式有很多只这两种是不满足我们的需求的。比如以只读、只写、读写、追加等方式打开。而我们要实现这一功能需要对flags进行独特的设计。Linux借用int的特性因为int是32个bit位OS使用每一个bit位来做标记那么它到底是怎么实现的呢我们可以借助演示一段代码 这里的宏ONE TWO THREE FOUR就是类似于一个个的选项。并且这里采用一个比特位表示一个选项彼此位置不重叠。
我们在使用这些选项采用的是| 操作。
明白了这些我们就可以简单使用一下open操作。
②接口使用
以只写方式打开一个不存在的文件。 我们发现以只写形式系统不会自动创建文件那是因为OS不会自己创建。C语言是经过了封装如果我们想让它以只写形式的同时还要创建要再 | 上创建选项。 然而这里的创建的log.txt是没法使用的因为它的权限是随机的我们在创建文件的时候要加上文件的权限。这一点也是open接口第三个参数的意义权限。 所以我们也能理解了为什么OS准备了两套open接口 对于已经存在的文件打开时是不需要设置权限的。
同时如果我们想修改创建文件的权限可以修改umask值。
因为文件权限设置的方式是 而umask默认权限是0002. 当然这里flags选项并没有讲完它需要结合其他接口来演示效果更佳。 2、write接口 在了解了open是如何使用之后我们再来看一下write的使用方式。老规矩先来段演示代码 write接口和fwrite是不一样的。我们在使用fwrite接口的时候因为strlen不计入\0所以我们要1为\0预留一个位置。而我们在使用write接口的时候是否需要也预留一个位置给\0呢
那我们不妨打印输出一下 显然1结果并不如我们所愿。那么出现这种情况是因为以\0作为字符串的结尾是C语言的规定和文件没有关系。而系统看来它的文件并不需要\0它只要字符串的有效内容所以使用系统接口write向文件写入字符串并不需要给\0留位置
我们再来看一段代码 我们发现我们以只写打开文件操作系统并没有为我们清空文件。这一点也是和C语言不同的。
C语言对这部分做了处理会自动清空文件重写而默认操作系统的文件写入是覆盖它没有清空里面的内容。
如果我们想重写文件时先清空再写入需要添加选项O_TRUNC 至此O_WRONLY选项是只写如果文件不存在不会自动创建而O_CREAT选项则是文件不存在就创建默认情况下对文件写入是覆盖不会清空文件所以O_TRUNC是清空文件选项。
那么如果我想实现追加呢 O_APPEND 3、read接口 ssize_t 是一种系统定制的类型是有符合整数可以等于0大于0小于0.
从特定的文件fd当中将数据读到缓冲区里期望读多少个就是count。
演示 至此我们可以看一下语言对文件的操作本质就是对系统调用接口的封装。 二、深入理解文件描述符fd
1、fd具体实质 我们再来理解一下文件。通过对系统调用接口open、write、read的认识我们大概清楚文件操作的本质就是进程和被打开文件的关系。而系统中存在大量的文件其中有的没被打开存储在磁盘上而被打开的文件就要被OS管理起来。到底是如何管理的呢
操作系统为了管理对应的打开文件必定要为文件创建对应的内核数据结构标识文件
struct file{} 而这其中包含了文件的大部分属性。
结构体file里面有文件的属性也有文件描述符。这样讲起来还是有点抽象我们可以画个图更深入理解 当文件被打开的时候它的地址被计入到文件指针数组中进程通过指针数组管理。而文件描述符本质上讲就是这个指针数组的下标。也就是标识打开文件在指针数组中的位置。而键盘、鼠标、显示器这些也是文件
当然以上所述是结论还是举些例子 我们看到新打开文件的fd文件描述符从3开始既然他们表示数组下标为什么不是从0开始呢这是因为默认打开了三个标准输入输出流 那么他们的返回值不是文件描述符而是结构体指针说明结构体中包含了文件描述符。
验证 果然他们的fd是0、1、2.所以我们打开的文件都是从3开始。
补充FILE结构体
在/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 putbackget 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 0int _blksize;
#elseint _flags2;
#endif_IO_off_t _old_offset; /* This used to be _offset but its too small. */
#define __HAVE_COLUMN /* temporary *//* 1column 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
};2、文件fd的分配规则
文件fd的分配规则是从指针数组下标从小到大按照顺序寻找最小的且没有被占用的下标fd。 举例说明 再运行文件时没有办法看到printf的内容。这是因为按照有空插空的原则这时fd数组下标为1的地方被文件myfile占用而OS角度来看它只会内容输出到fd为1(标准输出)的文件。这时就会出现无法打印到屏幕的现象。 3、fd重定向 本来应该输出到显示器但是这里输出打印到log.txt这称为重定向。
重定向的本质是上层用的fd不变在内核中更改fd对应的struct file*的地址。
这里的上层用的fd不变指的是在上层看来标准输入输出的文件描述符一直是0和1.
简单来说OS不会去具体查看struct file* 的内容而是根据下标来执行。 ①输出重定向
针对重定向OS为我们提供了一个接口专门用于重定向。 把oldfd下标指向的内容拷贝到newfd下标指向。 ②追加重定向 ③输入重定向 这里我们要解决几个问题子进程重定向会影响父进程吗
我们知道进程具有独立性所以子进程不能影响父进程所以说file*表父子进程是不一致的 所以对于父子进程来说子进程要拷贝父进程的进程管理而如果对子进程进行重定向那么就会file*表发生改变而这一操作是不影响父进程的(进程具有独立性)。而文件是属于系统部分的不需要拷贝给子进程。他们只是根据file*表对文件的处理不同。
所以说Linux的做法不是让子进程和父进程共享一张file_struct的表而是拷贝一份父进程的表这样不影响进程间的独立性。而执行进程程序替换的时候不会影响曾经进程打开的重定向文件。因为你替换的是磁盘上拷贝下来的代码和数据而重定向这些属于内核维护的数据结构也就是说不影响pcb。
Linux下一切皆文件 在OS看来它们都是struct file没有什么不同读写方法时直接调用对应的函数指针(多态的思想)。所有的设备和文件统一都是struct file.
④文件的引用计数
我们close文件是真的关闭了文件吗如果真的关闭了文件那么如果有多个进程打开同一个文件我关闭了它而别的进程还在使用这样显然是不合适的。所以就有了文件的引用计数在file结构体中有一个f_count变量用于统计打开文件的个数。当f_count为0时文件才被关闭因为当有引用文件文件不会被关闭。 三、缓冲区的理解
缓冲区本质就是一段内存。
缓冲区的意义是节省进程进行数据IO的时间因为进程将数据存储到磁盘(访问外设)速度很慢所以就有了进程先将数据放到缓冲区缓冲区再将数据存储到磁盘。
而存放到缓冲区的数据缓冲区有自己的刷新策略 我们来段代码具体感受缓冲区的刷新策略 我们发现在fork子进程之后当重定位到文件中时C接口的函数前后打印了两次而系统接口前后只打印了一次这就和缓冲区有关。 所以我们知道一个信息C语言中存在缓冲区而write在将数据拷贝到文件中的过程中并不存在缓冲区。但是我们的内核中是存在缓冲区的这个缓冲区存在于将文件中的数据拷贝到磁盘上这个过程中。 所以说如果我们使用C语言将数据拷贝到文件再存储到磁盘经历了两次将数据拷贝到缓冲区。而使用OS提供的写入则只经历了一次将数据存入到内核缓冲区。
而内核缓冲区刷新数据到磁盘完全是由操作系统决定的。
那这里也衍生了一个问题如果操作系统挂掉了我们在内核缓冲区的数据该怎么办呢 自己实现的一个迷你版shell命令行控制器
#includestdio.h
#includestring.h
#includeunistd.h
#includesys/types.h
#includesys/wait.h
#includeassert.h
#includestdlib.h
#includectype.h
#includesys/stat.h
#includefcntl.h
#includeerrno.h
#define NUM 1024
#define OPT_NUM 64#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3#define trimSpace(start) do{\while(isspace(*start)) start;\}while(0)
//do while(0) 包裹一个代码块
char lineCommand[NUM];
char *myargv[OPT_NUM];//指针数组
int lastCode0;
int lastSig0;
int redirType NONE_REDIR;
char *redirFileNULL;void commandCheck(char* commands)
{assert(commands);char * startcommands;char* endcommands strlen(commands);while(startend){if(*start ){*start\0;start;if(*start){redirTypeAPPEND_REDIR;}else{redirTypeOUTPUT_REDIR;}//要么是输出要么是追加trimSpace(start);redirFilestart;break;}else if(*start){//拆分成两个//cat file.txt*start\0;start;trimSpace(start);//////usrredirTypeINPUT_REDIR;redirFilestart;break;}else{start;}}
}
int main()
{while(1){redirType NONE_REDIR;redirFileNULL;//输出提示符printf(用户名主机名 当前路径# );fflush(stdout);char *s fgets(lineCommand,sizeof(lineCommand)-1,stdin);//去除\0assert(s!NULL);(void)s;//清除最后一个\n,abcd\n \n重置为\0lineCommand[strlen(lineCommand)-1]0;//ls -a -l -i ls -a -l -i//字符串切割//ls -a -l -i -ls -a -l -icommandCheck(lineCommand);//strtokmyargv[0]strtok(lineCommand, );//如果没有子串了strtok-NULL,myargv[end]NULLint i1;if(myargv[0]!NULL strcmp(myargv[0],ls)0){myargv[i](char*)--colorauto;}//如果没有子串了strtok-NULL,myargv[end]NULLwhile(myargv[i]strtok(NULL, ));//如果是cd命令不需要创建子进程让shell自己执行对应的命令if(myargv[0]!NULLstrcmp(myargv[0],cd)0){if(myargv[1]!NULL)chdir(myargv[1]);continue;}if(myargv[0]!NULL myargv[1]!NULL strcmp(myargv[0],echo)0){if(strcmp(myargv[1],$?)0){printf(%d,%d\n,lastCode,lastSig);}else{printf(%s\n,myargv[1]);}continue;}//测试是否成功条件编译
#ifdef DEBUGfor(int i0;myargv[i];i){printf(myargv[%d]: %s\n,i,myargv[i]);}
#endif//#注释掉DEBUG//执行命令pid_t id fork();assert(id !-1);if(id0){//因为命令是子进程执行的真正重定向的工作一定要是子进程来完成//如何重定向是父进程要给子进程提供信息的//这里重定向会影响父进程吗switch(redirType){case NONE_REDIR://什么都不做break;case INPUT_REDIR:{int fdopen(redirFile,O_RDONLY);if(fd0){perror(open);exit(errno);}//重定向的文件已经成功打开了dup2(fd,0);}break;case OUTPUT_REDIR:case APPEND_REDIR:{umask(0);int flagsO_WRONLY | O_CREAT;if(redirTypeAPPEND_REDIR) flags|O_APPEND;else flags|O_TRUNC;int fdopen(redirFile,flags,0666);if(fd0){perror(open);exit(errno);}dup2(fd,1);}break;default:printf(bug\n);break;}execvp(myargv[0],myargv);exit(1);}int status0;pid_t ret waitpid(id,status,0);assert(ret0);(void)ret;lastCode(status8)0xFF;lastSig(status0x7F);}return 0;
}