【Linux】文件描述符——万字详解

目录​​​​​​​

前言

预备知识 

复习C语言的文件接口

写方式打开文件

追加方式打开文件

读方式打开文件

系统的文件接口

open

close 

write

read

文件描述符

0 & 1 & 2 

理解文件描述符

文件描述符的分配规则

重定向的本质

dup2

理解Linux下一切皆文件


前言

本篇文章内容有点多,但是非常的重要,,希望你可以坚持看下去,看会你将会对Linux下一切皆文件有一个全新的理解。

如果内容有错或者不足,还望你能指出,让我们共同进步

预备知识 

知识点1 

当我们创建了一个空文件时,这个空文件是否会占空间呢?

答案是:当然啦,因为文件 = 内容 + 属性,文件的属性也是数据当然会占空间啦

因此我们对文件的操作无外乎是对文件的内容或者文件的属性做操作。

知识点2

文件会被加载到磁盘中,我们在C语言或者其它语言中通过编写代码并运行来访问文件,那么这本质是谁在访问文件呢?

答:是进程通过C语言或者其它语言给我们提供的文件接口来访问的。

知识点3 

我们的磁盘是属于硬件,我们普通用户是没有权利向硬件中写入的,只有操作系统才有这个权利,而我们也想要向硬件中写入的话必须通过操作系统提供给我们的接口才能向磁盘中写入。

知识点4

显示器也是属于硬件,我们在C语言中使用printf向显示器中打印时,其实和向磁盘中写入是一样的,没有本质的区别。 

知识点5

所有的语言给我们都提供了有文件访问接口,其实在底层实现都封装的是系统调用的接口,那么这些语言为什么要封装其接口呢?

封装的原因是系统调用的接口比较难,为了能让这些接口更好的给用户使用,所以在语言层面上对这些接口进行了封装。并且这些语言为了实现跨平台性,在底层把所用平台提供的文件接口都实现了一遍,通过条件编译的方式将这些代码进行动态裁剪(就跟多态才不多),这样就能实现在不同平台调用的文件访问接口都是一样的。

知识点6

Linux下一切皆文件,在这里先有个感性的认识,站在你写代码的角度,你会认为加载到内存中的就是文件,但站在系统的角度,能够被读取或者能够被写入的设备就叫做文件。

狭义上理解的文件:普通的磁盘文件

广义上理解的文件:几乎所有外设都可被称为文件。

复习C语言的文件接口

文件打开的方式

写方式打开文件

 先来看看下面的代码

#include <stdio.h>int main()
{FILE *fp = fopen("log.txt", "w");if(fp == NULL){perror("fopen");return 1;}fclose(fp);return 0;
}

我们都知道当我们执行这份代码时,如果当前路径下没有log.txt文件,w方式会在当前路径下创建log.txt文件,那么什么叫做当前路径呢?

所以当前路径是指当你的进程运行起来时所处的工作路径

在来看看下面的代码

#include <stdio.h>
#include <string.h>int main()
{FILE *fp = fopen("log.txt", "w");if(fp == NULL){perror("fopen");return 1;}const char *s1 = "hello man\n";fwrite(s1, strlen(s1), 1, fp);fclose(fp);return 0;
}

我们知道字符串的末尾是会加上一个'\0',那么这里的strlen(s1)是否要加1呢?

答案肯定是不要的,因为'\0'结尾是C语言的规定,文件不需要遵守,并且文件中要保存的是有效数据,'\0'不是有效数据。如果你加上1的话就会出现以下乱码的情况。

当我们以w方式写入时,是先清空文件中的内容再写入。

把上个图片中的hello man和乱码清除再写入hello Linux

这个就和输出重定向很像

追加方式打开文件

#include <stdio.h>
#include <string.h>int main()
{FILE *fp = fopen("log.txt", "a");if(fp == NULL){perror("fopen");return 1;}const char *s1 = "hello Linux\n";fwrite(s1, strlen(s1), 1, fp);fclose(fp);return 0;
}

 

读方式打开文件

