【Linux文件篇】系统文件、文件描述符与重定向的实用指南

news2025/2/22 0:39:28

W...Y的主页 😊 

代码仓库分享💕 


 前言:相信大家对文件都不会太陌生、也不会太熟悉。在没有学习Linux操作系统时,我们在学习C或C++时都学过如何去创建、打开、读写等待文件的操作,知道一些语言级别的一些接口与函数。但是我相信对于没有学习操作系统的对文件的认识还很浅,今天我们就进入文件,好好学习操作系统与文件的关系。相信大家看完文件篇后会对文件有了新的认识和启发。话不多说我们开始学习。

目录

铺垫

代码回顾C文件接口

系统文件I/O 

open接口

write read close类比C文件相关接口

文件描述符fd

文件描述符的分配规则

 Linux中的重定向

 使用 dup2 系统调用

在自治shell中加入重定向 


铺垫

1.当我们创建一个文件但不对文件进行写入时,需不需要开辟一些空间呢?肯定是需要的。因为文件 = 内容 + 属性。文件的名称、创建时间、修改时间……对应的信息都需要进行存储。

2.我们访问文件之前,都得先打开文件,修改文件都是通过代码的方式完成修改的。而我们不能通过代码对磁盘中的文件直接做修改,是直接将文件加载到内存中后通过CPU进行对文件的修改。

3.我们在C语言中都知道,想要操作一个文件就必须使用fopen函数将文件打开,那是谁打开的文件呢?当我们在打开文件之前,必须要将想要访问文件的程序跑起来,所以归根结底是进程打开了文件。

4.一个进程也可以打开多个文件。所以一定时间段内,系统中存在多个进程,也可能存在更多被打开的文件。OS就要对这些被打开的文件进行管理,所以内核中一定要有描述被打开文件的结构体,并用其定义对象。

5.系统中不是所有的文件都是被打开的,被打开的文件叫内存文件,没有被打开的文件叫磁盘文件。

代码回顾C文件接口

hello.c写文件

#include <stdio.h>
#include <string.h>
int main()
{
    FILE *fp = fopen("myfile", "w");
    if (!fp)
    {
        printf("fopen error!\n");
    }
    const char *msg = "hello why!\n";
    int count = 5;
    while (count--)
    {
        fwrite(msg, strlen(msg), 1, fp);
    }
    fclose(fp);
    return 0;
}

fopen模式中有很多模式:

r : 打开文本文件进行阅读,流位于文件的开头。

r+:开放阅读和写作,流位于文件的开头。

w:截断(缩短)文件为零长度或创建文本文件进行写入,流位于文件的开头。

w+:开放阅读和写作,如果文件不存在,则创建该文件,否则将被截断,流位于文件的开头。

a:打开以进行追加(写在文件末尾)。如果文件不存在,则创建该文件,流位于文件的末尾。

a+:打开以供读取和追加(在文件末尾写入)。如果文件不存在,则创建该文件。初始文件位置。对于阅读是在文件的开头,但输出始终附加到文件末尾。

#include <stdio.h>
#include <string.h>
int main()
{
    FILE *fp = fopen("myfile", "r");
    if (!fp)
    {
        printf("fopen error!\n");
    }
    char buf[1024];
    const char *msg = "hello bit!\n";
    while (1)
    {
        // 注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明
        size_t s = fread(buf, 1, strlen(msg), fp);
        if (s > 0)
        {
            buf[s] = 0;
            printf("%s", buf);
        }
        if (feof(fp))
        {
            break;
        }
    }
    fclose(fp);
    return 0;
}

fwrite与fread函数都是C语言提供对文件读写的接口, 其就是将对应的内容输入或输出对应的文件流。

fread函数参数:

void * buffer 也就是想要把读取的数据到的地方的地址 比如一个叫做a的整形变量的地址 也就是&a 又或许是一个结构体对象 假设他叫s 那便是&s

size_t size 这就是接收数据的变量所占空间的大小 如果他是一个int类型 那就是sizeof(int)

size_t count 要写入数据的个数

FILE*stream 这是管理文件操作的指针 一般会在开始对文件开始操作前定义 这个指针会指向我们接下来要操作的文件

fwrite函数参数与fread参数相同,只是方向刚好相反。

 输出信息到显示器,你有哪些方法:

