【Linux】文件描述符fd
1.前置预备
- 文件 = 内容 + 属性
- 访问文件之前,都必须先打开他
#include<stdio.h> int main()
{ FILE* fp=fopen("log.txt","w"); if(fp==NULL) { perror("fopen"); return 1; } fclose(fp); return 0;
}
把源代码编成可执行程序,并没有打开文件,当程序运行执行到fopen时,才被打开,fopen和malloc一样,都是运行时操作,
当访问一个文件时,是进程在进行访问,可是进程是在内存中被CPU调度,文件是在磁盘中的,
根据冯诺依曼,CPU不能直接访问到磁盘,所以,文件也必须被加载到内存中。
所以打开文件fopen,是在做什么?
把文件加载到内存中
进程 = 属性 + 内容,同样 ,文件 = 内容 + 属性,OS需要对进程进行管理,需不需要对加载到内存的文件进行管理呢?
必须要!!!
如果管理文件呢?
先描述,再组织,
在内核中,文件 = 文件的内核数据结构 + 文件的内容,
磁盘中,文件 = 文件属性 + 内容
结论:我们研究打开的文件,本质是在研究进程和文件的关系。
没有被打开的文件呢?在磁盘上
文件:
1.被打开的文件------加载到内存,
2.没有被打开的文件------磁盘
2.以“w”方式打开文件
先看用C语言打开一个文件,用"w"的方式打开一个文件log.txt,如果这个文件不存在,就新建,如果这个文件存在,则清空内容后打开,
在命令行上,可以通过 > 文件 。对一个文件进行操作,> 意思跟"w"一样,文件不存在则创建,存在则对内容进行清空, > 也叫输出重定向。
下面一段代码内容是文件不存在,新建然后进行数据插入:
#include <stdio.h>int main()
{//'w'文件不存在新建FILE *fp = fopen("./log.txt","w");if(fp == NULL){perror("fopen");return 1;}const char *message = "hello file\n";int i = 0;while(i<10){fputs(message,fp);i++;}fclose(fp);return 0;
}
下面一段代码是打开文件,然后关闭文件,不进行任何操作,会对文件内容进行清空:
#include <stdio.h>int main()
{//'w'文件不存在新建FILE *fp = fopen("./log.txt","w");if(fp == NULL){perror("fopen");return 1;}// const char *message = "hello bit\n";// int i = 0;// while(i<10)// {// fputs(message,fp);// i++;// }fclose(fp);return 0;
}
3.以“a”方式打开文件
在命令行上,通过 >> 文件。对文件进行操作,>> 和“a”意思一样,对内容进行追加
先用“w”方式打开文件,每次执行该程序,都会先进行清空,无法进行追加:
#include <stdio.h>int main()
{//'w'文件不存在新建FILE *fp = fopen("./log.txt","w");if(fp == NULL){perror("fopen");return 1;}char buffer[1024];const char *message = "hello bit";int i = 0;while(i<10){snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);fputs(buffer,fp);i++;}fclose(fp);return 0;
}
这时可以用“a”方法进行打开文件,每次执行该程序,都会追加式的像文件进行插入:
#include <stdio.h>int main()
{//'a'追加内容FILE *fp = fopen("./log.txt","a");//appendif(fp == NULL){perror("fopen");return 1;}char buffer[1024];const char *message = "hello bit";int i = 0;while(i<10){snprintf(buffer,sizeof(buffer),"%s:%d\n",message,i);fputs(buffer,fp);i++;}fclose(fp);return 0;
}
4.程序默认打开三个输入输出流
一个程序默认启动,会打开三个输入输出流,标准输入,标准输出,标准错误,在C语言中,底层硬件所对应的文件键盘与显示器,把他们包装成文件的样子,最后访问键盘显示器,就可以以文件FILE*指针的形式进行访问
总结:是进程会默认打开三个输入输出流
看看下面代码,把内容打印到显示器方法:可以通过stdout进行操作
#include <stdio.h>int main()
{printf("hello word\n");fputs("bit\n",stdout);fwrite("aaaaa\n",1,4,stdout);fprintf(stdout,"bbbb\n");return 0;
}
2.文件管理
往显示器上显示,往磁盘文件打开或写入文件,读写键盘,本质上是访问硬件,我们用户在访问硬件,不可能直接通过语言进行直接访问硬件的,必须要通过操作系统,
我们使用的C接口,看起来是直接访问硬件,其实是通过操作系统提供的系统调用接口,才能访问到硬件,所以我们使用的C接口,底层一定要封装对应的文件类的系统调用!!
fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数 (libc)。
⽽ open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝
1.open
现在我们来认识一下文件系统调用接口:我们操作文件,首先是要打开文件,系统调用open。
第一个参数是文件名,
第二个参数,实际上是一个32bit位,也就是一个位图
第三的参数,如果文件已经创建,就不需要带,如果文件没有被创建,就需要对文件进行权限赋值
我们通过下面一段代码来认识,位图参数的传递:
#include <stdio.h>#define ONE (1<<0)//1 000001
#define TWO (1<<1)//2 000010
#define THREE (1<<2)//4 000100
#define FOUR (1<<3)//16 001000
#define FIVE (1<<4)//32 010000//code 1
void PrintTest(int flags)
{//都为1才为1,只要有一个不为1,就为0if(flags & ONE){printf("one\n");}if(flags & TWO){printf("two\n");}if(flags & THREE){printf("three\n");}if(flags & FOUR){printf("four\n");}if(flags & FIVE){printf("five\n");}
}int main()
{printf("=====================\n");PrintTest(ONE);printf("=====================\n");PrintTest(TWO);printf("=====================\n");PrintTest(THREE);printf("=====================\n");//只要两个操作数对应的位中有一个为1,那么结果位就为1。PrintTest(ONE | THREE);printf("=====================\n");PrintTest(ONE | TWO | THREE);printf("=====================\n");PrintTest(ONE | TWO | THREE | FOUR);printf("=====================\n");return 0;
}
所以我们可以通过 | 的方式进行传递参数,通俗点意思就是说,| 两边条件都满足。
open函数第二个参数:
- O_RDONLY:以只读方式打开文件。
- O_WRONLY:以只写方式打开文件。
- O_RDWR:以读写方式打开文件。
- O_CREAT : 若⽂件不存在,则创建它。需要使⽤mode选项,来指明新⽂件的访问 权限
- O_TRUNC:如果文件已存在且成功打开,则将其长度截断为 0。
- O_APPEND:写入时将数据追加到文件末尾。
返回值: 成功:新打开的⽂件描述符 失败:-1
现在我们来使用一下open函数,第二个参数传递 O_WRONLY | O_CREAT,以只写方式打开,如果文件不存在则创建:
#include <stdio.h>
#include <fcntl.h>int main()
{//以只写方式打开,如果文件不存在则创建open("log.txt",O_WRONLY | O_CREAT);return 0;
}
这里我们发现新创建的文件,他的权限是错乱的,那是因为我们调用系统接口时,新创建文件,需要给文件进行权限设置,而我们语言级接口fopen不需要,是因为底层对其进行了封装。
所以用系统调用接口时,新建文件,还要告诉新建文件默认的起始权限是多少!!!!也就是第三个参数传递,传递权限。
下面代码我们给文件进行权限赋值666,意思就是文件权限为rw-rw-rw-,r = 4 w = 2 x = 1.
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{//以只写方式打开,如果文件不存在则创建open("log.txt",O_WRONLY | O_CREAT,0666);return 0;
}
此时我们就可以发现权限没有错乱,但同时又有一个问题第三方的全是为什么只要一个r?
因为系统里有个umask,他会默认屏蔽掉一些权限,系统的umask = 0002,结合666,就会编成664,所以文件的最终权限会结合umask值来进行最终确认。
我们在编写代码的时候,也可以进行设置umask值,如下代码所示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{//不让系统屏蔽某些权限umask(0);//以只写方式打开,如果文件不存在则创建open("log.txt",O_WRONLY | O_CREAT,0666);return 0;
}
对umask清0,最终文件权限就是是第三个参数传递的权限,对在代码中umask清0,并不会影响到系统的umask值。
1.每一个进程有一个umask值,表示创建文件umask权限,进程umask权限默认从系统中获得,但自己手umask权限,采用就近原则来直接使用用户的umask值
2.touch创建文件其实权限都是666,然后受umask影响编程664.
下面代码模拟实现touch:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>//模仿touch
int main(int argc,char *argv[])
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建open(argv[1],O_WRONLY | O_CREAT,0666);return 0;
}
2.write
第一个参数传文件表示符,第二个参数传一个指向要写入的数据的缓冲区的指针,第三个参数表示写入的大小
3.read
第一个参数是文件标识符,第二个参数是一个指针,指向用于存储读取数据的缓冲区,第三个参数是该区域大小。
4.close
5.文件描述符fd
open返回值,成功时返回一个文件描述符,失败返回-1
现在我们看一下文件的返回值是多少,有什么用?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);if(fd1<0){perror("open");return 0;}printf("fd1: %d\n",fd1);return 0;
}
此时文件标识符为3,为什么文件打开从3开始呢?
因为进程启动,默认打开了三个标准的输入输出流stdin,stdout,stderr,因为Linux下一切皆文件,这三个标准的输入输出流被当成文件打开了。
现在我们使用系统调用接口来进行文件写入,如下代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);if(fd1<0){perror("open");return 0;}printf("fd1: %d\n",fd1);const char *message = "hello word\n";write(fd1,message,strlen(message));close(fd1);return 0;
}
紧接着,我们只修改一个message指向的内容,原本文件内容不变:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建int fd1 = open("log.txt",O_WRONLY | O_CREAT,0666);if(fd1<0){perror("open");return 0;}printf("fd1: %d\n",fd1);const char *message = "aaaaaa";write(fd1,message,strlen(message));close(fd1);return 0;
}
因为在做操作时只告诉了写入,并没有告诉要清空,只覆盖在原来基础上进行覆盖式的写入!!!
所以我们再加个选项O_TRUNC,如果文件存在则进行先清空:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建,如果存在则清空int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);if(fd1<0){perror("open");return 0;}printf("fd1: %d\n",fd1);const char *message = "aaaaaa\n";write(fd1,message,strlen(message));close(fd1);return 0;
}
如果对该文件只进行了打开然后关闭不做写入:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建,如果存在则清空int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);if(fd1<0){perror("open");return 0;}printf("fd1: %d\n",fd1);const char *message = "aaaaaa\n";//write(fd1,message,strlen(message));close(fd1);return 0;
}
内容被清空了,这样O_WRONLY | O_CREAT | O_TRUNC传参,最终作用就是和用fopen打开使用“w”方法一样,fopen使用“w”方法底层就是封装了这!!!
现在我们使用追加的形式进行写入:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建,如果存在则清空//int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);if(fd1<0){perror("open");return 0;}printf("fd1: %d\n",fd1);const char *message = "aaaaaa\n";write(fd1,message,strlen(message));close(fd1);return 0;
}
在上一个log.txt基础上进行内容的追加。
这样O_WRONLY | O_CREAT | O_APPEND传参,最终作用就是和用fopen打开使用“a”方法一样,fopen使用“a”方法底层就是封装了这!!!
fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数 (libc)。
open close read write lseek 都属于系统提供的接⼝,称之为系统调⽤接⼝
fopen fclose fread fwrite底层就是对open close read write进行了封装。
关于fd的问题:
我们连续打开几个文件,看看文件描述符是多少?
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{//不让系统屏蔽某些权限//umask(0);//以只写方式打开,如果文件不存在则创建,如果存在则清空//int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_TRUNC,0666);int fd1 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);int fd2 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);int fd3 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);int fd4 = open("log.txt",O_WRONLY | O_CREAT | O_APPEND,0666);if(fd1<0){perror("open");return 0;}printf("fd1: %d\n",fd1);printf("fd2: %d\n",fd2);printf("fd3: %d\n",fd3);printf("fd4: %d\n",fd4);const char *message = "aaaaaa\n";write(fd1,message,strlen(message));close(fd1);return 0;
}
观察到文件描述符从3开始依次创建!!!
而前面我们说到,0,1,2被键盘,显示器,显示器占用,又说过,这些硬件在底层被包装成文件的形式,同样,我们能不能通过0,1,2进行对键盘文件,显示器文件进行写与读呢?
看下面代码:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{const char *message = "hello write\n";write(1,message,strlen(message));return 0;
}
我们通过write,直接向文件描述符1,进行写入message指向的内容,结果的确打印在屏幕上
我们再来看看下面调用read进行读:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>int main()
{//abcdchar buffer[128];ssize_t s = read(0,buffer,sizeof(buffer));if(s > 0){//把回车改为0buffer[s-1] = 0;printf("%s\n",buffer);}return 0;
}
我们键盘输入abcd,然后打印,发现的确从标识符0进行读取。
内核角度:
我们来从内核角度进行观察,当我们磁盘中有个文件log.txt包含内容+属性,当我们把程序进行加载,就变成了进程,OS为了对其进行管理,就有了PCB,CPU调度该进程,执行到open函数,此时打开文件,也就是把磁盘中文件加载到内存,加载到内存中,那么OS也要对其进行管理,先描述再组织,OS要为我们文件创建一个数据结构struct file,里面包含文件的属性,一个文件就对应一个struct内核对象,而进程启动时,会默认打开三个输入输出的文件,当我们打开了好几个文件,创建这些文件的struct file,然后为了方便管理,struct file里面包含一个struct file*的指针,通过这个指针就可以把这些文件以链表的形式进行管理起来,文件链表的形式。每创建一个文件,就链入该链表中进行管理,所以这样把对文件的管理,转换成对链表的增删查改,
而Linux内核以软件的一贯特性,进程管理和文件管理,这是两个单元的,他们两个不能是强耦合,必须是松耦合的!!!
所以我们现在就是要把进程和文件进行关联起来,我们进程是一个结构体对象,文件也是一个结构体对象,这就可以看成结构体与结构体直接的联系,一般情况下一个进程可以打开多个文件,1:n的形式
所以接下来,在我们task_struct中存在一个struct file_struct *files的结构,所指向的struct file_strct结构体对象里面存在一个很重要的成员变量struct file* fd_array[]。包含了一个指针数组。天然就具备下标0 1 2 3.....。然后把键盘文件,显示器文件,显示器文件填入进来,0 1 2就被他们三个给占用了后来我们调用接口open,打开文件,然后我们系统在查的时候在这个数组里面查0 1 2都被占用,3没有被占,然后加载打开这个文件,在内核中把这个struct file创建出来,把该结构体对象的地址填入到3号下标位置。
所以writr(3,message,strlen(message)),就是进程拿着3号描述符找到自己进程内部的
struct file_struct *files,然后找到struct file *fd_arryay[]然后找到3号下标对应的文件进行写入。
这个表叫做文件描述符表,这个表是构建了进程和文件之间的关系的一张表,每一个进程都有这一张表,双方通过指针来完成各个模块的关联和解耦!!!!
我们来打开内核源代码来看看到底是不是这回事
所以fd到底是什么?-----数组下标!!!
在系统里面 ,fd文件描述符是访问文件的唯一方式!!!
可是我们在C语言里用的全是文件流FILE *,而不是fd文件描述符?
所以什么是FILE?什么是FILE*?
这个是C语言给我们提供访问文件的一个东西,因为只能通过文件描述符进行访问,这个FILE是在C语言上封装的一个struct FILE,结构体对象,这个结构体里必定要有很多的属性,但这个属性里面肯定有一个对文件描述符进行封装的一个成员变量!!!
我们来看一下下面代码来进行验证:因为stdin,stdout,stderr是FILE*类型,所以用他进行指向一个成员变量_fileno,就可以看到文件描述符:
int main()
{printf("stdin:%d\n",stdin->_fileno);printf("stdout:%d\n",stdout->_fileno);printf("stderr:%d\n",stderr->_fileno);return 0;
}
再来fopen打开一个文件:
int main()
{printf("stdin:%d\n",stdin->_fileno);printf("stdout:%d\n",stdout->_fileno);printf("stderr:%d\n",stderr->_fileno);FILE *fp = fopen("log.txt","w");printf("fp:%d\n",fp->_fileno);return 0;
}
这样就验证上述说法!!!
所以任你文件怎么办,OS只认文件描述符!!!
在C语言上,用到的函数都是对系统调用的封装!!!
不仅做了接口上的封装,还做了类型上的封装就是struct_file!!!
重新理解一切皆文件:
如果理解硬件为文件呢?
OS被称为软硬件的管理者,我们OS系统不光对软件进行管理,也要对硬件进行管理,先描述再组织,所以我们硬件也一定要是一个结构体。
在内核中肯定要有描述这些设备,每一个设备都有一个对应的strut device结构体对象,然后OS通过链表对这些结构体进行管理起来,对设备的管理就变成 对链表的增删查改,对于外设,属性类别一样,但是值可以不一样,对于这些外设最核心的动作就是IO,读写,每一种设备都要有查看内容的放入,最核心的方法就是读和写一个read方法和write方法。
输出一个结论,各个设备的属性可以统一设置成结构体,来体现设备的差异,但方法同样也是不一样的,虽然都叫读写。
对于外设有读写方法,比如键盘读写,显然读写方法没有,就可以把写方法设置为空。
所有的方法在底层实现上肯定是不一样的。
虽然他们方法底层实现不一样,所以Linux设计者,设计,当我们OS启动的时候,创建我们
struct file对象,我们struct file里面有,C语言结构体里面不能包含方法,但是可以包含函数指针:
我们可以让读指针指向底层的方法,写指针指向底层的方法,我们所对应的设备,在在内核中都创建对应的struct file,然后每个对应设备的struct file里面的函数指针指向对应的方法,站在struct file角度,我们要进行读写,我们压根就不需要关系底层实现方法,只要知道函数指针,就能找到底层方法!!!向上我们要访问任何一个硬件,统一叫做读和写等方法,不用关注底层实现,只需要调这个方法,他自动给我们找到底层实现方法。
struct file就想当于在软件层面进行了一次封装,在上层看来,一切皆文件,对上层来说只需要提供struct file就可以。只要找到struct file就可以找到底层的方法,
这套机制在Linux中被叫做VFS(virtual file system)(虚拟文件系统)。
这样在上层就看不到各个设备的差异了,所以一切皆文件!!!
所有用户的行为都会被转换成进程,无论启动读写等各种命令,所有行为都是进程,而我们站在进程角度,只需要拿文件描述符,找到对应的struct file,剩下的就不是进程的事情了,只要找到执行file中的方法,就可以完成对这些设备的操作。
所以进程角度,一起皆文件。
所以之前用的系统调用函数open,read,write,访问键盘显示器,都是调用底层设备对应
struct file中的函数指针对应的方法进行访问。
在上层一切皆文件,底层有各种设备,这种语系在C++当中,这种技术叫做多态!!!这也是C语言实现多态的方法!!!
补充:底层实现的这些方法全是在驱动程序里面!!!
打开内核源代码看看:
文本写入VS二进程写入
在计算机里,OS系统层面上只有二进制概念,语言层看起来可以文本写入,也可以二进制写入。
为什么我们语言喜欢做封装?(C/C++)
我们向显示器写12345,我写的是12345这个整数呢,还是’1‘’2‘’3‘’4‘’5‘字符?-----字符!
我们显示器我们给他叫做字符设备,有一个东西叫做ACSLL码表,写12345,实际上是这几个字符ACSLL码值二进制,这个二进程被我们显示器解释成12345字符
看下面两段代码:
int main()
{char *message = "hello\n";write(1,message,strlen(message));return 0;
}
int main()
{int a = 12345;write(1,&a,sizeof(a));printf("\n");return 0;
}
把字符显示到显示器上,直接就是字符,把数字输入到显示器上,显示器是字符设备只认字符,只不过这样的字符转成我们对应的二进程,被显示器转换成字符’9‘’0‘。
所以我们在显示到显示器之前,我们给转换成字符,然后再进行显示,如下代码所示:
int main()
{int a = 12345;char buffer[1024];snprintf(buffer,strlen(buffer),"%d",a);write(1,buffer,strlen(buffer));printf("\n");return 0;
}
这样才能把12345显示到显示器上 ,所以用系统调用是没办法直接把一个整数答应到显示器上,必须进行相关的转换,
所以我们为什么要有printf相关这样的函数呢?不是有write这样的接口呢?、
因为我们再很多的情况,我们需要把内存级的二进制数据转换成字符风格,通过write打印在显示器上,这个过程叫做格式化的过程。
如果在系统中只有系统调用,那么这个格式化的过程必须要我们手动自己设置,然后才能显示。
所以C语言提供一些printf,scanf等一系列接口函数,直接可以进行使用打印,因为在底层对格式化的过程进行了封装!!!
所以为什么我们C语言要给我们很多接口做封装?
1.方便用户进行操作
2.提高语言的可移植性
系统调用的接口类型设计成void*,所以 传过来字符串或者其他类型,你以为你传的是这些类型,在这些接口看来是二进制,因为是void*。
读到的数据,写到的数据,做转化,都是用户进行操作,然后C语言对其进行封装,用户就能直接使用。
为什么喜欢做封装?如果不是Linux平台,而是win或者macos平台?
如果是系统调用接口,那么换平台就不能运行的,如果是封装的接口,在Linux调Linux的系统调用接口,在win调win的接口,在什么平台就调什么平台的接口。
所以语言层,把我们与平台强相关的接口操作做封装,提高语言的可移植性。
在使用这些接口的时候,win和Linux或者其他平台提前给我们安装了一些东西,这个东西叫glibc的库,语言层使用的一些接口,在glibc中进行了封装,把库编成Linux版本的库,win版本的库等等
语言的可移植性性越高,这个语言就越流通!!!!
相关文章:
【Linux】文件描述符fd
1.前置预备 文件 内容 属性访问文件之前,都必须先打开他 #include<stdio.h> int main() { FILE* fpfopen("log.txt","w"); if(fpNULL) { perror("fopen"); return 1; } fclose(fp); return 0…...
mysql之事务
MySQL的事务隔离特性指的是多个并发事务之间相互隔离的程度,以保证数据的一致性和并发性。MySQL支持四个隔离级别,分别是读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repe…...
python插入mysql数据
# 插入与上一篇变化不大,只是需要进行确认操作. 增加确认操作的方法有两种(假设类对象为a): 1.在连接时传入一个参数:autocommitTrue aConnection( host"localhost", port3306, user"root", password"自己的密码…...
C语言面试题/笔试题/高频面试题
一、C: 1.static和const的作用优缺点 限制作用域: static声明中使用全局变量、函数 ,仅当前文件内可用,其他文件不能引用 static修饰的局部变量只能在本函数中使用. 延长生命周期: static修饰的变量生命周期为整个程序 存放位置&a…...
rust websocket Echo server高性能服务器开发
最近在学习websocket时,一直没有发现好的websocket server工具来调试,于是就自己做了一个websocket server用来学习和调试。因为rust性能遥遥领先,所以就采用了rust来搭建服务器。废话不多说直接上代码main.rs: use tokio::net::TcpListener; use tokio_tungstenite::tung…...
Docker打包SpringBoot项目
一、项目打成jar包 在进行docker打包之前,先确定一下,项目能够正常的打成JAR包,并且启动之后能够正常的访问。这一步看似是可有可无,但是能避免后期的一些无厘头问题。 二、Dockerfile 项目打包成功之后,需要编写Doc…...
ViT学习笔记(二) Patch+Position Embedding阶段的详细推演与理解
我认为讲得最好的一个文章:Vision Transformer详解-CSDN博客 有很多文章,自己并没有完全正确理解。 我的笔记,以ViT的标准应用为例: • 输入图像:输入图像的尺寸是224x224,且是RGB图像,因此输…...
Elasticsearch 单节点安全配置与用户认证
Elasticsearch 单节点安全配置与用户认证 安全扫描时发现了一个高危漏洞:Elasticsearch 未授权访问 。在使用 Elasticsearch 构建搜索引擎或处理大规模数据时,需要启用基本的安全功能来防止未经授权的访问。本文将通过简单的配置步骤,为单节…...
【PHP项目实战】活动报名系统
目录 项目介绍 开发语言 后端 前端 项目截图(部分) 首页 列表 详情 个人中心 后台管理 项目演示 项目介绍 本项目是一款基于手机浏览器的活动报名系统。它提供了一个方便快捷的活动报名解决方案,无需下载和安装任何APP,…...
ASP.NET Core8.0学习笔记(二十五)——EF Core Include导航数据加载之预加载与过滤
一、导航属性数据加载 1.在EF Core中可以使用导航属性来加载相关实体。 2.加载实体的三种方式: (1)预先加载:直接在查询主体时就把对应的依赖实体查出来(作为初始查询的一部分) (2)显式加载:使用代码指示稍后显式的从…...
【RK3562J开发笔记】MCP2518FD外部CAN-FD控制器的调试方法
“SPI转CAN-FD”是嵌入式开发领域的常用方法,它极大地促进了不同通信接口之间的无缝连接,并显著降低了系统设计的复杂性。飞凌嵌入式依托瑞芯微RK3562J处理器打造的OK3562J-C开发板因为内置了SPI转CAN-FD驱动,从而原生支持这一功能。该开发板…...
docker安装Emqx并使用自签名证书开启 SSL/TLS 连接
docker安装Emqx并使用自签名证书开启 SSL/TLS 连接 一、获取自签名证书1、创建openssl.cnf文件2、生成证书自签名证书 二、docker安装EMQX1、初始化目录2、加载镜像文件并挂载相应的文件目录3、启动docker容器4、EMQX加载自签名证书 三、客户端MQTTX连接测试四、Springboot整合…...
AI驱动的低代码平台:解密背后的算法与架构创新
引言 在如今的数字化浪潮中,企业对软件的需求正以前所未有的速度增长。传统的开发方式由于开发周期长、成本高,已逐渐无法满足市场的快速变化。而低代码平台的出现,使得开发者和业务人员能够以极简的方式快速构建应用。然而,随着企…...
ruoyi的excel批量导入
最简单方式 若依的官方文档提供了教程,可以按照起前后端的教学,进行代码编写 前段 组件 <!-- 导入对话框 --><el-dialogtitle"导入数据"v-model"openImport"width"500px"append-to-body><el-uploadref&quo…...
大数据-244 离线数仓 - 电商核心交易 ODS层 数据库结构 数据加载 DataX
点一下关注吧!!!非常感谢!!持续更新!!! Java篇开始了! 目前开始更新 MyBatis,一起深入浅出! 目前已经更新到了: Hadoop࿰…...
Spring Security
一.权限控制 1.1 认证和授权概念 问题1:在生产环境下我们如果不登录后台系统就可以完成这 些功能操作吗? 答案显然是否定的,要操作这些功能必须首先登录到系统才可 以。 问题2:是不是所有用户,只要登录成功就都可以操…...
OpenAI 正式发布 o1 完整版
OpenAI 在 o1 模型完整版,该模型相较于之前的 o1-preview 版本在智能能力上有所提升,特别是在编程能力方面,并且能够根据问题的难度智能调节响应速度。此外还新增了图像识别功能,但目前仍然不支持网页浏览、文件上传等功能 o1 模…...
Ubuntu22.04搭建LAMP环境(linux服务器学习笔记)
目录 引言: 一、系统更新 二、安装搭建Apache2 1.你可以通过以下命令安装它: 2.查看Apache2版本 3.查看Apache2运行状态 4.浏览器访问 三、安装搭建MySQL 1.安装MySQL 2.查看MySQL 版本 3.安全配置MySQL 3.1是否设置密码?(按y|Y表…...
C#与PLC通讯时,数据读取和写入浮点数,字节转换问题(ModbusTCP)
在与PLC进行通讯时,会发现一个问题,浮点数1.2接收过来后,居然变成了两个16位的整数。 经过一系列的分析,这是因为在PLC存储浮点数时32位,我们接收过来的数据会变成两个16位的高低字节,而且我们进行下发数据…...
synchronized的特性
1.互斥 对于synchronized修饰的方法及代码块不同线程想同时进行访问就会互斥。 就比如synchronized修饰代码块时,一个线程进入该代码块就会进行“加锁”。 退出代码块时会进行“解锁”。 当其他线程想要访问被加锁的代码块时,就会阻塞等待。 阻塞等待…...
NLP与LLM的工程化实践与学习思考 - 说说知识图谱
NLP与LLM的工程化实践与学习思考[24年半年工作总结] - 说说知识图谱 0 真的就是先说说1 为什么知识图谱什么是知识图谱?基于图的数据结构?基于数据结构的图?知识图谱的技术要点两个技术维度:知识、图七个技术要点:表示…...
php 系统函数 记录
PHP intval() 函数 PHP函数介绍—array_key_exists(): 检查数组中是否存在特定键名 如何使用PHP中的parse_url函数解析URL PHP is_array()函数详解,PHP判断是否为数组 PHP函数介绍:in_array()函数 strpos定义和用法 strpos() 函数查找字符串在另一字符串…...
游戏引擎学习第38天
仓库: https://gitee.com/mrxiao_com/2d_game 回顾上次的内容。 我们之前讨论了将精灵放在屏幕上,但颜色错误的问题。问题最终查明是因为使用了一个调整工具,导致文件的字节顺序发生了变化。重新运行“image magic”工具对一些大图像进行重新处理后&am…...
Android 15 行为变更:所有应用
Android 15 平台包含一些可能会影响您的应用的行为变更。以下行为变更将影响在 Android 15 上运行的所有应用,无论采用哪种 targetSdkVersion 都不例外。您应该测试您的应用,然后根据需要进行修改,以适当地支持这些变更。 此外,请…...
基于pytorch的深度学习基础4——损失函数和优化器
四.损失函数和优化器 4.1 均值初始化 为减轻梯度消失和梯度爆炸,选择合适的权重初值。 十种初始化方法 Initialization Methods 1. Xavie r均匀分布 2. Xavie r正态分布 4. Kaiming正态分布 5. 均匀分布 6. 正态分布 7. 常数分布 8. 正交矩阵初…...
《Clustering Propagation for Universal Medical Image Segmentation》CVPR2024
摘要 这篇论文介绍了S2VNet,这是一个用于医学图像分割的通用框架,它通过切片到体积的传播(Slice-to-Volume propagation)来统一自动(AMIS)和交互式(IMIS)医学图像分割任务。S2VNet利…...
Webpack Source Map 配置详解与优化策略
前言 Source Map 是前端开发和调试中的核心工具之一,它可以显著提高我们在代码调试和错误追踪方面的效率。随着 JavaScript 应用越来越复杂,代码打包和优化成为必然,而这一过程会使得调试变得异常困难。Source Map 的出现,为我们…...
el-tree组件刷新指定id的节点数据
示例 封装一个可以刷新多个指定id的节点数据。 <template><el-tree ref"treeRef"></el-tree> </template><script lang"ts" setup> const treeRef ref()function refreshTreeById(nodeIds: number[]) {nodeIds.forEach((…...
深入 Java 基础 XML:高级特性与最佳实践
在上一篇文章中,我们对 Java 基础 XML 有了一个初步的认识,了解了 XML 的基本结构以及在 Java 中常见的解析方式。今天,我们将进一步深入探讨 Java 与 XML 的结合,包括一些高级特性和最佳实践。 一、XML 命名空间 在复杂的 XML …...
aws(学习笔记第十六课) 使用负载均衡器(ELB)解耦webserver以及输出ELB的日志到S3
aws(学习笔记第十六课) 使用负载均衡器(ELB)以及输出ELB的日志到S3 学习内容: 使用负载均衡器(ELB)解耦web server输出ELB的日志到S3 1. 使用负载均衡器(ELB) 全体架构 使用ELB(Elastic Load Balancer)能够解耦外部internet访问和web server之间的耦合,…...
Ubuntu与Centos系统有何区别?
Ubuntu和CentOS都是基于Linux内核的操作系统,但它们在设计理念、使用场景和技术实现上有显著的区别。以下是详细的对比: 1. 基础和发行版本 Ubuntu: 基于Debian,使用.deb包管理系统。包含两个主要版本: LTSÿ…...
【OpenDRIVE_Python】使用python脚本读取txt指定内容,输出OpenDRIVE数据中对应的信息
示例代码说明: 读取txt指定内容如地物id,输出OpenDRIVE数据中的对应地物id和名称name信息为xml文件 import xml.dom.minidom from xml.dom.minidom import parse from xml.dom import Node import sys import os # 读取OpenDRIVE文件路径 xml_filepath…...
Qt入门8——Qt文件
1. Qt文件概述 文件操作是应用程序必不可少的部分。Qt作为⼀个通用开发库,提供了跨平台的文件操作能力。Qt 提供了很多关于文件的类,通过这些类能够对文件系统进行操作,如文件读写、文件信息获取、文件复制或重命名等。 2. 输入输出设备类 在…...
【每天一道面试题】JWT是什么?Java-jwt是什么?(2024/12/7)
【每天一道面试题】JWT是什么?Java-jwt是什么?(2024/12/7) JWT:JSON Web Token 俗称令牌 当我们想实现: 1.用户不用输入用户名和密码就可以登录(不用每次都输入用户名和密码) 2.用户的信息在传…...
ORB-SLAM3源码学习:ImuTypes.cc:Eigen::Matrix3f RightJacobianSO3计算右雅可比矩阵
前言 计算右雅可比矩阵这个函数涉及到了函数重载,可以接受不同的参数来实现计算右雅可比矩阵。 代码分析 右雅可比矩阵: /** * brief 计算右雅可比* param xyz 李代数* return Jr*/ Eigen::Matrix3f RightJacobianSO3(const float &x, const fl…...
电子公文交换系统设计 ——基于商用密码标准的密码模块的应用
文章目录 《密码系统设计》实验实验项目实验四 密码模块的应用实践要求(40 分) 《密码系统设计》实验 实验项目 实验序号实验名称实验学时数实验目的实验内容实验类型学生学习预期成果实验四密码模块的应用6基于商用密码标准的密码模块的应用对电子公文…...
java抽象类
目录 一.抽象类 1.什么是抽象类 2.抽象类特点 (1)抽象类不能直接实例化对象 (2)可以包含抽象方法和具体方法 (3)可以有构造方法 (4)抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使用 abstract 修…...
渤海证券基于互联网环境的漏洞主动防护方案探索与实践
来源:中国金融电脑 作者:渤海证券股份有限公司信息技术总部 刘洋 伴随互联网业务的蓬勃发展,证券行业成为黑客进行网络攻击的重要目标之一,网络攻击的形式也变得愈发多样且复杂。网络攻击如同悬于行业之上的达摩克利斯之剑&…...
ClouderaManager 集群搭建
前提:服务器之前做过域名映射、免密登录 ClouderaManager 集群 1. 组件分布规划 服务器服务器h1zk、hdfs(dn)、yarn(nm)、spark、kafka、flumeh2hdfs(nn-standy)、yarn(rm-active)、sparkh3hdfs(nn-active)、yarn(rm-standy)、hive、sparkh4zk、hdfs(dn)、yarn(n…...
Nginx部署PHP服务端跨域以及跨域携带cookie
🤵 作者:coderYYY 🧑 个人简介:前端程序媛,目前主攻web前端,后端辅助,其他技术知识也会偶尔分享🍀欢迎和我一起交流!🚀(评论和私信一般会回!!) 👉 个人专栏推荐:《前端项目教程以及代码》 ✨一、前言 前端技术栈Vue+后端技术栈PHP+Mysql鉴权以及存储信息用…...
流量转发利器之Burpsuite概述(1)
目录 一、Burpsuite Burp Suite Spider 的主要特点: 在 Burp Suite 中使用 Spider: Spider 的用例: 限制: 声明:学习视频来自b站up主 泷羽sec,如涉及侵权马上删除文章 声明:本文主要用作技…...
【优选算法 二分查找】二分查找算法入门详解:二分查找小专题
x 的平方根 题目解析 算法原理 解法一: 暴力解法 如果要求一个数(x)的平方根,可以从 0 往后枚举,直到有一个数(a),a^2<x,(a1)^2>x,a即为所求; 解法二:二分查找 …...
LeetCode—56. 合并区间(中等)
题目描述: 以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。 示例1: 输入&#x…...
SHELL----正则表达式
一、文本搜索工具——grep grep -参数 条件 文件名 其中参数有以下: -i 忽略大小写 -c 统计匹配的行数 -v 取反,不显示匹配的行 -w 匹配单词 -E 等价于 egrep ,即启用扩展正则表达式 -n 显示行号 -rl 将指定目录内的文件打…...
web斗地主游戏实现指北
前后端通信 作为一个即时多人游戏,不论是即时聊天还是更新玩家状态,都需要服务端有主动推送功能,或者客户端轮询。轮询的时间间隔可能导致游玩体验差,因为不即时更新,而且请求数量太多可能会打崩服务器。 建议在cs间…...
ES(elasticsearch)整合Spring boot使用实例
1.1通过docker安装es详细教程参考 docker部署elasticsearch(内涵集群部署的compose文件)-CSDN博客 2.1创建MySQL数据库,通过sql命令进行表的创建与数据的写入(sql命令如下) /*Navicat Premium Data TransferSource Server : localSo…...
创建简单的 PL/pgSQL 存储过程
文章目录 创建简单的 PL/pgSQL 存储过程CREATE OR REPLACE FUNCTIONadd_two_numbers(a integer, b integer)RETURNS integerAS$$ ... $$函数体LANGUAGE plpgsql 创建带有 IN 和 OUT 参数的存储过程创建修改数据的存储过程创建带有异常处理的复杂存储过程 在 PostgreSQL 中&…...
前端路径“@/“的使用和配置
环境:vitets 需要安装types/node npm install types/node --save-dev在tsconfig.json中添加 如果有tsconfig.app.json和tsconfig.node.json文件,则在app.json中添加 "compilerOptions": {"baseUrl":".","paths&q…...
彻底理解ThreadLocal的应用场景和底层实现
一.概念 定义: ThreadLocal 是 Java 中所提供的线程本地存储机制,可以利用该机制将数据缓存在某个线程内部,该线程可以在任意时刻、任意方法中获取缓存的数据。 其实是可以通过调用 Set() 方法往里面存入值,存入的值是每个线程互…...
机器学习(5)无监督模型之降维PCA算法
主成分分析(Principal Component Analysis, PCA) 是一种经典的无监督学习算法,主要用于数据降维、特征提取和数据可视化。它通过线性变换将数据从原始空间映射到一个新的空间,使得数据的方差最大化,从而实现降维。PCA …...