在线视频网站怎么做seo,吴川手机网站建设公司,网站建设 英文,电子商务网站经营性icp目录 一、C文件接口
二、系统文件I/O
2.1认识系统文件I/O
2.2系统文件I/O
2.3系统调用和库函数
2.4open( )的返回值--文件描述符
2.5访问文件的本质
三、文件重定向
3.1认识文件重定向
3.2文件重定向的本质
3.3在shell中添加重定向功能
3.4stdout和stderr
3.5如何理…目录 一、C文件接口
二、系统文件I/O
2.1认识系统文件I/O
2.2系统文件I/O
2.3系统调用和库函数
2.4open( )的返回值--文件描述符
2.5访问文件的本质
三、文件重定向
3.1认识文件重定向
3.2文件重定向的本质
3.3在shell中添加重定向功能
3.4stdout和stderr
3.5如何理解“linux下一切皆文件” --以对外设的IO操作为例
四、文件缓冲区
4.1认识FILE
4.2文件缓冲区引入
4.3文件缓冲区的原理
4.4解释现象
4.5总结 一、C文件接口 stdin stdout stderr C默认会打开三个输入输出流分别是stdin, stdout, stderr 仔细观察发现这三个流的类型都是FILE*, fopen返回值类型文件指针 fwrite向指定文件写入内容 fread从指定文件读取内容fprintf根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件fprintf可以使得信息写入到指定的文件调用C文件接口以w的形式打开若文件不存在会在当前目录下新建文件当前路径就是进程的当前路径cwd如果改变了进程的cwd就可以在其他目录下新建文件w写入前都会对文件进行清空a在文件结尾追加写两者都是写入C默认打开的三个输入输出流不是C语言的特性而是操作系统的特性进程会默认打开键盘显示器显示器 二、系统文件I/O
2.1认识系统文件I/O 文件其实是在磁盘上的磁盘是外设对文件进行访问就是对硬件进行访问 任何用户都不能直接访问硬件的数据 而必须通过系统调用 几乎所有的库只要是访问硬件设备必须封装系统调用 C文件接口就是一种库函数是对系统调用的封装
2.2系统文件I/O 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例0666 选项来指明新文件的访问权限 O_APPEND: 追加写 O_TRUNC: 每一次写入都清空文件 返回值 成功新打开的文件描述符 失败-1
代码示例
umask( )可以用来设置掩码的值 比特方位式的标志位传递方式 通过位运算来实现 2.3系统调用和库函数 上面的 fopen fclose fread fwrite 都是C标准库当中的函数我们称之为库函数libc open close read write lseek 都属于系统提供的接口称之为系统调用接口 可以认为f#系列的函数都是对系统调用的封装方便二次开发。
2.4open( )的返回值--文件描述符 Linux进程默认情况下会有3个缺省打开的文件描述符分别是标准输入0 标准输出1 标准错误2 012对应的物理设备一般是键盘显示器显示器 linux下文件描述符的分配规则从0下标开始寻找最小没有被使用过的数组位置它的下标就是新文件的文件描述符--结合访问文件的本质来说明
代码示例
因为C库函数是对系统接口的封装系统接口下只认识文件描述符所以C库自己提供的FILE结构体中必定也包含着文件描述符用_fileno记录 如果关闭了1号文件printf就无法向1号文件显示器写入了 但可以向3号文件写入所以我们打印就只能看到n的值 2.5访问文件的本质 任何一个被打开的文件在内存中都要被管理起来操作系统如果管理被打开的文件----先描述再组织 当我们打开文件时操作系统在内存中要创建相应的数据结构来描述目标文件--file结构体直接或间接包含如下属性文件的基本属性文件的内核缓冲区信息引用计数struct file*next在磁盘的什么位置表示一个已经打开的文件对象 而进程执行open系统调用所以必须让进程和文件关联起来每个进程都有一个指针*files, 指向一张表files_struct该表最重要的部分就是包涵一个指针数组每个元素都是一个指向打开文件的指针 所以本质上文件描述符就是该数组的下标只要拿着文件描述符就可以找到对应的文件 当一个进程open()一个文件时操作系统会在struct_file的指针数组中从下标为0的地方在开始寻找一个没有被使用过的数组位置填入要打开文件的struct file*再将数组下标返回给open( )调用作为该文件的文件描述符fd当一个进程要向某个文件写入的时候操作系统只认识文件描述符根据文件描述符找到对应的数组下标根据数组下标位置里的内容找到所对应的文件再写入close关闭文件本质上是清空对应fd数组下标位置的内容再将该fd内容指向的文件的引用计数--引用计数为0才释放销毁相应的struct_ file
三、文件重定向
3.1认识文件重定向 关闭1号文件再打开新文件 向1号文件写入内容 可以看到原来要向1号文件显示屏打印的信息被写入到了新打开的文件其中fd1。这种现象叫做输出重定向
常见的重定向有输出重定向 追加重定向 输入重定向
追加重定向 输入重定向 3.2文件重定向的本质 文件重定向的本质将1号文件描述符在指针数组中对应位置的内容用log.txt文件描述符在指针数组中对应位置的内容进行覆盖原本数组内的指向1号文件的文件指针就被替换成log.txt的文件指针当我们再向1号文件描述符写入内容的时候就是向文件指针指向的log.txt内写入而不再写到标准输出 dup2系统调用 原本向显示屏打印的内容被写入到log.txt文件中 3.3在shell中添加重定向功能
#includestdio.h
#includestdlib.h
#includestring.h
#includeunistd.h
#includesys/types.h
#includesys/wait.h
#includeassert.h
#includectype.h
#includefcntl.h#define LEFT [
#define RIGHT ]
#define LABLE #
#define DELIM \t
#define LINE_SIZE 1024
#define ARGV_SIZE 32#define NONE -1
#define IN_RDIR 0
#define OUT_RDIR 1
#define APPEND_RDIR 2extern char** environ;
char commandline[LINE_SIZE];
char* argv[ARGV_SIZE];
char pwd[LINE_SIZE];
char myenv[LINE_SIZE];int lastcode0;
int quit0;char *rdirfilename NULL;
int rdir NONE;const char* getuser()
{return getenv(USER);
}const char* gethostname()
{return getenv(HOSTNAME);
}void getpwd()
{getcwd(pwd,sizeof(pwd));
}void check_redir(char *cmd)
{// ls -al -n// ls -al -n // filename.txtchar *pos cmd;while(*pos){if(*pos ){if(*(pos1) ){*pos \0;*pos \0;while(isspace(*pos)) pos;rdirfilename pos;rdirAPPEND_RDIR;break;}else{*pos \0;pos;while(isspace(*pos)) pos;rdirfilename pos;rdirOUT_RDIR;break;}}else if(*pos ){*pos \0; // ls -a -l -n filename.txtpos;while(isspace(*pos)) pos;rdirfilename pos;rdirIN_RDIR;break;}else{//do nothing}pos;}
}void interact(char* cline,int size)
{getpwd();printf(LEFT%s%s %sRIGHTLABLE ,getuser(),gethostname(),pwd);char* sfgets(cline,size,stdin);assert(s);(void)s;cline[strlen(cline)-1]\0;//printf(echo : %s,cline);//ls -a -l myfile.txtcheck_redir(cline);
}int splitstring(char cline[],char* _argv[])
{int i0;_argv[i]strtok(cline,DELIM);while(_argv[i]strtok(NULL,DELIM));return i-1;
}void normalexcute(char* _argv[])
{pid_t idfork();if(id0){perror(fork);//continue;return ;}else if(id0){int fd 0;// 后面我们做了重定向的工作后面我们在进行程序替换的时候难道不影响吗if(rdir IN_RDIR){fd open(rdirfilename, O_RDONLY);dup2(fd, 0);}else if(rdir OUT_RDIR){fd open(rdirfilename, O_CREAT|O_WRONLY|O_TRUNC, 0666);dup2(fd, 1);}else if(rdir APPEND_RDIR){fd open(rdirfilename, O_CREAT|O_WRONLY|O_APPEND, 0666);dup2(fd, 1);}//子进程执行指令//execvpe(argv[0],argv,environ);execvp(argv[0],argv);}else{int status0;pid_t ridwaitpid(id,status,0);if(ridid){lastcodeWEXITSTATUS(status);}}
}int buildcommand(char* _argv[],int _argc)
{if(_argc2strcmp(_argv[0],cd)0){chdir(_argv[1]);getpwd();sprintf(getenv(PWD),%s,pwd);return 1;}else if(_argc2strcmp(_argv[0],export)0){strcpy(myenv,_argv[1]);putenv(myenv);return 1;}else if(_argc2strcmp(_argv[0],echo)0){if(strcmp(_argv[1],$?)0){printf(%d\n,lastcode);lastcode0;}else if(*_argv[1]$){char* sgetenv(_argv[1]1);if(s) printf(%s\n,s);}else{printf(%s\n,_argv[1]);}return 1;}//特殊处理lsif(_argc2strcmp(_argv[0],ls)0){_argv[_argc]--color;_argv[_argc]NULL;}return 0;}int main()
{while(!quit){//交互问题获得命令行参数interact(commandline,sizeof commandline);//字符串分割解析命令行参数int argc splitstring(commandline,argv);if(argc0) continue;//指令的判断int nbuildcommand(argv,argc); //普通指令的执行if(!n)normalexcute(argv);}return 0;
} 进程历史打开的文件以及文件的重定向关系并不会被程序替换所影响进程程序替换之后影响页表右边的物理地址所指向的内容虚拟地址并左边的部分并不会受到影响程序替换并不会影响文件访问
3.4stdout和stderr
stdout和stderr对应的硬件设备都是显示屏访问的都是同一个文件引用计数在重定向的时候默认只对stdout的fd进行重定向
代码示例 如果对1号和2号文件都要进行重定向呢
示例./mytest 1 log.txt 2err.txt 示例./mytest log.txt 21 3.5如何理解“linux下一切皆文件” --以对外设的IO操作为例
不同的外设在进行IO操作时都有自己对应的读写方法放在struct device里这些读写方法如何被找到--由struct operation_func来对读写方法进行管理该结构体里存在指向对应读写法的函数指针如何找到struct operation_func--由struct file来对struct operation_func进行管理file结构体存在指向struct operation_func的指针基于struct file之上的被称为虚拟文件系统VFS--一切皆文件当我们打开一个文件的时候通过进程的pcb数据结构找到struct struct_file操作系统根据文件描述符的分配规则在struct struct_file的指针数组中为该文件分配一个fd当我们要访问一个外设的时候根据该外设文件fd对应的数组下标内容找到该外设文件的struct file根据file结构体找到对应的struct operation_func由于访问的外设的不同在struct operation_func中根据函数指针找到对应的读写方法就可以对外设进行访问了
四、文件缓冲区
4.1认识FILE 因为IO相关函数与系统调用接口对应并且库函数封装系统调用所以本质上访问文件都是通过fd访问的 所以C库当中的FILE结构体内部必定封装了fd
4.2文件缓冲区引入
对比有无fork( )的代码 我们发现 printf 和 fwrite 库函数都输出了 2 次而 write 只输出了一次系统调用为什么呢肯定和 fork有关 再来验证一个现象
不加\n并且在最后close1 代码运行的结果是只有系统调用接口写入的内容被打印出来了 加上\n结果又不一样了 4.3文件缓冲区的原理
C语言会提供一个缓冲区我们调用C文件接口写入的数据会被暂存在这个缓冲区内缓冲区的刷新方式有三种
无缓冲直接刷新一般我们使用的fflush( )就是无缓冲的刷新方式行缓冲遇到\n才刷新一般对应显示器全缓冲缓冲区满了才刷新一般对应普通文件的写入特殊说明进程结束的时候会自动刷新缓冲区
在操作系统的内核中也存在一个内核级别的缓冲区目前认为只要将数据刷新到了内核数据就可以到硬件了内核缓冲区也有自己的刷新方式为什么要有C层面的缓冲区
用户不需要一步一步将数据写入到硬件中而是可以直接调用C库为我们提供的读写方法将数据交给库函数来处理解决用户的效率问题我们真正存到文件里的都是一个个的字符调用C库的读写方法可以在放入缓冲区之前将我们的数据格式化成字符串再刷新到内核中进而写入文件C层面的缓冲区可以配合格式化的工作
C为我们提供的缓冲区在FILE结构体里FILE里面有相关缓冲区的字段和维护信息FILE属于用户层面而不属于操作系统文件写入的过程
首先在文件写入之前进程会打开一个文件通过对各种内核数据结构的访问和操作获得该文件的文件描述符如果使用系统调用接口来对文件进行写入数据直接通过write和fd写入对应的内核级别缓冲区默认最后都会刷新到硬件中如果使用fwrite等库函数来对文件进行写入首先在语言层面会malloc出一个FILE结构体FILE里面有对应的缓冲区信息以及文件的fd然后内容会先被暂存在C层面的缓冲区如果是无缓冲数据直接被刷新到内核中如果是行缓冲遇到\n就会被刷新到内核中如果是全缓冲等缓冲区满了就被刷新到内核中由于库函数是对系统调用接口的封装用户通过write和fd将数据刷新到对应的文件的内核缓冲区内再由该内核缓冲区刷新到外设
4.4解释现象 为什么不加\n并且close(1)的时候使用库函数写入的内容不会被显示 不加\n调用库函数写入的数据都会被暂存在C层面的缓冲区 close(1)后即使进程退出后缓冲区会自动刷新但是此时已经找不到1号文件的fd了缓冲区内的数据也无法被写入到内核中最后也不会显示到显示器上 加了\n即使最后close(1)遇到\n缓冲区就会立马将数据刷新到内核中就会显示到显示器上
为什么fork()之后重定向C接口会被调用两次
重定向后缓冲区的刷新方式会从行缓冲变成全缓冲也就说数据要么等到缓冲区满了再被刷新要么等待进程结束后再刷新所以我们放在缓冲区中的数据就不会被立即刷新甚至fork之后fork( 之后创建子进程子进程会继承父进程的内核数据结构对象的内容父子进程在一开始的时候数据和代码是共享的缓冲区也属于数据进程退出后要对缓冲区的数据进行统一刷新刷新就是对数据进行访问写入此时父子数据会发生写时拷贝所以当父进程准备刷新的时候子进程也就有了同样的一份数据随即产生两份数据由于write没有所谓的缓冲区write()写入的数据直接在内核中所以write( )的数据只有一份
4.5总结 printf fwrite 库函数会自带缓冲区而 write 系统调用没有带缓冲区。这里所说的缓冲区 都是用户级缓冲区。其实为了提升整机性能OS也会提供相关内核级缓冲区 那这个用户级缓冲区谁提供呢 printf fwrite 是库函数 write 是系统调用库函数在系统调用的“上层” 是对系统 调用的“封装”但是 write 没有缓冲区而 printf fwrite 有说明该缓冲区是二次加上的由C标准库提供