#include <stdio.h>
#include <string.h>
int main()
{
    const char *msg = "hello fwrite\n";
    fwrite(msg, strlen(msg), 1, stdout);
    printf("hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    return 0;
}

 stdin & stdout & stderr

C默认会打开三个输入输出流,分别是stdin, stdout, stderr
仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

系统文件I/O 

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:

写文件:

#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 fd = open("myfile", O_WRONLY | O_CREAT, 0644);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    int count = 5;
    const char *msg = "hello bit!\n";
    int len = strlen(msg);
    while (count--)
    {
        write(fd, msg, len); // fd: 后面讲, msg:缓冲区首地址, len: 本次读取,期望写入多少个字节的数据。 返回值:实际写了多少字节数据
    }
    close(fd);
    return 0;
}

 读文件:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
const char *msg = "hello bit!\n";
char buf[1024];
while(1){
ssize_t s = read(fd, buf, strlen(msg));//类比write
if(s > 0){
printf("%s", buf);
}else{
break;
}
}
close(fd);
return 0;
}

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: 追加写

其实我们可以使用|的方式将flags参数进行组合,因为他们就是宏。

返回值:
成功:新打开的文件描述符
失败:-1

mode的意思就是创建目标文件时所需要的权限。

 open 函数具体使用哪个,和具体应用场景相关,如目标文件不存在,需要open创建,则第三个参数表示创建文件的默认权限,否则,使用两个参数的open。

write read close类比C文件相关接口

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数

上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write lseek 都属于系统提供的接口,称之为系统调用接口
回忆一下我们讲操作系统概念时,画的一张图

系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。 

文件描述符fd

 通过学习open函数我们知道在成功打开文件后会返回一个文件描述符,而这些文件描述符就是一些整数,当我们打开多个文件后这些数字会从3开始一直往后延续,那为什么0、1、2没有人用呢?

0 & 1 & 2

Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.0,1,2对应的物理设备一般是:键盘,显示器,显示器

在系统层面我们可以看出,这写接口都是以的是系统提供的接口所使用的参数是文件描述符,而在语言层面上使用的都是File*指针,这个File是C语言分装的一个结构体。

 

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件。于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要拿着文件描述符,就可以找到对应的文件 。

  

上图是源码,所以文件描述符的本质就是数组下标!!! 

文件描述符的分配规则

直接看代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

输出发现是 fd: 3

关闭0或者2,在看

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main()
{
close(0);
//close(2);
int fd = open("myfile", O_RDONLY);
if(fd < 0){
perror("open");
return 1;
}
printf("fd: %d\n", fd);
close(fd);
return 0;
}

 发现是结果是: fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

 Linux中的重定向

我们知道重定向可以向文件中写入内容。

但是当我们使用 > 对文件进行重定向时,多次对文件进行写入,然后进行查看我们只能看到文件中只有我们最后一次写的内容,清空了之前写的所有内容。这不就是我们fopen函数中的‘w’模式。

 当我们使用>>操作符对文件进行重定向时,每次对文件写入都不会清空文件中的内容。所以这就类比fopen函数中的‘a’模式。

当我们现在已经知道重定向的含义以及文件描述符的分配规则,并且我们知道在语言层面是stdin、stdout、strerr,在系统层面就是0、1、2,当我们关闭1,然后使用写打开一个文件时,使用stdout就可以往文件中写入,这种现象叫做输出重定向。

输出重定向代码:

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