#include <stdio.h>
#include <string.h>int main(int argc, char *argv[])//命令行参数
{if(argc != 2){printf("argv error");return 1;}FILE *fp = fopen(argv[1], "r");if(fp == NULL){perror("fopen");return 2;}char line[64];while(fgets(line, sizeof(line), fp) != NULL){fprintf(stdout, "%s", line);//向显示器输出}fclose(fp);return 0;
}

这样就实现了和cat一样的功能

在上面的代码中用到了一个stdout,这个stdout为标准输出

在C语言中会默认打开三个标准输入输出流:stdin、stdout、stderr

并且这三个流的类型都是FILE*的(解释在后面)

系统的文件接口

在上面已经说到了C语言标准库中给我们提供的文件操作的函数在底层其实是调用的系统提供的文件接口,那么系统给我们提供了哪些文件接口呢?下面让我们来认识一下这些文件接口

open

打开成功返回的是一个文件描述符,失败则返回-1

参数介绍

pathname:要打开的目标文件

flags:打开文件的方式。提供了多个选项,可传入多个选项共同构成这个参数

下面三个选项必须指定一个且只能指定一个

  • O_RDONLY:只读
  • O_WRONLY:只写
  • O_RDWR:可读可写

上面选项配合其它选项一起使用,如

  • O_APPEND 表示追加方式的写入文件
  • O_CREAT 表示如果指定文件不存在,则创建这个文件
  • O_TRUNC 表示截断,如果文件存在,并且以只写、读写方式打开,则将其长度截断为0。
  • ……

这些选项还有好多这里就不一一列举了,大家可以通过man进行查看

可以观察到这些选项都是大写,一般这种大写的基本上是宏定义,这里也不例外。

我们通过或(|)运算就可以将这些选项结合在一起使用了,例如:O_WRONLY|O_CREAT

为什么使用或运算就可以将这些选项结合在一起使用了呢?

其实这里是用到了一个位图的思想,看了下面代码想必你应该就会明白了

#include <stdio.h>
#include <string.h>//用不重复的位就可以标识一个状态
#define ONE 0x1 //0000 0001
#define TWO 0x2 //0000 0010
#define THREE 0x4 //0000 0100void print(int flags)
{//&运算:有0则为0,全1才为1if(flags & ONE) printf("I am ONE\n");if(flags & TWO) printf("I am TWO\n");if(flags & THREE) printf("I am THREE\n");
}int main()
{print(ONE);printf("--------------------------------\n");print(TWO);printf("--------------------------------\n");print(THREE);printf("--------------------------------\n");print(ONE | TWO | THREE);//0000 0001 | 0000 0010 | 0000 0100 = 0000 0111printf("--------------------------------\n");print(ONE | TWO);//0000 0001 | 0000 0010 = 0000 0011printf("--------------------------------\n");print(ONE | THREE);//0000 0001 | 0000 0100 = 0000 0101printf("--------------------------------\n");return 0;
}

库中也确实是这样干的

mode:为设置文件访问的权限

红色区域即为文件的权限,新建文件夹的默认权限是0666,但实际看到的权限不是这个值,因为还会受到umask的影响,umask默认是0002

使用

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);if(fd < 0){perror("open");return 1;}printf("open success, fd:%d\n", fd);return 0;
}

close 

关闭一个文件描述符,成功返回0,失败返回-1并且设置对应的错误码

和fclose的用法就差不多

write

返回值为实际写入的有效数据,ssize_t为有符号整形

参数介绍

fd:为文件描述符

buf:为要写入的数据

count:为写入数据的大小

使用演示

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

 

read

 

返回值的读取到的有效数据 

参数和write一样就不过多介绍了

使用演示

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int fd = open("log.txt", O_RDONLY);if(fd < 0){perror("open");return 1;}printf("open success, fd:%d\n", fd);char buffer[64];memset(buffer, '\0', sizeof(buffer));read(fd, buffer, sizeof(buffer));printf("%s\n", buffer);close(fd);return 0;
}

这里系统接口可不会给我们在末尾加上\0,所以我们需要手动在末尾加上'\0'。 

文件描述符

0 & 1 & 2 

先来看一下下面的代码 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int fd1 = open("log1.txt", O_WRONLY|O_CREAT);int fd2 = open("log2.txt", O_WRONLY|O_CREAT);int fd3 = open("log3.txt", O_WRONLY|O_CREAT);int fd4 = open("log4.txt", O_WRONLY|O_CREAT);printf("open success, fd1:%d\n", fd1);printf("open success, fd2:%d\n", fd2);printf("open success, fd3:%d\n", fd3);printf("open success, fd4:%d\n", fd4);close(fd1);close(fd2);close(fd3);close(fd4);return 0;
}

从上面的的代码可以看出fd都是从3开始的,那么前面的0,1,2去哪里了呢

在上面复习C语言文件接口时,提到过C语言中会默认打开三个标准输入输出流:stdin、stdout、stderr,这三个标准的输入输出流对应的就是0,1,2。

不信的话我们可以验证一下

stdout 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{fprintf(stdout, "hello stdout\n");const char *s1 = "hello 1\n";write(1, s1, strlen(s1));return 0;
}

