【Linux】基础IO --- 系统级文件接口、文件描述符表、文件控制块、fd分配规则、重定向…
能一个人走的路别抱有任何期待,死不了
文章目录
- 一、关于文件的重新认识
- 二、语言和系统级的文件操作(语言和系统的联系)
- 1.C语言文件操作接口(语言级别)
- 1.1 文件的打开方式
- 1.2 文件操作的相关函数
- 1.3 细节问题
- 2.系统级文件操作接口(系统级别)
- 2.1 open
- 2.2 write
- 2.3 read
- 3.文件控制块&&文件描述符&&文件指针的关系
- 三、文件描述符的分配规则
- 1.关闭012文件描述符产生的现象(新打开文件的fd被赋值为0或1或2)
- 2.stderr和stdout的区别
- 四、重定向(上层用的fd始终不变,内核中更改fd对应的struct file*地址)
- 1.系统调用dup2进行重定向(新打开文件的struct file*地址复制到0/1/2文件地址中)
- 2.minishell中的重定向(shell派生的子进程利用dup2先完成重定向,之后再进行程序替换,彻底完成重定向工作)
- 五、Linux下一切皆文件
- 六、看看Linux内核源代码是怎么说的
一、关于文件的重新认识
1.空文件也要在磁盘中占据空间,因为文件属性也是数据,保存数据就需要空间。
2.文件=内容+属性
3.文件操作=对内容的操作or对属性的操作or对内容和属性的操作
4.标识一个文件必须有文件路径和文件名,因为这具有唯一性。
5.如果没有指明对应的文件路径,默认是在当前路径下进行文件访问,也就是在当前进程的工作目录下进行文件访问。如果想要改变这个目录,可以通过系统调用chdir来改变。
6.在C语言中,调用fread、fwrite、fopen、fclose、等接口对磁盘中的文件进行操作,实际上必须等到代码和数据加载到内存中,变成进程之后,cpu读取进程对应的代码,然后操作系统才会对文件进行操作,而不是只要我们一调用文件操作的接口就会对文件操作,而是必须将这些接口加载到内存之后,才可以。
所以对文件的操作,本质上就是进程对文件的操作!!!
7.一个文件要被访问,必须先被打开。用户进程可以调用文件打开的相关函数,然后操作系统对磁盘上相应的文件进行处理。在磁盘上的文件可以分为两类,一类是被打开文件,一类是未被打开的文件。
8.所以,文件操作的本质就是进程和被打开文件的关系。
二、语言和系统级的文件操作(语言和系统的联系)
1.C语言有文件操作接口,C++有文件操作接口,JAVA有文件操作接口,python、php、go、shell这些语言都有文件操作接口,这些文件操作接口在不同的语言中都是不一样的。
2.在磁盘中的文件如果想要被进程访问,则一定绕不开操作系统,因为磁盘是硬件,而操作系统是硬件的管理者,所以想要访问文件,必须通过操作系统提供的接口来访问文件,因为直接访问的方式是不安全的,所以必须使用操作系统提供的系统调用接口来访问这些文件。
3.库函数底层必须调用系统调用接口,因为无论什么进程想访问文件,都必须按照操作系统提供的方式来进行访问,所以就算文件操作相关函数千变万化,但是底层是不变的,这些函数最后都会调用系统调用接口,按照操作系统的意愿来合理的访问磁盘上的文件。
4.如果进程想要访问其他硬件,道理也相同,最终都必须按照操作系统的意愿来访问,也就是通过系统调用来访问。
1.C语言文件操作接口(语言级别)
1.1 文件的打开方式
r:以只读的方式打开文件,若文件不存在就会出错。
w:以只写的方式打开文件,文件若存在则清空文件内容重新开始写入,若不存在则创建一个文件。
a:以只写的方式打开文件,文件若存在则从文件尾部以追加的方式进行写入,若不存在则创建一个文件。
r+:以可读写的方式打开文件,若文件不存在就会出错。
w+:以可读写的方式打开文件,其他与w一样。
a+:以可读写的方式打开文件,其他与a一样。
其他打开方式是以二进制形式打开,不怎么用到,这里不做详细说明。
1.2 文件操作的相关函数
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #define FILE_NAME "log.txt" 6 int main() 7 { 8 FILE*fp = fopen(FILE_NAME,"a"); 9 //FILE*fp = fopen(FILE_NAME,"w"); 10 //FILE*fp = fopen(FILE_NAME,"r");11 // r、w、r+(读写,文件不存在就出错)、w+(读写,文件不存在就创建文件)、a(append,追加,只写的形式打开文件)、a+(以可读写的方式打开文件) 12 if(fp==NULL) 13 { 14 perror("fopen"); 15 exit(1); 16 } 17 18 int cnt = 5; 19 while(cnt) 20 { 21 fprintf(fp,"%s:%d\n","hello wyn",cnt--);// 对文件进行写入 22 } 23 fclose(fp); 24 // char buffer[64]; 25 // while(fgets(buffer,sizeof(buffer) - 1,fp) != NULL)// sizeof-1的原因是有给文本末尾留一个位置,让fgets放terminating null character 26 // { 27 // buffer[strlen(buffer)-1]=0;//把换行符改成结束符 28 // puts(buffer); 29 // } 30 // fclose(fp); 31 return 0; 32 }
fprintf,fopen,fclose,fgets,puts,等都是有关文件操作的函数,常用的文件打开方式有r(只读)、w(只写),a(追加)三种。
很容易被忽略的细节:
1.fprintf向文件写入时,换行符也是会被写入到文件当中的
2.fgets在读取文件内容的时候,换行符会被认为是有效字符读取到缓冲字符数组里面的,并且在每行读取结束后,fgets会自动添加null character到缓冲字符数组的每个字符串末尾处。
3.puts在将字符串打印的时候,会自动在字符串末尾追加一个换行符。所以为了防止puts打印两个换行符,在while循环里面将buffer数组里面的换行符改为null character。
4.fgets在读取的时候,以读取到num-1个字符,或换行符,或者文件结束符为止,以先发生者为准,这就是读取一行的内容。所以如果想要读取多行内容,就需要搞一个while循环。
1.3 细节问题
1.在C语言中,如果以w的方式单纯的打开文件,则文件内部的数据会自动被清空。
2.文本类文件在被创建的时候,默认权限是0664,因为文件刚开始的权限是0666,经过和umask0002的取反结果按位与后,最终的权限就变为了0664,顺便提一句,八进制用0开头来表示,十六进制用0x开头来表示。
2.系统级文件操作接口(系统级别)
2.1 open
1.
open用于打开文件,是系统级别的接口,open有两种使用形式,一种是只有两个参数,一种是有三个参数,第二种是针对打开文件不存在的情况,需要我们创建一个文件,并设定文件的初始权限,第一种是针对文件存在的情况,无须设定文件初始权限。
2.
第一个参数就是文件的名字,第二个参数flags是指打开文件时的方式,例如O_RDONLY,O_WRONLY,O_RDWR,O_CREAT,O_APPEND等宏,都可以在调用open时作为参数进行传参。
3.
mode_t mode作为第三个参数,代表打开文件不存在时,首先需要创建文件,创建文件的初始权限需要被设置,权限的设置就是通过这个参数来实现的。
4.
文件打开成功,则会返回新的文件描述符,打开失败就会返回-1,虽然现在还不清楚文件描述符是什么,但这不重要下面的2.3会讲到的,现在只要知道文件描述符是一个整数就可以了。
5.
要想理解open的第二个参数,则需要先理解如何使用比特位来传递选项,如果想让函数实现多种功能的话,我们可以利用或运算来讲多个选项 “粘合” 到一起,从而让一个接口同时实现多种不同的功能。利用的原理就是宏整数的32比特位中只有一个比特位是1,且不同的宏的1的位置是不重叠的,这样就可以利用或运算来同时实现多个功能。
11 每一个宏,对应的数值,只有一个比特位是1,彼此位置不重叠12 #define ONE (1<<0)13 #define TWO (1<<1)14 #define THREE (1<<2) 15 #define FOUR (1<<3)16 17 void show(int flags)18 {19 if(flags & ONE) printf("one\n");20 if(flags & TWO) printf("two\n");21 if(flags & THREE) printf("three\n");22 if(flags & FOUR) printf("four\n");23 24 }25 int main()26 {46 show(ONE);47 printf("---------------------\n");48 show(TWO);49 printf("---------------------\n");50 show(ONE | TWO);51 printf("---------------------\n");52 show(ONE | TWO | THREE);53 printf("---------------------\n");54 show(ONE | TWO | THREE | FOUR);55 printf("---------------------\n");56 }
6.
这也就是flags参数的不同的宏对应着不同的功能的原理,这些宏实际上就是利用了不同的比特位来表示不同的含义的,实现原理是一样的,但在具体实现上可能和我们上面所讲的简单原理不同,但只要原理相同就够了
25 int main()26 {27 umask(0);//将进程的umask值设置为000028 29 // C语言中的w选项实际上底层需要调用这么多的选项O_WRONLY O_CREAT O_TRUNC 066630 // C语言中的a选项需要将O_TRUNC替换为O_APPEND31 int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);//设置文件起始权限为066632 if(fd < 0)33 {34 perror("open");35 return 1;//退出码设置为136 }37 close(fd); 38 }
7.
O_CREAT代表打开文件如果不存在,就创建一个文件,如果没有这个宏,且打开了一个不存在的文件,则会报错,0666是设置的文件的起始权限,如果不想受到父进程shell的umask值0002的影响的话,可以通过系统调用umask()手动设置子进程的umask的值为0,这样起始权限实际上就是最终的文件权限了,因为umask按位取反后是全1,起始权限按位与后不会改变。
如果不设置文件起始权限,则创建出来的文件的权限就会是乱码。
8.
创建目录的命令mkdir,目录起始权限默认是0777,创建文件的命令touch,文件起始权限是0666,这些命令的实现实际上是要调用系统接口open的,并且在创建文件或目录的时候要在open的第三个参数中设置文件的起始权限。
2.2 write
1.
在C语言中的写入函数有fputs,fprintf,fwrite等,但在系统级别,写入接口只有一个write
25 int main() 26 { 27 umask(0);//将进程的umask值设置为0000 28 29 // C语言中的w选项实际上底层需要调用这么多的选项O_WRONLY O_CREAT O_TRUNC 066630 // C语言中的a选项需要将O_TRUNC替换为O_APPEND31 int fd = open(FILE_NAME,O_WRONLY | O_CREAT,0666);//设置文件起始权限为066632 if(fd < 0) 33 { 34 perror("open"); 35 return 1;//退出码设置为1 36 } 37 close(fd); 38 int cnt = 5; 39 char outbuffer[64]; 40 while(cnt) 41 { 42 sprintf(outbuffer,"%s:%d\n","hello linux",cnt--);43 //以\0作为字符串的结尾,是C语言的规定,和文件没什么关系,文件要的是字符串的有效内容,不要\044 //除非你就想把\0写到文件里面取,否则strlen()不要+145 write(fd,outbuffer,strlen(outbuffer));46 } 47 printf("fd:%d\n",fd);// 文件描述符的值为3 48 close(fd);
2.
如果write写入时第三个参数要多加一个\0的位置,创建出来的log.txt用vim打开时会出现乱码,以\0作为字符串的结束标志,这是C语言的规定和文件没有关系,文件只要存储有效内容就好了,不需要\0,所以在write写入的时候,strlen求长度不要+1
3.
只将写入的内容改为aaaa,打印出来的log.txt的内容就发生了覆盖式写入的现象,而不是先将文件原有内容清理,然后在重新写入。
在C语言中,如果再次以写的方式打开文件,会自动将原先文件中的内容清理掉,重新向文件写入内容。
自动清空原有数据,实际上是通过open系统调用中的第三个宏参数O_TRUNC来实现的。
4.
所以C语言中打开文件时,使用的打开方式为w,在底层的open接口中,要用三个宏参数O_WRONLY,O_CREAT,O_TRUNC来实现。
C语言中的a打开方式,在系统底层实现上只需要将O_TRUNC替换为O_APPEND即可。
可见库函数和系统调用的关系,本质就是库函数封装系统调用。
2.3 read
1.
read从一个文件描述符中读取内容,然后将其存到缓冲区buf里面,如果read调用成功,则会返回read读取的字节数,返回0代表读到了文件的结尾。
2.
我们知道要读取的内容是字符串,所以在数组buffer里面,需要手动设置字符串的末尾为\0,方便printf打印字符串。
3.
0,‘\0’,NULL等字面值实际上都是0,只不过他们的类型不同。
25 int main()26 {27 umask(0);//将进程的umask值设置为000028 29 // C语言中的w选项实际上底层需要调用这么多的选项O_WRONLY O_CREAT O_TRUNC 066630 // C语言中的a选项需要将O_TRUNC替换为O_APPEND31 int fd = open(FILE_NAME,O_RDONLY,0666);//设置文件起始权限为066632 if(fd < 0)33 {34 perror("open");35 return 1;//退出码设置为136 }37 char buffer[1024];38 ssize_t num = read(fd,buffer,sizeof(buffer)-1);39 if(num > 0) buffer[num]=0;//字符数组中字面值0就是\040 printf("%s",buffer); 41 close(fd);42 }
3.文件控制块&&文件描述符&&文件指针的关系
1.
进程可以打开多个文件,对于大量的被打开文件,操作系统一定是要进行管理的,也就是先描述再组织,所以操作系统会为被打开的文件创建对应的内核数据结构,也就是文件控制块FCB,在linux源码中是struct file{}结构体,包含了文件的大部分属性。
1 #include <assert.h>2 #include <stdio.h>3 #include <sys/types.h>4 #include <sys/stat.h>5 #include <fcntl.h>6 #include <stdlib.h>7 #include <string.h>8 #include <unistd.h>9 #define FILE_NAME(number) "log.txt"#number25 int main() 26 { 27 int fd0 = open(FILE_NAME(1),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066628 int fd1 = open(FILE_NAME(2),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066629 int fd2 = open(FILE_NAME(3),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066630 int fd3 = open(FILE_NAME(4),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066631 int fd4 = open(FILE_NAME(5),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为0666 32 printf("fd:%d\n",fd0);33 printf("fd:%d\n",fd1);34 printf("fd:%d\n",fd2);35 printf("fd:%d\n",fd3);36 printf("fd:%d\n",fd4);37 close(fd0);38 close(fd1);39 close(fd2);40 close(fd3);41 close(fd4); 42 }
2.
fd值为-1表示文件打开时出现错误,返回正数表示文件打开成功。
标准输入,标准输出,标准错误输出是系统默认打开的三个标准文件,系统自定义的三个文件指针stdin、stdout、stderr中一定含有文件描述符。
3.
文件指针指向的是一个被称为FILE的结构体,该结构一定含有文件描述符,因为在系统底层的接口中,只认文件描述符,才不管FILE结构体什么的,所以C语言的FILE结构体中一定含有系统底层的文件描述符。
4.
这就是为什么我们自己打开的文件的文件描述符是从3开始的,因为012被三个文件指针中的文件描述符提前占用了
1 #include <assert.h>2 #include <stdio.h>3 #include <sys/types.h>4 #include <sys/stat.h>5 #include <fcntl.h>6 #include <stdlib.h>7 #include <string.h>8 #include <unistd.h>9 #define FILE_NAME(number) "log.txt"#number25 int main() 26 { 27 int fd0 = open(FILE_NAME(1),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066628 int fd1 = open(FILE_NAME(2),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066629 int fd2 = open(FILE_NAME(3),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066630 int fd3 = open(FILE_NAME(4),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为066631 int fd4 = open(FILE_NAME(5),O_WRONLY | O_CREAT | O_TRUNC,0666);//设置文件起始权限为0666 32 printf("stdin->fd:%d\n",stdin->_fileno);33 printf("stdout->fd:%d\n",stdout->_fileno);34 printf("stderr->fd:%d\n",stderr->_fileno); 35 printf("fd:%d\n",fd0);36 printf("fd:%d\n",fd1);37 printf("fd:%d\n",fd2);38 printf("fd:%d\n",fd3);39 printf("fd:%d\n",fd4);40 close(fd0);41 close(fd1);42 close(fd2);43 close(fd3);44 close(fd4); 45 }
5.
内存中文件描述符,文件描述符表,文件控制块,进程控制块的关系如下图所示,文件描述符表,说白了就是一个存储指向文件控制块的指针的指针数组,而文件描述符就是这个指针数组的索引,进程控制块中会有一个指向文件描述符表的指针。通过文件描述符就可以找到对应的被打开的文件。
操作系统通过这些内核数据结构,将被打开的文件和进程联系起来。
三、文件描述符的分配规则
1.关闭012文件描述符产生的现象(新打开文件的fd被赋值为0或1或2)
1.
当关闭0或2时,打印出来的log.txt对应的fd的值就是对应的关闭的0或2的值,而当关闭1时,显示器不会显示对应的fd的值。
1 #include <stdio.h>2 #include <sys/types.h>3 #include <sys/stat.h>4 #include <fcntl.h>5 #include <unistd.h>6 7 int main()8 {9 //close(0);10 //close(1);11 //close(2); 12 umask(0000); 13 int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);//没有指明文件路径,默认在当前路径下,也就是当前进程的工作目录14 if(fd<0) 15 { 16 perror("open"); 17 return 1; 18 } 19 20 printf("open fd:%d\n",fd); 21 close(fd); 22 return 0; 23 }
2.
实际上文件描述符在分配时,会从文件描述符表中的指针数组中,从小到大按照顺序找最小的且没有被占用的fd来进行分配,自然而然关闭0时,0对应存储的地址就会由stdin改为新打开的文件的地址,所以打印新的文件的fd值时,就会出现0。
关闭2也是这个道理,fd为2对应的存储的地址会由stderr改为新打开的文件的地址,所以在打印fd时,也就会出现2了。
3.
但是,当关闭1时,情况就有所不同了,要知道无论是printf还是fprintf等函数,在打印时,实际上都是打印到stdout,也就是对应的显示器文件中,而现在1对应的存储地址不再是显示器文件的地址了,而是变成新打开文件的地址,所以printf或fprintf等函数打印的内容全都到新打开文件中了,只不过由于缓冲区的刷新策略问题,没有立即显示到log.txt文件中。加上fflush(stdout)就可以在log.txt中看到相关内容了。
1 #include <stdio.h>2 #include <sys/types.h>3 #include <sys/stat.h>4 #include <fcntl.h>5 #include <unistd.h>6 7 int main()8 {9 // close(0);10 close(1);11 // close(2);12 umask(0000);13 int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);//没有指明文件路径,默认在当前路径下,也就是当前进程的工作目录14 if(fd<0)15 {16 perror("open");17 return 1;18 }19 20 printf("open fd:%d\n",fd);// printf --> stdout21 fprintf(stdout,"open fd:%d\n",fd);// fprintf --> stdout 22 23 fflush(stdout); 24 close(fd);25 return 0;26 }
4.
当关闭文件描述符1时,本来应该写到stdout对应的显示器文件中的内容,现在写到了log.txt文件中,这样的特性就叫做输出重定向。
2.stderr和stdout的区别
stdin — 标准输入文件
stdout — 标准输出文件
stderr — 标准错误输出文件
标准输入文件对应的终端是键盘,其余两个输出文件对应的终端是显示器,进程将从标准输入文件中得到输入数据,将正常输出数据输出到标准输出文件,而将错误信息送到标准错误文件中。
1.
所以大多数情况下,我们输出的数据都是到标准输出文件stdout中的,例如printf、fprintf、fputs、等函数,都会将内容输出到stdout(标准输出文件)中,最后显示到stdout对应的显示器上。
2.
在某些命令使用错误时,会将错误信息输出到stderr(标准错误输出文件)中。
例如下面的la指令使用错误,错误信息会被输出到stderr中,最后显示到stderr对应的终端显示器上。
四、重定向(上层用的fd始终不变,内核中更改fd对应的struct file*地址)
1.系统调用dup2进行重定向(新打开文件的struct file*地址复制到0/1/2文件地址中)
1.
通过close关闭1,然后系统将新打开文件的地址分配到对应被关闭的1中的地址,然后打印到stdout的数据,就会被打印到新打开文件中,这样重定向的方式太搓了,完全可以利用系统调用dup2来进行重定向。
2.
如果用dup2来实现,显示到stdout中的内容,写到log.txt中,那就非常简单了,直接dup2(fd,1)即可,这样1对应的地址就被赋值为fd文件的地址了,也就实现了输出重定向。
3.
从原来的输出到屏幕改为输出到文件中,这就叫做输出重定向。
而追加重定向的方式也比较简单,只要将文件打开方式中的O_TRUNC替换为O_APPEND即可。
8 int main() 9 {10 umask(0000); 11 int fd = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);//输出重定向
E> 12 int fd = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);//追加重定向13 if(fd<0) 14 { 15 perror("open"); 16 return 1; 17 } 18 19 dup2(fd,1); 20 21 printf("open fd:%d\n",fd);// printf --> stdout 22 fprintf(stdout,"open fd:%d\n",fd);// fprintf --> stdout 23 24 const char* msg = "hello linux"; 25 write(1,msg,strlen(msg));//向显示器上write 26 27 close(fd); 28 return 0; 29 }
4.
从原来的键盘中读取数据,改为从文件fd中读取数据,这就叫做输入重定向。
文件log.txt中的内容,作为输入重定向重新输出到显示器中,即使fgets获取的方式是stdin也没有关系,因为我们使用dup2将stdin中的地址改为了文件log.txt的地址。
8 int main()9 {10 umask(0000);13 int fd = open("log.txt",O_RDONLY);//输入重定向14 if(fd<0)15 {16 perror("open");17 return 1;18 }19 20 dup2(fd,0);//由键盘读取改为从fd文件中读取 21 char line[64]; 22 while(1) 23 { 24 printf("<"); 25 if(fgets(line,sizeof(line),stdin)==NULL) break; 26 printf("%s",line); 27 } 28 }
2.minishell中的重定向(shell派生的子进程利用dup2先完成重定向,之后再进行程序替换,彻底完成重定向工作)
1.
如果要在原来的minishell中实现重定向,其实也比较简单,完成以下步骤即可:
a.将重定向指令按照重定向符号分为指令和目标文件两部分。(记得过滤重定向符号后面的空格)
b.确定重定向的方式后,利用dup2系统调用让fork之后的子进程先完成重定向。
c.最后就是子进程进行相应指令的程序替换,彻底完成子进程的重定向工作。
1 #include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>4 #include <sys/types.h>5 #include <sys/stat.h>6 #include <sys/wait.h>7 #include <assert.h>8 #include <string.h>9 #include <ctype.h>10 #include <fcntl.h>11 #include <errno.h>12 13 #define NUM 102414 #define OPT_NUM 6415 16 #define NONE_REDIR 017 #define INPUT_REDIR 118 #define OUTPUT_REDIR 219 #define APPEND_REDIR 320 21 #define trimSpace(start) do{\22 while(isspace(*start))\23 ++start;\24 }while(0)25 26 char command_line_array[NUM];27 char *myargv[OPT_NUM];//指针数组,每个指针指向命令行被切割后的字符串28 int lastcode=0;29 int lastsig=0;30 int redirType;31 char* redirFile;32 33 void commandCheck(char* command_line_array)34 {35 assert(command_line_array);36 char* start = command_line_array;37 char* end = command_line_array + strlen(command_line_array);//end指向\038 39 while(start < end)40 { 41 if(*start == '>')42 {43 *start = '\0';44 start++;45 if(*start == '>')46 {47 redirType = APPEND_REDIR;48 start++;49 }50 else 51 { 52 redirType = OUTPUT_REDIR;53 }54 trimSpace(start);55 redirFile = start;56 break;57 }58 else if(*start == '<')59 {60 *start = '\0';61 start++;62 trimSpace(start);//过滤重定向符号后面的空格63 //填写重定向信息64 redirType = INPUT_REDIR;65 redirFile = start;66 break;67 }68 else 69 {70 start++;//如果没有重定向符号,会进入else分支语句,start一直++,while循环最后停止71 }72 }73 74 75 }76 int main()77 { 78 while(1)79 {80 redirType = NONE_REDIR;81 redirFile = NULL;82 errno = 0;//重新执行命令时,保证这些数据都被初始化。83 84 printf("[%s@%s 当前路径]#",getenv("USER"),getenv("HOSTNAME"));85 //获取用户输入
W> 86 char *s=fgets(command_line_array,sizeof(command_line_array)-1,stdin);//读取字节数最大为1023留出一个\087 assert(s!=NULL);88 //将获取输入时输入的回车赋值成反斜杠089 command_line_array[strlen(command_line_array)-1] = 0;90 91 //将命令行输入的字符串,进行字符串切割,以空格为分隔符92 //空格全都换成反斜杠0,或者用strtok93 // "ls -a -l -i" > "log.txt"94 // "cat" < "log.txt" 95 // "ls -a -l -i" >> "log.txt"96 97 //在命令字符串切割之前,首先需要以重定向符号为基准将命令行切割为目标文件和执行命令两部分,把重定向符号赋值为\0即可98 commandCheck(command_line_array);99 100 myargv[0]=strtok(command_line_array," ");101 int i=1;102 if(strcmp(myargv[0],"ls") == 0 && myargv[0]!= NULL)//我们自己在ls的命令行参数表中手动加上执行颜色命令。103 {104 myargv[i++]=(char*)"--color=auto";105 }106
W>107 while(myargv[i++]=strtok(NULL," "));108 109 // 如果是cd命令,不需要创建子进程,让shell进程执行cd命令就可以,本质就是执行系统接口chdir110 // 像这种不需要派生子进程执行,而是让shell自己执行的命令,我们称之为内建或内置命令。111 if(myargv[0] != NULL && strcmp(myargv[0],"cd")==0)112 {113 if(myargv[1] != NULL)114 {115 chdir(myargv[1]);//将shell进程的工作目录改为cd的路径116 continue;117 }118 }119 // 完成另一个内建命令echo的运行,保证$?可以运行120 if(myargv[0]!=NULL && myargv[1]!=NULL && strcmp(myargv[0],"echo")==0)121 {122 if(strcmp(myargv[1],"$?") == 0)123 {124 printf("%d,%d\n",lastcode,lastsig);125 }126 else127 {128 printf("%s\n",myargv[1]);129 }130 continue;//后面的代码无须继续执行,直接continue即可131 }132 133 // 最后以NULL结尾,切割的字符串中已经没有字符串时,函数返回NULL134 #ifdef DEBUG 135 for(int i=0;myargv[i],i++)136 {137 printf("myargv[%d]:%s\n",myargv[i]);138 }139 #endif 140 //执行命令141 pid_t id=fork();142 assert(id!=-1);143 if(id==0)144 {145 //子进程进行重定向146 switch(redirType)147 {148 case NONE_REDIR:149 //什么都不做即可150 break;151 case INPUT_REDIR:152 {153 int fd = open(redirFile,O_RDONLY);154 if(fd < 0)155 {156 perror("open");157 exit(errno);//文件打开失败,命令执行出现错误,没必要进行子进程的程序替换,直接终止子进程即可。158 }159 //重定向的文件已经成功打开160 dup2(fd,0);161 }162 break;163 case APPEND_REDIR:164 case OUTPUT_REDIR:165 {166 umask(0000);167 int flags = O_WRONLY | O_CREAT;168 if(redirType == APPEND_REDIR) flags |= O_APPEND;169 else flags |= O_TRUNC;170 int fd = open(redirFile,flags,0666);171 if(fd < 0)172 {173 perror("open");174 exit(errno);//文件打开失败,命令执行错误,终止子进程。 175 }176 //重定向的文件已经成功打开177 dup2(fd,1);178 }179 break;180 default:181 printf("bug?");//重定向只设置了4种类型,现在出现第5种,可能出现了bug182 break;183 }184 185 186 execvp(myargv[0],myargv);187 exit(1);//如果程序替换失败,直接让子进程退出188 }189 int status=0;
W>190 pid_t ret = waitpid(id,&status,0);191 assert(ret > 0);192 lastcode = ((status>>8) & 0xFF);193 lastsig = (status & 0x7F);194 195 }196 return 0;197 }
2.
因为命令是子进程执行的,所以重定向的工作也一定是子进程来执行的,但是如何重定向,重定向的类型,重定向的目标文件,这些都是父进程来提供给子进程的。
3.
子进程的重定向是不会影响父进程的,因为进程具有独立性,在创建子进程时,会将父进程的pcb拷贝一份给子进程,除pcb外,mm_struct(虚拟地址空间),页表,文件描述符表等其实也都需要给子进程拷贝一份,所以进程之间是相互独立的,子进程的重定向不会影响父进程。
4.
在给子进程拷贝时,子进程继承了父进程的文件描述符表,但文件控制块是不需要继承的,因为文件控制块属于文件系统部分,而你的子进程或父进程这些东西是属于进程管理部分,这属于两个领域的知识,是不沾边的。
5.
执行程序替换的时候,会不会影响曾经的子进程打开的文件呢?
其实是不会的,需要注意的是,无论是文件描述符表还是pcb等等结构,本质上都是内核数据结构,而子进程在进行程序替换时,替换的是代码和数据,并不影响内核数据结构,所以即使子进程进行了程序替换,但原先子进程打开的文件是不会受任何影响的。
五、Linux下一切皆文件
1.
不同的硬件的读写方法一定是不一样的,但在OS看来,一切设备和文件都是struct file内核数据结构,在管理对应的硬件时,虽然硬件的管理方法不在OS层,而是在驱动层,这也没有关系,只需要利用struct file结构体中的函数指针,调用对应的硬件的读写方法即可。
2.
vfs层是Linux内核中的一个软件层,可以使得我们不关心底层硬件读写方式的差别,只用struct file中的函数指针即可管理对应的硬件的读写方式。
六、看看Linux内核源代码是怎么说的
1.下面是文件描述符表的固定个数
2.下面是文件描述符表的扩展个数
3.下面是云服务器下的文件描述符表的最多打开文件个数。
4.下面是文件控制块的具体内容
相关文章:
Codeforces Round 939 (Div. 2) E1-E2
Codeforces Round 939 (Div. 2) E1. Nene vs. Monsters (Easy Version) 题意: 有n个怪物,能量等级为\(a_i\),现在可以使用一种法术,使第1个怪物攻击第2个怪物,第2个怪物攻击第3个怪物……第n个怪物攻击第1个怪物,当能量等级为x的怪物攻击能量等级为y的怪物时,怪物y->m…...
阅读推荐
...
mORMot2 定义多对多关系
mORMot2 定义多对多关系 处理“has many”和“has many through”关系时,主要涉及到的是多对多关系的数据库设计和管理。以下是对您提供的文本的技术性翻译和解释:多对多关系是通过一个专门为这种关系创建的表来追踪的,将这个关系转变为两个指向相反方向的一对多关系。 默认…...
gitlab 如何设置保护分支
在GitLab上设置保护分支的步骤与在GitHub上略有不同。以下是在GitLab上设置保护分支的步骤:登录GitLab并选择仓库: 登录你的GitLab账号,选择你要设置保护分支的项目。进入仓库设置: 进入项目页面后,在左侧导航栏中点击“Settings”。选择Repository选项: 在“Settings”页…...
MASM中子程序(Procedures)的写法
在MASM(Microsoft Micro Assembler)的汇编体系中,子程序(Procedures)的定义和调用是非常重要的,就像C、pascal等的函数和方法一样;且对深入理解高级语言里函数的底层原理极其重要,如函数的参数传递、栈、变长参数等。但在网络上许多教程及代码都极其不规范且语焉不详;有的…...
详解Al作画算法原理
ChatGPT AI作画算法,又称为AI图像生成算法,是一种人工智能技术,它可以根据给定的输入自动生成图像。这类算法近年来变得非常流行,尤其是随着深度学习技术的发展。这里我将聚焦于目前最先进的一类AI作画算法,即生成对抗…...
【Linux】基础IO --- 系统级文件接口、文件描述符表、文件控制块、fd分配规则、重定向…
能一个人走的路别抱有任何期待,死不了 文章目录一、关于文件的重新认识二、语言和系统级的文件操作(语言和系统的联系)1.C语言文件操作接口(语言级别)1.1 文件的打开方式1.2 文件操作的相关函数1.3 细节问题2.系统级文…...
ffmpeg无损裁剪、合并视频
我用的版本是 ffmpeg version git-2020-06-23-ce297b4 官方文档 https://ffmpeg.org/ffmpeg-utils.html#time-duration-syntax 时间格式 [-][HH:]MM:SS[.m...] 或 [-]S[.m...][s|ms|us]裁剪视频 假设需要裁剪视频aaa.mp4,第5秒到第15秒 ffmpeg -ss 5 -to 15 -i…...
ATAC-seq分析:Annotating Peaks(9)
1. 注释开放区域 将已识别的无核小体区域与基因组特征(如基因和增强子)相关联通常很有趣。 一旦注释到基因或增强子的基因,我们就可以开始将 ATACseq 数据与这些基因的特征相关联。 (功能注释、表达变化、其他表观遗传状态&#x…...
蓝桥杯刷题015——最少刷题数(二分法+前缀和)
问题描述 小蓝老师教的编程课有 N 名学生, 编号依次是 1…N 。第 i 号学生这学期刷题的数量是 Ai 。 对于每一名学生, 请你计算他至少还要再刷多少道题, 才能使得全班刷题比他多的学生数不超过刷题比他少的学生数。 输入格式 第一行包含一个正整数 N 。 第二行包含 N 个整数:…...
Linux——进程
目录 冯诺依曼体系结构 操作系统(Operator System) 概念 设计OS的目的 定位 如何理解 "管理" 总结 系统调用和库函数概念 承上启下 进程 基本概念 描述进程-PCB task_struct-PCB的一种 task_ struct内容分类 组织进程 查看进程 通过系统调用获取进程…...
Junit单元测试框架【基础篇】
Junit单元测试框架【基础篇】🍎一.Junit单元测试框架🍒1.1 注解🍒1.2 断言🍒1.3 用例执行顺序🍒1.4 测试套件🍉1.4.1 指定类🍉1.4.1 指定包🍒1.5 参数化🍉1.5.1 单参数&a…...
高通平台开发系列讲解(WIFI篇)什么是WLAN无线局域网
文章目录 一、什么是WLAN1.1、WLAN发展史1.2、WLAN工作频段二、高通相关文件2.1、配置文件2.2、开机启动2.3、wpa_supplicant沉淀、分享、成长,让自己和他人都能有所收获!😄 📢本文将基于高通平台介绍什么是无线局域网。 一、什么是WLAN 在WLAN领域被大规模推广和商用的是…...
JavaScript 变量
JavaScript 变量 变量是什么? 变量是计算机中用来存储数据的“容器”,它可以让计算机变得有记忆,通俗的理解变量就是使用【某个符号】来代表【某个具体的数值】(数据) JavaScript 变量 与代数一样,JavaScript 变量…...
C语言进阶——文件管理
每当我们写好一段代码运行结束之后,再次运行的时候就会发现,之前在终端上输入的数据都会消失,那么如何把之前输入的数据保存下来呢? 我们一般把数据持久化的方式有把数据存放在磁盘文件中、存放到数据库。打印等方式进行保存。 …...
[Rust笔记] 规则宏的“卫生保健”
规则宏代码的“卫生保健”规则宏mbe即是由macro_rules!宏所定义的宏。它的英文全称是Macro By Example。相比近乎“徒手攀岩”的Cpp模板元编程,rustc提供了有限的编译时宏代码检查功能(名曰:Mixed Hygiene宏的混合保健)。因为rust…...
芒果改进YOLOv7系列:超越ConvNeXt结构,原创结合Conv2Former改进结构,Transformer 风格的卷积网络视觉基线模型,高效涨点
💡该教程为改进进阶指南,包含大量的原创首发改进方式, 所有文章都是全网首发原创改进内容🚀💡本篇文章 基于 YOLOv5、YOLOv7芒果改进YOLO系列:芒果改进YOLOv7系列:超越ConvNeXt结构,原创结合Conv2Former改进结构,Transformer 风格的卷积网络视觉基线模型,高效涨点、…...
Java---微服务---RabbitMQ部署
RabbitMQ部署1.单机部署1.1.下载镜像1.2.安装MQ1.3访问管理端2.集群部署2.1.集群分类2.2.设置网络1.单机部署 我们在Centos7虚拟机中使用Docker来安装,如未安装dockr,请参考《Centos7安装Docker》 1.1.下载镜像 方式一:在线拉取 docker …...
别总写代码,这130个网站比涨工资都重要
今天推荐一些学习资源给大家,当然大家可以留言评论自己发现的优秀资源地址 搞学习 找书籍 冷知识 / 黑科技 写代码 资源搜索 小工具 导航页(工具集) 看视频 学设计 搞文档 找图片 搞学习 TED(最优质的演讲)࿱…...
Python 类方法简记
文章目录前言必须实例化的类方法使用静态装饰器的类方法使用类装饰器的类方法省流版本ref:前言 Python 的类可以有特定的方法。下面是三种设计类方法的模板。 class A(object):a adef foo1(self, name):print hello, namestaticmethoddef foo2(name):print hello, nameprint…...
分享136个ASP源码,总有一款适合您
ASP源码 分享136个ASP源码,总有一款适合您 下面是文件的名字,我放了一些图片,文章里不是所有的图主要是放不下..., 136个ASP源码下载链接:https://pan.baidu.com/s/11db_K2QXns5pm8vMZBVPSw?pwds0lb 提取码&#x…...
【Python百日进阶-Web开发-Peewee】Day241 - Peewee 安装和测试、快速开始
文章目录 一、安装和测试1.1 使用 git 安装1.2 运行测试1.3 可选依赖项1.4 关于 SQLite 扩展的注意事项二、快速开始2.1 模型定义2.2 存储数据2.3 检索数据2.3.1 获取单条记录2.3.2 记录列表2.3.3 排序2.3.4 组合过滤器表达式2.3.5 聚合和预取2.3.6 SQL 函数2.4 数据库2.4.1 使…...
手机提供GMS支持(适用安卓和鸿蒙系统)
手机提供GMS支持 - 适用安卓和鸿蒙系统前言方式1:安装GMS套件(不推荐)方式2:安装OurPlay(推荐)方式3:安装Gspace(推荐)前言 本文提供多种为手机(安卓和鸿蒙系…...
[JavaEE]线程池
专栏简介: JavaEE从入门到进阶 题目来源: leetcode,牛客,剑指offer. 创作目标: 记录学习JavaEE学习历程 希望在提升自己的同时,帮助他人,,与大家一起共同进步,互相成长. 学历代表过去,能力代表现在,学习能力代表未来! 目录: 1. 线程池是什么? 2. 线程池的实现原理 3. 标准…...
Redis缓存污染了怎么办?
我们应用Redis缓存时,如果能缓存会被反复访问的数据,那就能加速业务应用的访问,但是,如果发生了缓存污染,那么,缓存对业务应用的加速作用就减少了。 在一些场景下,有些数据被访问的次数非常小&…...
特斯拉印证成本仍是工业制造取胜的关键,反证中国制造的优势
近20年来,业界对于工业制造的竞争优势开始逐渐偏离成本控制,然而如今特斯拉在全球的成功却正在印证着工业制造的成功仍然在于成本,成本才是工业制造取胜的关键,其他任何被吹嘘的个性化、创新等全都是骗人的把戏。特斯拉的成功曾被…...
工具及方法 - 斗地主技巧
斗地主游戏起源 斗地主是流行于湖北武汉、汉阳一带的一种扑克游戏。游戏需由3个玩家进行,用一副54张牌(连鬼牌),其中一方为地主,其余两家为另一方,双方对战,先出完牌的一方获胜。斗地主起源于湖北武汉汉阳一带…...
BIO NIO AIO IO多路复用的区别
1、基础概念 1.1、阻塞非阻塞和同步异步的结合 下面通过例子来具体说明: 同步阻塞: 小明一直盯着下载进度条,到100%的时候完成。 同步体现在:小明关注下载进度条并等待完成通知。(可以看成同步是我主动关注任务完成的…...
数学建模——降维算法
降维 降维的意义 降低无效、错误数据对建模的影响,提高建模的准确性少量切具有代表性的数据将大幅缩减挖掘所需的时间降低存储数据的成本 需要降维的情况 维度灾难。很难有一个简洁的模型在高维空间中依旧具有鲁棒性,而随着模型复杂度的增加…...
04_iic子系统
总结 iic_client和iic_driver 加入iic总线的思想和paltform总线的玩法一样 把iic设备和驱动注册到iic总线中 构造出字符设备驱动和设备节点供app进行操作 但是iic硬件设备是挂在iic控制器下面的 所以iic控制器也会有自己的驱动和设备树节点 厂家一般都会帮做好 我们写的iic_dr…...
离散系统的数字PID控制仿真-3
离散PID控制的封装界面如图1所示,在该界面中可设定PID的三个系数、采样时间及控制输入的上下界。仿真结果如图2所示。图1 离散PID控制的封装界面图2 阶跃响应结果仿真图:离散PID控制的比例、积分和微分三项分别由Simulink模块实现。离散PID控制器仿真图&…...
如何好好说话-第12章 理清楚问题就是答案
生活中该不该积极主动与别人展开社交活动?有些时候社交活动并不开心,仅仅只是无聊的闲才。但他确实能拉拢人际关系,帮我们获得近身套路。而且有一种观点认为不善于社交的人是不成功的。注意以上说的这些都是偏见。当我们站在一个更高的维度认…...
ice规则引擎==启动流程和源码分析
启动 git clone代码 创建数据库ice,执行ice server里的sql,修改ice server的配置文件中的数据库信息 启动ice server 和ice test 访问ice server localhost:8121 新增一个app,默认给了个id为1,这个1可以看到在ice test的配置文件中指定…...
进度管理(上)
规划进度管理 定义:规划进度管理是为实施项目进度管理制定计划的过程。 输入: 1、项目管理计划 2、项目章程(包含里程碑,这个和规划进度有直接干系) 3、组织过程资产 4、事业环境因素。 输出:进度管…...
2021 XV6 8:locks
实验有两个任务,都是为了减少锁的竞争从而提高运行效率。Memory allocator一开始我们是有个双向链表用来存储空闲的内存块,如果很多个进程要竞争这一个链表,就会把效率降低很多。所以我们把链表拆成每个CPU一个,在申请内存的时候就…...
JUC面试(十一)——LockSupport
可重入锁 可重入锁又名递归锁 是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁对象得是同一个对象),不会因为之前已经获取过的锁还没释放而阻塞。 Java中ReentrantLock和synchronized都是可重入锁&am…...
Datawhale 202301 设计模式 | 人工智能 现代方法 习题
Exercise 1 绪论 Q:用您自己的话来定义:(a)智能,(b)人工智能,(c)智能体,(d)理性,(e)逻…...
k8s安装dashboard面板
k8s dashboard github地址:https://github.com/kubernetes/dashboard注意:dashboard版本要和k8s版本匹配,具体参考release里的Compatibility:https://github.com/kubernetes/dashboard/releases安装命令wget https://raw.githubus…...
最详细、最仔细、最清晰的几道python习题及答案(建议收藏哦)
名字:阿玥的小东东 学习:python。c 主页:没了 今天阿玥带大家来看看更详细的python的练习题 目录 1. 在python中, list, tuple, dict, set有什么区别, 主要应用在什么样的场景? 2. 静态函数, 类函数, 成员函数、属性函数的区别? 2.1静态…...
逆水寒魔兽老兵服副本攻略及代码分析(英雄武林风云录,后续更新舞阳城、扬州、清明等副本攻略)
文章目录一、武林风云录1)老一:陈斩槐(只有四个机制,dps压力不大,留爆发打影子就行)(1)点名红色扇形区域(2)点名红色长条,注意最后还有一段大劈&a…...
SpringMVC总结
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring 框架提供了构建 Web 应用程序的全功能 MVC 模块。SpringMVC是一种web层的mvc框架,用于替代servlet(处理响应请求,获取表单参数,表单验…...
二进制部署kubernetes高可用集群
二进制部署kubernetes高可用集群 一、单节点部署 1、集群节点规划(均是24位掩码) 负载均衡节点Master节点Node节点Harbor私有仓库节点nginx110.4.7.23master110.4.7.11node110.4.7.2110.4.7.200nginx210.4.7.24master210.4.7.12node210.4.7.22 2、基本…...
机器学习(七):Azure机器学习模型搭建实验
文章目录 Azure机器学习模型搭建实验 前言 Azure平台简介 Azure机器学习实验 Azure机器学习模型搭建实验 前言 了解Azure机器学习平台,知道机器学习流程。 Azure平台简介 Azure Machine Learning(简称“AML”)是微软在其公有云Azure上推…...
第二类换元法倒代换专项训练
前置知识:第二类换元法 题1: 计算∫1x10xdx\int\dfrac{1}{x^{10}x}dx∫x10x1dx 解: \qquad令x1tx\dfrac 1txt1,t1xt\dfrac 1xtx1,dx−1t2dtdx-\dfrac{1}{t^2}dtdx−t21dt \qquad原式∫11t101t⋅(−1t2)dt−∫…...
VMware虚拟机无法向宿主机拖放文件
宿主机环境: Windows 10 x64专业工作站版 VMware workstation pro 17 TotalCommander 9.21a 虚拟机环境: Windows 10 x64专业工作站版 TotalCommander 9.21a 现象: 从虚拟机的TC向宿主机TC拖放文件时,光标显示为禁止drop的图…...
Java基础语法——运算符与表达式
目录 Eclipse下载 安装 使用 运算符 键盘录入 Eclipse下载 安装 使用 Eclipse的概述(磨刀不误砍柴工)——是一个IDE(集成开发环境)Eclipse的特点描述(1)免费 (2)纯Java语言编写 (3)免安装 (…...
连通性1(Tarjan 理论版)
目录 一、无向图割点、桥、双连通分量 Tarjan 算法求割点和桥(割边) “割点”代码 边双和点双连通分量 边双连通分量 和 点双连通分量 的缩点 二、有向图强连通分量 1.有向图的弱连通与强连通 2.强连通分量 Kosaraju算法 Tarjan 算法(…...
数据库02_函数依赖,数据库范式,SQL语句关键字,数据库新技术---软考高级系统架构师009
1.首先我们来看这个,给定一个X,能确定一个Y那么就说,X确定Y,或者Y依赖x,那么 比如y = x * x 就是x确定y,或者y依赖于x 2.然后再来看图,那么左边的部分函数依赖,就是,通过A和B能决定C,那么如果A只用给就能决定C,那么就是部分函数依赖. 3.然后再来看,可以看到,A可以决定B,那么…...
王者荣耀入门技能树-解答
前言 前段时间写了一篇关于王者荣耀入门技能树的习题,今天来给大家解答一下。 职业 以下哪个不属于王者荣耀中的职业: 射手法师辅助亚瑟 这道题选:亚瑟 王者荣耀中有6大职业分类,分别是:坦克、战士、刺客、法师、…...
java基础学习 day37 (集合)
集合与数组的区别 长度:数组长度固定,一旦创建完成,就不能改变。集合长度可变,根据添加和删除元素,自动扩容或自动收缩,(添加几个元素就扩容多少,删除几个元素就收缩多少࿰…...
C语言:数组
往期文章 C语言:初识C语言C语言:分支语句和循环语句C语言:函数 目录往期文章前言1. 一维数组的创建和初始化1.1 数组的创建1.2 数组的初始化2. 一维数组的使用3. 一维数组在内存中的存储4. 二维数组的创建和初始化4.1 二维数组的创建4.2 二维…...
斐波那契数列的--------5种算法(又称“兔子数列”)
斐波那契数列(Fibonacci sequence),又称黄金分割数列,因数学家莱昂纳多斐波那契(Leonardo Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:…...
【计算机网络(考研版)】第一站:计算机网络概述(二)
目录 四、OSI参考模型和TCP/IP模型 1.ISO/0SI参考模型 2.TCP/IP模型 3.OSI/RM参考模型和TCP/IP参考模型的区别和联系 4.五层教学模型 5.数据流动示意图 四、OSI参考模型和TCP/IP模型 前面我们已经讨论了体系结构的基木概念,在具体的实施中有两个重要的网络体系…...
Python内置包Tkinter的重要控件(下)
本文将接着介绍剩下的五个重要的控件,包括Canvas,Messagebox,Listbox,Checkbutton,Radiobutton。 目录 前言 控件 1. Canvas 2. Messagebox 3. Listbox 4. Radiobutton 5. Checkbutton 总结 前言 包括但不…...
(Java高级教程)第四章必备前端基础知识-第二节2:CSS属性
文章目录一:CSS属性一览表二:常用属性详解(1)字体属性(2)文本属性(3)背景属性一:CSS属性一览表 W3C:元素属性 A: align-content规定弹性容器内…...
听障人士亲述:我们在VRChat用手语交流,成员规模5000人
如果你在B站上搜索VRChat,排在前面的热门视频几乎都是与老外聊天的内容。除了练习语言、交文化流外,你还能在VRChat上遇到不少哇哇乱叫的小孩。作为一款VR社交应用,除了有趣的小游戏外,说话聊天也是VRChat关键的玩法之一。而有这么…...
Vue2 —— 学习(十)
目录 一、vue-resource 库 二、插槽 (一)默认插槽 (二)具名插槽 (三)作用域插槽 三、vuex (一)介绍 (二)多组件共享数据 1.通过全局事件总线实现 2…...
盲人使用公共设施:科技助力无障碍出行与智能识别
在我们的日常生活中,公共设施扮演着不可或缺的角色,它们为人们提供了便利的服务,构建起和谐、高效的社会环境。然而,对于视障人士而言,尽管公共设施设计之初便考虑到通用性和包容性,实际使用过程中仍难免遭…...
SpringBoot中Bean的创建过程及扩展操作点 @by_TWJ
目录 1. 类含义2. Bean创建过程 - 流程图3. 例子3.1. 可变属性注入到实体中3.2. 模拟Bean创建的例子 1. 类含义 BeanDefinition - 类定义,为Bean创建提供一些定义类的信息。实现类如下: RootBeanDefinition - 类定义信息,包含有父子关系的Be…...
力扣110. 平衡二叉树
思路:与二叉树最大高度类似,但是这里需要返回 -1 的高度来标识不是平衡二叉树,判断左右子树的高度相差大于1则不平衡,否则就是平衡。 class Solution {public boolean isBalanced(TreeNode root) {int ans func(root);if(ans >…...
SpringBoot整合Websocket的使用
一、什么是WebSocket WebSocket 是一种在单个 TCP 连接上进行全双工通信的网络协议。它允许客户端和服务器之间的双向通信,使得实时数据传输成为可能。相比传统的 HTTP 请求-响应模型,WebSocket 具有更低的延迟,更高的性能和更少的网络开销。…...
Python-VBA函数之旅-hash函数
目录 一、hash函数的定义: 二、hash函数的工作方式: 三、hash函数的优缺点: 四、hash函数的常见应用场景: 1、hash函数: 1-1、Python: 1-2、VBA: 2、推荐阅读: 个人主页&…...