输入重定向代码:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
int main()
{
    close(0);
    int fd = open("test.txt", O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    int a = 0;
    scanf("%d", &a);
    printf("%d\n", a);
    close(fd);
    exit(0);
}

 追加重定向与输出重定向基本相同,唯一不同的就是将open函数中的flags参数改成O_WRONLY | O_CREAT|O_TRUNC即可。

 使用 dup2 系统调用

其中,oldfd表示待复制的文件描述符,newfd表示新的文件描述符。该函数返回新的文件描述符,若出错则返回-1。

dup2函数的工作原理相对简单,主要包括以下几个步骤:

  1. 检查oldfdnewfd是否相等,若相等则不进行任何操作,直接返回newfd
  2. 检查newfd的合法性,若已经打开,则先关闭;
  3. 复制oldfd的文件表项到newfd,使得两者指向同一个文件表项;
  4. 返回newfd

所以我们不需要进行close,直接使用dup2即可。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{
    int fd = open("./log", O_CREAT | O_RDWR);
    if (fd < 0)
    {
        perror("open");
        return 1;
    }
    close(1);
    dup2(fd, 1);
    for (;;)
    {
        char buf[1024] = {0};
        ssize_t read_size = read(0, buf, sizeof(buf) - 1);
        if (read_size < 0)
        {
            perror("read");
            break;
        }
        printf("%s", buf);
        fflush(stdout);
    }
    return 0;
}

在自治shell中加入重定向 

我们在命令行中可以使用echo指令进行重定向,我们之前做的简易shell中没有重定向,现在我们将代码补充完整:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>

#define SIZE 1024
#define MAX_ARGC 64
#define SEP " "
#define STREND '\0'

// 下面的都和重定向有关
#define NoneRedir  -1
#define StdinRedir  0
#define StdoutRedir 1
#define AppendRedir 2

#define IgnSpace(buf,pos) do{ while(isspace(buf[pos])) pos++; }while(0)

int redir_type = NoneRedir;
char *filename = NULL;

char *argv[MAX_ARGC];
char pwd[SIZE];
char env[SIZE]; // for test
int lastcode = 0;


// ls -a -l
// ls -a -l > log.txt


const char* HostName()
{
    char *hostname = getenv("HOSTNAME");
    if(hostname) return hostname;
    else return "None";
}

const char* UserName()
{
    char *hostname = getenv("USER");
    if(hostname) return hostname;
    else return "None";
}

const char *CurrentWorkDir()
{
    char *hostname = getenv("PWD");
    if(hostname) return hostname;
    else return "None";
}

char *Home()
{
    return getenv("HOME");
}

int Interactive(char out[], int size)
{
    // 输出提示符并获取用户输入的命令字符串"ls -a -l"
    printf("[%s@%s %s]$ ", UserName(), HostName(), CurrentWorkDir());
    fgets(out, size, stdin);
    out[strlen(out)-1] = 0; //'\0', commandline是空串的情况?
    return strlen(out);
}

void CheckRedir(char in[])
{
    // ls -a -l
    // ls -a -l > log.txt
    // ls -a -l >> log.txt
    // cat < log.txt
    redir_type = NoneRedir;
    filename = NULL;
    int pos = strlen(in) - 1;
    while( pos >= 0 )
    {
        if(in[pos] == '>')
        {
            if(in[pos-1] == '>')
            {
                redir_type = AppendRedir;
                in[pos-1] = STREND;
                pos++;
                IgnSpace(in, pos);
                filename = in+pos;
                break;
            }
            else
            {
                redir_type = StdoutRedir;
                in[pos++] = STREND;
                IgnSpace(in, pos);
                filename = in+pos;
                //printf("debug: %s, %d\n", filename, redir_type);
                break;
            }
        }
        else if(in[pos] == '<')
        {
            redir_type = StdinRedir;
            in[pos++] = STREND;
            IgnSpace(in, pos);
            filename = in+pos;
            //printf("debug: %s, %d\n", filename, redir_type);
            break;
        }
        else
        {
            pos--;
        }
    }
}

void Split(char in[])
{
    CheckRedir(in);
    int i = 0;
    argv[i++] = strtok(in, SEP); // "ls -a -l"
    while(argv[i++] = strtok(NULL, SEP)); // 故意将== 写成 =
    if(strcmp(argv[0], "ls") ==0)
    {
        argv[i-1] = (char*)"--color";
        argv[i] = NULL;
    }
}

void Execute()
{
    pid_t id = fork();
    if(id == 0)
    {
        int fd = -1;
        if(redir_type == StdinRedir)
        {
            fd = open(filename, O_RDONLY);
            dup2(fd, 0);
        }
        else if(redir_type == StdoutRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC);
            dup2(fd, 1);
        }
        else if(redir_type == AppendRedir)
        {
            fd = open(filename, O_CREAT | O_WRONLY | O_APPEND);
            dup2(fd, 1);
        }
        else
        {
            // do nothing
        }

        // 让子进程执行命名
        execvp(argv[0], argv);
        exit(1);
    }
    int status = 0;
    pid_t rid = waitpid(id, &status, 0);
    if(rid == id) lastcode = WEXITSTATUS(status); 
    //printf("run done, rid: %d\n", rid);
}