stdin 

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{char buf[64];ssize_t s = read(0, buf, sizeof(buf));if(s > 0){buf[s] = '\0';//在读取到的有效数据的末尾添加\0printf("%s\n",buf);}return 0;
}

我们再回到C语言中,我们在使用C语言的文件函数时都见过FILE*,这个FILE*是一个指针,那么这个FILE是什么呢?

FILE其实是一个结构体,是C语言对底层做出的一个封装,里面包含了很多成员其中就有这个fd(文件描述符)。

#include <stdio.h>int main()
{printf("stdin:%d\n", stdin->_fileno);printf("stdout:%d\n", stdout->_fileno);printf("stderr:%d\n", stderr->_fileno);return 0;
}

理解文件描述符

进程要访问文件,必须先要打开文件,并且一个进程可以打开多个文件,那么如果多个进程都打开自己的文件,系统中就会存在大量被打开的文件,面对如此之多的文件,操作系统肯定要管理起来,而管理的本质就是先描述,再组织。所以在操作系统的内部为了管理每一个被打开的文件,会构建一个结构体,这个结构体中包含了一个被打开的文件的所有内容,然后再将这一个个的结构体用双链表组织起来。

当我们调用open时,必须让进程和文件关联起来,才能打开对应文件,那么我们的进程又是如何和文件关联起来的呢?

在每个进程的PCB中都有一个指针*files,指向一张表files_struct,该表中会包含一个指针数组,数组中的每个元素都是一个指向打开文件的指针,这就实现了和文件进行关联。

那么既然是数组就会有下标,并且是从0开始的,所以文件描述符的本质就是该指针数组的下标。所以只要拿着文件描述符,就可找到对应的文件。

文件描述符的分配规则

从上面的演示中我们就知道了0,1,2已经被三个标准输入输出流给占了,所以我们调用open打开文件时文件描述符(后面就简称fd了)只能从3以后开始,那么我们尝试close关闭0或者2看一下会发生什么现象?

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{//close(0);close(2);int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); close(fd);return 0;
}

所以fd的分配规则是:当前没有被使用的最小的一个下标

重定向的本质

当我们close关闭1又会发生什么现象呢?

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{close(1);int fd = open("log.txt", O_WRONLY|O_CREAT, 0666);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd); printf("fd: %d\n", fd);fflush(stdout);close(fd);return 0;
}

本该往显示器打印的内容结果打印到文件中去了。

这种现象是不是就和输出重定向是一样的了

本质

输入重定向

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{close(0);int fd = open("log.txt", O_RDONLY, 0666);if(fd < 0){perror("open");return 1;}printf("fd: %d\n", fd); char buffer[64];fgets(buffer, sizeof buffer, stdin);printf("%s\n", buffer);fflush(stdout);close(fd);return 0;
}

追加重定向

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{close(1);int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND);if(fd < 0){perror("open");return 1;}fprintf(stdout, "hello hjx\n");fflush(stdout);//刷新缓冲区close(fd);return 0;
}

但事实上重定向并不是这样实现的,我们上面的实现只是利用了fd的分配规则,而在操作系统中早就给我们准备了实现重定向的接口。

dup2

 

dup2的使用描述​​​​​​​

这里的意思就是将旧的文件描述符所对应的内容拷贝的新的文件描述符中,最后两个文件描述符是和旧的文件描述符保持一致,如果必要时把新的文件描述符关掉。

dup2的使用

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int fd = open("log.txt", O_WRONLY|O_CREAT|O_APPEND, 0666);if(fd < 0){perror("open");return 1;}dup2(fd, 1);fprintf(stdout, "hello dup2\n");close(fd);return 0;
}

理解Linux下一切皆文件

 Linux下一切皆文件时Linux的一个设计哲学,它是体现在操作系统的软件设计层面的。

我们都知道Linux是用C语言写的,那么你知道怎么用C语言来实现面向对象,甚至是运行时多态吗?

在C++或者其它语言中实现面向对象我们都知道是要用类,类中包含了成员属性和成员方法,但是C语言中只有结构体的概念,而结构体中只能包含成员属性,包含不了成员方法,那该怎么办呢?

没事,我们可以用到一个函数指针来实现其效果。

我们知道操作系统再往下就是硬件(比如磁盘、网卡、键盘、显示器等等)了,这些不同的硬件对应的一定是不同的驱动方法(你访问磁盘和访问键盘是不一样的),但是这些硬件都是外设,所以这每一个设备的核心驱动程序都可以是read/write——>I/O,根据冯诺依曼所有的外设无非都是I/O,所以所有的外设都可以有自己的read和write,但是代码实现一定是不一样的。