int BuildinCmd()
{
    int ret = 0;
    // 1. 检测是否是内建命令, 是 1, 否 0
    if(strcmp("cd", argv[0]) == 0)
    {
        // 2. 执行
        ret = 1;
        char *target = argv[1]; //cd XXX or cd
        if(!target) target = Home();
        chdir(target);
        char temp[1024];
        getcwd(temp, 1024);
        snprintf(pwd, SIZE, "PWD=%s", temp);
        putenv(pwd);
    }
    else if(strcmp("export", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1])
        {
            strcpy(env, argv[1]);
            putenv(env);
        }
    }
    else if(strcmp("echo", argv[0]) == 0)
    {
        ret = 1;
        if(argv[1] == NULL) {
            printf("\n");
        }
        else{
            if(argv[1][0] == '$')
            {
                if(argv[1][1] == '?')
                {
                    printf("%d\n", lastcode);
                    lastcode = 0;
                }
                else{
                    char *e = getenv(argv[1]+1);
                    if(e) printf("%s\n", e);
                }
            }
            else{
                printf("%s\n", argv[1]);
            }
        }
    }
    return ret;
}

int main()
{
    while(1)
    {
        char commandline[SIZE];
        // 1. 打印命令行提示符,获取用户输入的命令字符串
        int n = Interactive(commandline, SIZE);
        if(n == 0) continue;
        // 2. 对命令行字符串进行切割
        Split(commandline);
        // 3. 处理内建命令
        n = BuildinCmd();
        if(n) continue;
        // 4. 执行这个命令
        Execute();
    }
  
    return 0;
}

 上面就是简易shell完整版,代码仅供参考!!!


以上就是本次全部内容,感谢大家观看。

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

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

相关文章

怎样把便签里的内容移到桌面?桌面便签软件使用方法

每次打开电脑&#xff0c;我总是被满屏的文件和图标弄得眼花缭乱。那些记录在各式各样便签里的重要事项&#xff0c;经常被埋没在这信息的海洋中&#xff0c;找起来真是头疼。想必很多人都有过这样的困扰&#xff1a;如何在繁杂的桌面环境中&#xff0c;一眼就看到自己需要提醒…

问题:以下被纳入代理资产风险分类管理的业务包括() #媒体#知识分享

问题&#xff1a;以下被纳入代理资产风险分类管理的业务包括&#xff08;&#xff09; A&#xff0e;非标准化理财投资业务 B&#xff0e;特定债权投资业务 C&#xff0e;委托债权代理业务 D&#xff0e;非标准化代理销售业务 参考答案如图所示