这种设计方案就叫做虚拟文件系统(VFS)。 

在Linux内核中也确实是这么干的。 

内核源码

 

struct file_operations {struct module *owner;loff_t (*llseek) (struct file *, loff_t, int);ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);int (*readdir) (struct file *, void *, filldir_t);unsigned int (*poll) (struct file *, struct poll_table_struct *);int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);int (*mmap) (struct file *, struct vm_area_struct *);int (*open) (struct inode *, struct file *);int (*flush) (struct file *);int (*release) (struct inode *, struct file *);int (*fsync) (struct file *, struct dentry *, int datasync);int (*aio_fsync) (struct kiocb *, int datasync);int (*fasync) (int, struct file *, int);int (*lock) (struct file *, int, struct file_lock *);ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);int (*check_flags)(int);int (*dir_notify)(struct file *filp, unsigned long arg);int (*flock) (struct file *, int, struct file_lock *);
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2968996.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

近屿OJAC带你解读:AIGC核心知识点LLM

近年来&#xff0c;人工智能&#xff08;AI&#xff09;领域经历了令人瞩目的增长&#xff0c;尤其是自然语言处理&#xff08;NLP&#xff09;。你知道是什么推动了NLP领域的这种飞速发展吗&#xff1f;没错&#xff0c;那就是大型语言模型LLM。这些模型可能会彻底改变我们与科…

【华为 ICT HCIA eNSP 习题汇总】——题目集17

1、以下哪项不属于网络层安全威胁&#xff1f; A、DDos攻击 B、钓鱼攻击 C、IP Spoofing D、IP地址扫描 考点&#xff1a;网络安全 解析&#xff1a;&#xff08;B&#xff09; 钓鱼攻击通常被认为是应用层的安全威胁&#xff0c;也有在网络层进行伪装实施钓鱼攻击&#xff0c;…

揭秘分享京东商品详情数据接口(商品属性,sku,价格)API接口可测试

今天给大家分享关于封装根据京东商品ID或商品链接批量获取京东商品详情数据接口方法&#xff0c;支持高并发请求。 如果你对京东的商品详情数据感兴趣&#xff0c;我建议你采取以下合法和合规的途径&#xff1a; 使用京东开放平台&#xff1a;京东开放平台提供了一系列的API接…

MySQL-使用CPP接入到MySQL

&#x1f4df;作者主页&#xff1a;慢热的陕西人 &#x1f334;专栏链接&#xff1a;MySQL &#x1f4e3;欢迎各位大佬&#x1f44d;点赞&#x1f525;关注&#x1f693;收藏&#xff0c;&#x1f349;留言 本博客主要内容介绍如何在c/cpp代码连接和管理数据库 文章目录 MySQL-…

中标麒麟系统VSCode 终端字体间距变大的解决办法

设置 一、打开设置-用户-功能-终端 二、搜索 Integrated: Font Family, 如下图 至于字体的设置&#xff0c;不同系统设置并不一样 在CentOS7内核的中标麒麟系统&#xff0c;需要设置字体为“Courier New”&#xff0c;显示比较正常 参考链接 Vscode——终端字体突然间距变大…

探索 IntelliJ IDEA 2024.1最新变化:全面升级助力编码效率

探索 IntelliJ IDEA 2024.1最新变化&#xff1a;全面升级助力编码效率 文章目录 探索 IntelliJ IDEA 2024.1最新变化&#xff1a;全面升级助力编码效率摘要引言 IntelliJ IDEA 2024.1 最新变化关键亮点全行代码补全 Ultimate对 Java 22 功能的支持新终端 Beta编辑器中的粘性行 …

网工内推 | 兴业银行总行正编,科技运维部,硕士以上学历

01 兴业银行 招聘岗位&#xff1a;安全渗透专家 职责描述&#xff1a; 1.负责牵头组织本行红蓝对抗、攻防演练等工作&#xff1b; 2.负责牵头制定有效的渗透测试方案&#xff0c;开展对本行防御体系的验证工作&#xff1b; 3.负责牵头组织本行各类应用系统的渗透测试与漏洞扫…

SpringBoot + Redis实现用户信息登录的缓存

&#x1f34e;前言 &#x1f350;项目的背景 背景&#xff1a;&#x1f349;当我们在完成用户信息登录时&#xff0c;我们往往每次都会在数据库中查询用户的记录&#xff0c;生成token并返回给前端&#xff0c;不过这样会有一定的问题。 &#x1f350;造成的问题 问题&#xf…

《游戏系统设计十二》灵活且简单的条件检查系统

目录 1、序言 2、需求 3、实现 3.1 思路 3.2 代码实现 4、总结 1、序言 每个游戏都有一些检查性的任务&#xff0c;在做一些判断的时候&#xff0c;判断等级是不是满足需求。 比如如下场景&#xff1a;在进入副本的时候需要检查玩家等级是否满足&#xff0c;满足之后才…

Nginx莫名奇妙返回了404

描述 nginx作为反向代理&#xff0c;代理python的服务&#xff0c;但是通过代理访问服务的时候&#xff0c;报了404的错误。 难受的是客户现场没有查看日志的权限&#xff0c;只有查看配置文件的权限&#xff0c;我们检测了几遍配置文件也没有找到问题&#xff0c;哎~ 问题引…

vue里面事件修饰符.stop使用案例

Vue.js 事件修饰符 .stop 用于阻止事件继续传播&#xff0c;即阻止事件冒泡。这在处理父子组件之间的事件通信时特别有用&#xff0c;可以防止事件从子组件冒泡到父组件&#xff0c;或者在一个元素上绑定多个事件处理函数时&#xff0c;阻止后续事件处理函数的执行。 下面是一个…

nodejs工具模块学习

util 是一个Node.js 核心模块&#xff0c;提供常用函数的集合&#xff1b; util.inspect(object,[showHidden],[depth],[colors]) 是一个将任意对象转换 为字符串的方法&#xff0c;通常用于调试和错误输出&#xff1b; 如果只有一个参数 object&#xff0c;是要转换的对象&…

Web前端 Javascript笔记6

BOM 前面的笔记讲的都是DOM&#xff08;文档对象模型&#xff09;&#xff0c;DOM几乎被所有浏览器支持&#xff0c;是DOM的作用为操作HTML文档的重要手段。利用DOM可以对HTML文档中的所有元素&#xff0c;节点进行获取与访问&#xff0c;对标签属性与样式进行设置。 下面是一…

Linux下SPI设备驱动实验:创建SPI节点及SPI设备子节点

一. 简介 SPI 驱动框架和 I2C 很类似&#xff0c;都分为主机控制器驱动和设备驱动。主机控制器驱动一般由半导体厂商写好&#xff0c;我们来编写SPI设备驱动代码。 前一篇文章分析了 IMX6U系列芯片的 SPI中片选信号的处理&#xff0c;文章如下&#xff1a; I.MX6ULL SPI 主机控…

学习Python先从了解Python开始

Python是一种高级编程语言&#xff0c;它的语法简洁易读&#xff0c;功能强大&#xff0c;应用领域广泛。Python不仅适用于数据科学、机器学习、Web开发等领域&#xff0c;还可以用于自动化脚本编写、游戏开发等。在本文中&#xff0c;我们将探讨Python的特点、应用领域以及未来…

噪声系数测试之增益法

提到增益法测试噪声系数,大家并不陌生,这是一种简洁的测试方法,精度不如Y因子法,但是在某些测试场合,比如只有频谱仪而没有噪声头时,且待测件具有非常高的增益时,就可以使用增益法测试噪声系数。 增益法测试噪声系数的连接示意图如图1所示,其思路为:DUT输入端端接50 …

【UE 材质】雨水流淌效果

在上一篇&#xff08;【UE 材质】雨滴效果&#xff09;基础上继续实现雨水从顶部沿着墙壁侧面向下流淌的效果 效果 步骤 1. 下载所需纹理 2. 新建一个材质函数&#xff0c;这里命名为“MF_Weather_Drips”&#xff0c;在材质函数中添加如下节点 其中输入节点的默认值分别为…

【leetcode面试经典150题】61. 反转链表 II(C++)

【leetcode面试经典150题】专栏系列将为准备暑期实习生以及秋招的同学们提高在面试时的经典面试算法题的思路和想法。本专栏将以一题多解和精简算法思路为主&#xff0c;题解使用C语言。&#xff08;若有使用其他语言的同学也可了解题解思路&#xff0c;本质上语法内容一致&…

【MIT6.824】lab2C-persistence, lab2D-log compaction 实现笔记

引言 lab2C的实验要求如下 Complete the functions persist() and readPersist() in raft.go by adding code to save and restore persistent state. You will need to encode (or “serialize”) the state as an array of bytes in order to pass it to the Persister. Us…

WebLogic 数据源连接泄露

编码时&#xff0c;有时会忘记释放使用的数据源连接&#xff0c;造成连接泄露&#xff0c;没有连接资源可用。 现象 java.sql.SQLException: Cannot obtain XAConnectionat weblogic.jdbc.jta.DataSource.refreshXAConnAndEnlist(DataSource.java:1691)at weblogic.jdbc.jta.…