HTML静态网页成品作业(HTML+CSS)—— 兰蔻化妆品网页(1个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有1个页面。 二、作品演示 三、代…

NAT

文章目录 1.NAT是什么2.NAT功能3.NAT优缺点4.NAT作用工作原理5.NAT 静态 动态5.1静态静态配置1.全局模式下设置静态NAT2.接口上设置静态NAT 5.2动态动态配置测试 6.PAT多路复用 PAT NAPT Easyip NAT server6.1PAT端口多路复用PAT作用 1.NAPT配置测试 2.EasyIp配置测试 3.NAT se…

Fegin如何传参form-data文件

Form-data传输file参数&#xff0c;这个大家都比较清楚&#xff0c;那么针对于Fegin参数file参数该如何操作呢&#xff01;下面截图来找到对应的参数关系。 一、之前我们在postMan中是这种传参的&#xff0c;那么如果使用Feigin来传输文件File 二、在Fegin中传form-data参数&a…

Qt系统相关

本文目录 1.Qt事件事件的处理标签事件鼠标事件滚轮事件按键事件定时器事件窗口事件事件派发器 2.Qt文件操作QFile的基本使用 3.Qt多线程使用线程线程锁connect的第五个参数 条件变量和信号量 4.Qt网络编程UDP SocketTCP SocketQTcpServerQTcpSocket HTTP的编写 5.QT多媒体播放音…

【区块链】深入解析Proof of Work (PoW): 区块链技术的核心驱动力

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 深入解析 Proof of Work (PoW): 区块链技术的核心驱动力引言一、PoW基本概念1.1…

UNetMultiLane 多车道线、车道线类型识别

基于UNet 分割模型增加了检测头来识别车道线的类型&#xff08;单实线、双黄线等10种&#xff09;&#xff0c;同时可以识别出"所在车道"和"车道线类型"。 训练代码【训练练手代码】 1 数据说明 基于开源数据集 VIL100。其中数据标注了所在的六个车道的车…

大福利!微信付费进群源码

微信付费进群源码 前言效果图搭建教程源码领取下期更新预报 前言 1、修复SQL表 2、修复支付文件 3、修复支付图标不显示 4、修复定位、分销逻辑、抽成逻辑 5、新增支持源支付、易支付的支付接口 6、修复官方微信、支付宝支付接口文件 本来早就可以完工的&#xff0c;电脑…

苹果WWDC 2024速览

引言 2024年苹果全球开发者大会&#xff08;WWDC&#xff09;在充满科技感的苹果园区拉开帷幕&#xff0c;本次大会发布了众多令人振奋的软硬件更新&#xff0c;包括Vision OS 2、iOS 18、watchOS 11和macOS Sequoia的重大创新。本文将详细解读这些更新&#xff0c;为您展示它…

来自中国信通院的认可!上海斯歌受邀加入“EP-Link 智能流程推进计划”

5月30日&#xff0c;由中国信息通信研究院主办的“EP-Link 智能流程推进计划”周年产业研讨会在北京隆重召开。会上&#xff0c;在众多领袖专家、行业翘楚的见证下&#xff0c;上海斯歌被中国信息通信研究院授予“EP-Link 智能流程推进计划”成员单位证书&#xff0c;正式加入该…

Vxe UI vxe-modal 实现窗口的最大化与最小化,实现弹出多窗口

Vxe UI vue vxe-modal 实现窗口的最大化与最小化&#xff0c;实现弹出多窗口 通过js方式调用 js方式脱离模板&#xff0c;每次创建是多个实例。 mask&#xff1a;关闭遮罩层&#xff0c;如果不关闭则会显示遮罩层&#xff0c;就不能实现同时操作多窗口 lockView&#xff1a;…

IDA反调试、反汇编解决方案

IDA全称为Interactive Disassembler Professional&#xff0c;是一款交互式静态反汇编工具&#xff0c;它可以直接反汇编出二进制文件的汇编代码&#xff0c;是逆向分析领域最强大的工具之一。 IDA支持Windows、Linux等多平台与数十种指令集&#xff0c;凭借着泛用性与友好的图…

弘君资本股市资讯:突发!英伟达,被查!

4家科技巨擘一同被查&#xff01; 科技巨擘们&#xff0c;正迎来反独占风暴。据美媒报道&#xff0c;美国将对英伟达、微软、OpenAI翻开反独占查询&#xff0c;这标志着监管部门对人工智能工作的查看力度越来越大。 与此一同&#xff0c;苹果也面对新费事。6月6日&#xff0c…

机器学习-- 如何清洗数据集

文章目录 引言&#xff1a;数据清洗的具体步骤数据清洗的具体方法和示例1. 处理缺失值2. 去除重复数据3. 修正数据格式4. 处理异常值5. 标准化和归一化6. 处理不一致的数据7. 转换数据类型8. 数据集成 总结 引言&#xff1a; 数据清洗是数据处理和分析的关键步骤&#xff0c;旨…

如何预览XtraGrid控件

如何预览XtraGrid。 private void ShowGridPreview(DevExpress.XtraGrid.GridControl grid) {// Check whether the Grid Control can be previewed.if(!grid.IsPrintingAvailable) {MessageBox.Show("The DevExpress.XtraPrinting Library is not found", "Er…

linux-ubuntu20网卡驱动安装AX201

https://blog.csdn.net/vor234/article/details/131682778 联想拯救者Y7000P2023 Ubuntu20.04网卡驱动AX211安装 幻14 ubuntu20.04 AX210驱动安装 官网下载相应的驱动&#xff1a;https://www.intel.com/content/www/us/en/support/articles/000005511/wireless.html sudo a…

【全开源】生产管理系统源码(FastAdmin+ThinkPHP+Layui+uniapp)

&#x1f525;揭秘高效生产管理系统&#xff0c;让你的企业腾飞&#xff01;&#x1f680; 一款基于FastAdminThinkPHPLayuiuniapp开发的生产管理系统&#xff0c;帮助企业数字化转型&#xff0c;打造智能工厂&#xff0c;专业为生产企业量身开发的一套完整的生产管理系统。主…

【天池科普】1. 为啥人人都要学AI

大家好&#xff01;欢迎来到天池的AI科普系列课程&#xff0c;本期是第一期内容。在这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;不仅是技术进步的标志&#xff0c;更是推动社会向前发展的强大引擎。无论你是AI领域的新手&#xff0c;还是有一定基础的学…

通过PWmat发《npj Computational Materials》:rt-TDDFT揭示冷冻电镜中的保护机制

背景导读 自20世纪80年代以来&#xff0c;冷冻电镜&#xff08;cryo-EM&#xff09;已成为生物学领域最有用的工具之一。最近&#xff0c;冷冻电镜也被用于材料研究。与传统的透射电镜&#xff08;TEM&#xff09;相比&#xff0c;冷冻电镜通过液氮冷冻样品&#xff0c;可以有…