Linux 基础IO与系统IO - 实践

news/2025/9/29 19:39:36/文章来源:https://www.cnblogs.com/slgkaifa/p/19119385

Linux 基础IO与系统IO - 实践

从本章开始,我们逐渐导入文件系统的知识。要讲文件系统,文件与IO的知识就不得不讲,本章我们将从文件的认识和语言级别、系统级别的读写接口入手,深入了解基础IO与系统IO。

一.基础IO

1、文件认识

首先我们要明确:

文件=属性+内容

文件的读写实际上是进程对文件的操作

访问文件,首先要打开文件。执行完了open函数并成功,才算打开成功,那么是谁打开的文件?进程打开的文件,对文件的操作也就是进程对文件的操作。操作系统需要给用户提供系统级的文件操作系统调用。

系统中一定会有大量被打开的文件,各自与各自的(甚至多个)进程相关,操作系统自然就需要对这些被打开的文件管理:先描述,在组织。

宏观上,文件需要分两大类:“内存级”(被打开)的文件和磁盘级文件。下面我们慢慢进行展开。

2、语言层的IO

1.在这里我们用C语言的IO接口做演示,C语言的IO方式多种多样,这里我们就用最简单的文件打开、插入文字、关闭文件为例子。

1 #include2 #include34 int main(){5     FILE *fp=fopen("log.txt","w");67     if(fp==NULL){8         perror("fopen");9         return 1;10     }11 //我们用文本写入方式12     const char *msg="hello linux:";13     int cnt=1;14     while(cnt<=5){15         char buffer[1024];16         snprintf(buffer,sizeof(buffer),"%s%d",msg,cnt++);17         fwrite(buffer,strlen(buffer),1,fp);18     }1920     fclose(fp);21     return 0;2223 }

在这里我们调用C语言的fopen打开文件,若不存在则创建,写入方式打开。

创建一个字符数组并写入想要的数据,然后使用snprintf格式化输入进buffer数组中以便文本写入

然后将buffer的内容写入文件log.txt

运行效果如下:

wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
wujiahao@VM-12-14-ubuntu:~/file_test$ ls
log.txt  Makefile  myfile  myfile.c
wujiahao@VM-12-14-ubuntu:~/file_test$ cat log.txt
hello linux:1hello linux:2hello linux:3hello linux:4hello linux:5wujiahao

2.我们还可以用C语言的接口fread实现简单的cat指令。为了能读取任意文件的内容,我们需要在程序的main函数中添加命令行参数argv和argc来接受外界(bash)的参数传递。

在此之前我们需要强调以下fread函数的参数和返回值。当fread执行成功时,会返回读取的字符传长度。

SYNOPSIS#include size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);size_t fwrite(const void *ptr, size_t size, size_t nmemb,FILE *stream);
RETURN VALUEOn  success, fread() and fwrite() return the number of items read or written.  This number equals the number of bytes transferred only when size is 1.  If an error occurs, or the end of the file isreached, the return value is a short item count (or zero).

源码如下:

 1 #include2 #include34 int main(int argc,char *argv[]){5     if(argc!=2){6         printf("useage error!\n");7         return 1;8     }9     FILE *fp=fopen(argv[1],"r");10     if(!fp)11     {12     printf("fopen error!\n");13     return 2;14     }1516     char buffer[1024];17     while(1){18         int s=fread(buffer,1,sizeof(buffer),fp);19         if(s>0){20             buffer[s]=0;21             printf("%s",buffer);22         }23         if(feof(fp)){24             break;25         }26     }2728     fclose(fp);29     return 0;3031 }

运行结果:

wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
useage error!
wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile Makefile
myfile:myfile.cg++ -o $@ $^ -std=c++11 #-std=c99
.PHONY:clean
clean:rm -f myfile

3.标准输入输出流

在之前语言的学习中我们就清楚,C语言在程序启动前都会默认打开三个输入输出流:

stdin:标准输入,键盘文件

stdout:标准输出,显示器文件

stderr:标准错误,显示器文件

除此之外,我们使用的printf函数默认向文件描述符为1的标准输出流打印数据,而fprintf函数可以指定其他输出流。我们可以先看看标准输入输出流的类型——是FILE *。而我们上面打开的文件指针fp等等也是这个类型。

SYNOPSIS#include extern FILE *stdin;extern FILE *stdout;extern FILE *stderr;

那么为什么要自动打开这三个输入输出流呢?

程序是为了处理数据,这些数据流的打开是给程序提供默认的数据源

以读w方式打开文件,文件会先被清空。拿重定向为例,我们第一次输入aaa到文件log.txt,第二次再写入bbb时会被覆盖,原因是会先把文件清空再进行写入。

若把打开方式设置为a的话,就是追加写入——从文件的读写位置末尾开始写。

 1 #include2 #include34 int main(){5     FILE *fp=fopen("log.txt","a");6     if(fp==NULL){7         printf("fopen error");8         return 1;9     }1011     const char* msg="hello world\n";12     fprintf(fp,"%s",msg);1314     fclose(fp);1516     return 0;1718 }

可以发现,正常在文件末尾追加内容。

wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
wujiahao@VM-12-14-ubuntu:~/file_test$ cat log.txt
hello linux:1hello linux:2hello linux:3hello linux:4hello linux:5hello world
wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
wujiahao@VM-12-14-ubuntu:~/file_test$ cat log.txt
hello linux:1hello linux:2hello linux:3hello linux:4hello linux:5hello world
hello world
hello world
hello world

追加重定向的操作与此类似。

wujiahao@VM-12-14-ubuntu:~/file_test$ echo "test">>log.txt
wujiahao@VM-12-14-ubuntu:~/file_test$ echo "test">>log.txt
wujiahao@VM-12-14-ubuntu:~/file_test$ echo "test">>log.txt
wujiahao@VM-12-14-ubuntu:~/file_test$ cat log.txt
hello linux:1hello linux:2hello linux:3hello linux:4hello linux:5hello world
hello world
hello world
hello world
test
test
test

3、系统级的IO

讲完C语言提供的IO接口,我们来了解一下文件和系统级的IO接口。

我们可以把文件想象成一个一维数组,它的下标就是读写位置,每写进去一个内容就会向后移动,因此我们一写完就读会发现读取失败,是因为读写位置此时为空,我们需要对读写位置进行操作,用fseek系列函数

SYNOPSIS#include int fseek(FILE *stream, long offset, int whence);long ftell(FILE *stream);void rewind(FILE *stream);int fgetpos(FILE *stream, fpos_t *pos);int fsetpos(FILE *stream, const fpos_t *pos);

写字符串时不要把\0写进去,C语言中代表字符串结尾,但是系统与语言没有必然关系,输入\0会输出^@乱码。

1.open系统调用

SYNOPSIS#include #include #include int open(const char *pathname, int flags);int open(const char *pathname, int flags, mode_t mode);

open系统调用第一个参数就是打开的文件路径,第二个参数是标志位,第三个参数是按特定权限打开。

文件路径不必多说,我们需要在标志位上费一些笔墨讲解。

首先看看标志位是什么。

O_CLOEXEC, O_CREAT, O_DIRECTORY, O_EXCL, O_NOCTTY, O_NOFOLLOW, O_TMP‐ FILE, and O_TRUNC.

这里的标志位实际上类似于一些32位二进制数的宏,这些宏的意义其实就是C语言中我们说的打开方式。之所以要这样设计,是为了进行二进制传参。

操作系统在传参时是一个保守且谨慎的的行为,在open一个函数传入过多的参数可能会造成不必要的麻烦,而二进制传参就能很好的避免这一问题。实际上,open在接受flag时会进行按位或的操作来接受多个参数的传入,如下演示:

 1 #include2 #include34 #define ONE_FLAG (1<<0)5 #define TWO_FLAG (1<<1)6 #define THREE_FLAG (1<<2)7 #define FOUR_FLAG (1<<2)8910 void Print(int flags){11     if(flags & ONE_FLAG){12         printf("one!\n");13     }14     if(flags &TWO_FLAG){1516         printf("two!\n");17     }18     if(flags &THREE_FLAG){1921         printf("three!\n");22     }23     if(flags & FOUR_FLAG){2427         printf("four!\n");28     }29 }3031 int main(){32     Print(ONE_FLAG);33     Print(ONE_FLAG|TWO_FLAG|THREE_FLAG|FOUR_FLAG);34     return 0;35 }

结果如下

wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
one!
one!
two!
three!
four!

大致了解open的参数之后,我们可以来试着用一下它。

1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 89 int main(){10     int fd=open("log.txt",O_CREAT|O_WRONLY,0666);11 if(fd<0)12 {13     perror("open");14     return 1;15 }1617     return 0;18 }

那问题来了,为什么other的权限还是664?因为我们系统中存在umask影响。如果设置权限时不想受系统umask影响,我们在代码中设置当前文件的掩码为0即可。

-rw-rw-r--  1 wujiahao wujiahao     0 Sep 28 20:21 log.txt

关闭文件时我们需要close,传入open的返回值fd。

在这里我们现将问题保留,就是open的返回值fd。

2.write系统调用

1.write系统调用的参数比较简单,传入打开的文件描述符fd,要写入的字符数组,以及写入的长度。写入成功时返回写入的长度。

SYNOPSIS#include ssize_t write(int fd, const void *buf, size_t count);
int main(){10     int fd=open("log.txt",O_CREAT|O_WRONLY,0666);11 if(fd<0)12 {13     perror("open");14     return 1;15 }16     printf("fd:%d\n",fd);1718     const char *msg="hello!\n";19     int cnt=5;20     while(cnt--){21         write(fd,msg,strlen(msg));22     }2324     close(fd);2526     return 0;27 }

我们查看log,可以看到成功写入。

wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
fd:3
wujiahao@VM-12-14-ubuntu:~/file_test$ cat log.txt
hello!
hello!
hello!
hello!
hello!

奇怪的是,我们在写一行,并且写入的消息不带反斜杠,会发现奇怪的现象:

const char *msg="its new";
int cnt=1;

文件中是覆盖写,而不是清空写。

wujiahao@VM-12-14-ubuntu:~/file_test$ cat log.txt
hello!
hello!
hello!
hello!
hello!
wujiahao@VM-12-14-ubuntu:~/file_test$ vim myfile.c
wujiahao@VM-12-14-ubuntu:~/file_test$ make
g++ -o myfile myfile.c -std=c++11 #-std=c99
wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
fd:3
wujiahao@VM-12-14-ubuntu:~/file_test$ cat log.txt
its newhello!
hello!
hello!
hello!

那是因为在系统IO中,我们open传参时仅仅是创建并且只读,我们只有指定了清空写时他才会清空写,不要把系统的概念和C语言的概念混淆。open的清空,选项为TRUNC。

如果我们要追加,就不能选TRUNC,因为append是从末尾追加,清空是从开头写。

基础IO与系统IO:C语言的fopen在选择w或者a选项时,会对应转换成系统调用的选项。


2.文本写入vs二进制写入

我们往文件中写一个整数。

 int cnt=1;20     int a=12345;21     while(cnt--){22         write(fd,&a,sizeof(a);23     }

结果输出一个乱码?

这种写入是一种二进制写入,文件大小也变成4字节。

-rw-rw-r--  1 wujiahao wujiahao     4 Sep 28 20:39 log.txt

那如果我们把整数a用格式化输入snprintf处理,然后再将一个字符串数组写入文件:

 19     int cnt=1;20     int a=12345;21     while(cnt--){22         char buffer[16];23         snprintf(buffer,sizeof(buffer),"%d",a);24         write(fd,buffer,strlen(buffer));25     }

这时的输出内容就正常了。

♦♦现象揭秘:

作为操作系统,根本不关心用户是二进制写入还是文本写入(write写入的类型都是void*),所谓的文本写入和二进制写入是一个语言层的概念(也就是用户的需求),所以c语言中就有了文本写入函数fputs和二进制写入fwrite等对系统调用不同程度封装的接口。

格式化是在干什么?将二进制的数据转换成文本,也是语言层的工作。

我们不用太关注二进制或文本写入,对于显示器这种设备被称为字符设备的原因,那就是它基本是文本输出的。

3.read系统调用

对于read,传参也同样需要传入文件描述符fd。

SYNOPSIS#include ssize_t read(int fd, void *buf, size_t count);

从指定文件描述符fd读,因为读文件新建没有意义,所以我们这里只需要传一个选项可读即可。

 1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 89 int main(){10     int fd=open("log.txt",O_RDONLY);11     if(fd<0)12     {13         perror("open");14         return 1;15     }16     printf("fd:%d\n",fd);1718     while(1){19         char buffer[64];20         int n=read(fd,buffer,sizeof(buffer)-1);21         if(n>0){22             buffer[n]=0;23             printf("%s",buffer);24         }25         else if(n==0)26         {27             break;28         }29     }

结果如下:我们将log的内容读到字符数组buffer中,并回显到屏幕上。

wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
fd:3
12345

和上面的道理类似,读出来是什么格式系统不关心,但用户需要文本输出的话就需要自己处理。

可以说Linux系统中只有open,write,read,close这四个读写接口,语言层都是对他们进行不同程度封装的结果

4.文件描述符fd

现在,来详细说说,open的返回值fd是什么?

fd叫文件描述符,他是一个整数。打开多个文件试试:

 1 #include 2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 89 int main(){10         umask(0);11     int fd1 = open("log1.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);12     int fd2 = open("log2.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);13     int fd3 = open("log3.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);14     int fd4 = open("log4.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);15     if(fd1 < 0)exit(1);16     if(fd2 < 0)exit(1);17     if(fd3 < 0)exit(1);18     if(fd4 < 0)exit(1);1920     printf("fd1: %d\n", fd1);21     printf("fd2: %d\n", fd2);22     printf("fd3: %d\n", fd3);23     printf("fd4: %d\n", fd4);2425     close(fd1);26     close(fd2);27     close(fd3);28     close(fd4);

结果:

wujiahao@VM-12-14-ubuntu:~/file_test$ ./myfile
fd1: 3
fd2: 4
fd3: 5
fd4: 6

可以说,打开文件成功时的返回值都是大于0的。那么大家有没有好奇,0,1,2都是些啥?

没错,就是操作系统默认为我们打开的stdin,stdout,stderr:标准输入输出流。

我们可以预测的是,fopen的返回值FILE中一定封装了文件描述符fd,OS中只认文件描述符fd,C语言的东西一概不认。而这些标准输入输出流 中FILE封装的fd一定为0,1,2。我们可以简单验证:

 printf("stdin: %d\n", stdin->_fileno);20      printf("stdout: %d\n",stdout->_fileno);21      printf("stderr: %d\n", stderr->_fileno);

结果:

stdin: 0
stdout: 1
stderr: 2
fd1: 3
fd2: 4
fd3: 5
fd4: 6

那么文件描述符fd是什么?

什么样的数据,会呈现0123456的样式?

没错,就是数组。

操作系统在打开一个文件时,会创建一个struct file:

那么打开多个文件,就会有:

struct file会对应链接一个文件缓冲区,那么它对应的文件内容会被加载到缓冲区,文件属性会被初始化struct file。当前进程通过文件描述符拿到文件,也会对应创建一个文件描述符表struct file_struct,包含存放fd的数组以及当前打开的文件指针数组,那么当前进程为了访问对应的文件,就需要拿到文件描述符标的数组下标进行访问。

当用户层使用open时,就创建一个struct file,并在文件描述符标的指针数组中找一个没使用过的下标,把struct file的地址填入。

用户调用read时,创建一个buffer来读数据,操作系统拿着fd对数组进行索引找到对应文件,将struct file的文件缓冲区,再把缓冲区内容拷贝到用户创建的buffer中,read本质是内核到用户空间的数据拷贝

那么增改文件,对于冯氏结构我们知道,CPU无法直接对磁盘进行操作,所以需要先把磁盘文件内容加载到缓冲区,然后再内存中做修改,最后在写回磁盘。

也就是说,对文件内容进行任何操作,都需要把文件从磁盘拷贝到内存(加载到内核对应的文件缓冲区)。


5.重定向的原理

文件描述符的分配原则:当我们新打开一个文件时,会在表中分配最小的且没有被使用的文件描述符fd。

我们关闭fd1,新打开文件log.txt就会被默认分配到较小下标的1处。而我们执行printf时默认向stdout(fd=1)打印,而此时fd1指向的是log,所以此时打印会写到log中。注意这里后面没有对log关闭。

这就是重定向原理——更改文件描述符表的指针指向,数组下标不变。

dup2系统调用

我们上面对files_struct的指针指向修改属于对内核数据结构的修改,所以我们需要dup2系统调用做这件事。

这里需要注意的是dup2的用法。dup2会使newfd生成一份oldfd的拷贝,也就是说我们如果要把fd1的默认指向从标准输出指向我们自己的myfile,应该传参dup2(fd,1)。

本来要打印到显示器中的内容,打印到了log中。

如果我们想实现任意文件的输入重定向,就在main的参数中传入argv,让外部调用时的输入直接从指定的文件读取

重定向:打开文件的方式+dup2.

明确了重定向的原理,我们可以完善上章的简单shell重定向功能。

shell重定向

先通过宏定义重定向方式和文件名。

// 4. 关于重定向,我们关心的内容
#define NONE_REDIR 0
#define INPUT_REDIR 1
#define OUTPUT_REDIR 2
#define APPEND_REDIR 3
int redir = NONE_REDIR;
std::string filename;

这次我们在解析命令前,先要解析是不是重定向操作,需要空格消除方法。

void TrimSpace(char cmd[], int &end)
{while(isspace(cmd[end])){end++;}
}

然后判定重定向方式。

void RedirCheck(char cmd[])
{redir = NONE_REDIR;filename.clear();int start = 0;int end = strlen(cmd)-1;//"ls -a -l >> file.txt" > >> <while(end > start){if(cmd[end] == '<'){cmd[end++] = 0;TrimSpace(cmd, end);redir = INPUT_REDIR;filename = cmd+end;break;}else if(cmd[end] == '>'){if(cmd[end-1] == '>'){//>>cmd[end-1] = 0;redir = APPEND_REDIR;}else{//>redir = OUTPUT_REDIR;}cmd[end++] = 0;TrimSpace(cmd, end);filename = cmd+end;break;}else{end--;}}
}

重定向处理:让子进程进行重定向操作。

int Execute()
{pid_t id = fork();if(id == 0){int fd = -1;// 子进程检测重定向情况if(redir == INPUT_REDIR){fd = open(filename.c_str(), O_RDONLY);if(fd < 0) exit(1);dup2(fd,0);close(fd);}else if(redir == OUTPUT_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_TRUNC, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else if(redir == APPEND_REDIR){fd = open(filename.c_str(), O_CREAT | O_WRONLY | O_APPEND, 0666);if(fd < 0) exit(2);dup2(fd, 1);close(fd);}else{}// 进程替换,会影响重定向的结果吗?不影响//childexecvp(g_argv[0], g_argv);exit(1);}int status = 0;// fatherpid_t rid = waitpid(id, &status, 0);if(rid > 0){lastcode = WEXITSTATUS(status);}return 0;
}

此时主函数main的逻辑如下:

   InitEnv();while(true){// 1. 输出命令行提示符PrintCommandPrompt();// 2. 获取用户输入的命令char commandline[COMMAND_SIZE];if(!GetCommandLine(commandline, sizeof(commandline)))continue;// 3. 重定向分析 "ls -a -l > file.txt" -> "ls -a -l" "file.txt" -> 判定重定向方式RedirCheck(commandline);
//       printf("redir: %d, filename: %s\n", redir, filename.c_str());// 4. 命令行分析 "ls -a -l" -> "ls" "-a" "-l"if(!CommandParse(commandline))continue;//PrintArgv();// 检测别名// 5. 检测并处理内键命令if(CheckAndExecBuiltin())continue;// 6. 执行命令Execute();}

进程替换会影响重定向吗?毫不影响。因为程序替换是进程和进程地址空间,页表,物理内存的数据代码交互,而重定向改变的是内核数据结构的东西,我们后续子进程的操作都是在重定向后的文件中进行操作的

如果我们重定向的对象是网络,是不是就可以理解为进行网络的输入输出了?

在做内建命令重定向的处理,我们可以打开另一个临时文件保存标准输入输出流的指针,先dup2让他执行别的操作,然后再dup2回来即可,比较类似我们之前的swap操作。

tips:一个文件可以被多个进程打开,采用引用计数管理。


6.可移植性

那么我们之前学习的文件操作,FILE返回值封装了文件描述符,接口封装了系统调用。

我们来谈谈封装。为什么封装?

上层写的代码能在不同平台下能运行是由库屏蔽细节,实现可移植性。把平台之间的差异封装到库中。

C++等其他语言的接口如何呢?每一门语言对IO流的操作都各不相同,不管他们上层封装再怎么变化,底层都只认文件描述符fd。

要谈论可移植性,首先要说什么是不可移植性。是平台的原因。操作系统不一样,系统调用接口不一样,那么平台之间的可移植性就比较差。语言为了增加自己的可移植性,所以对不同平台都做了封装实现。

那为什么语言要增加自己的可移植性?平台背后是用户,语言增加可移植性是为了让更多用户用,有着他们自己的商业驱动。

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

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

相关文章

从零开始,使用Idea工具搭建一个springboot项目

一,搭建之前准备 搭建之前先确保本机已正确安装JDK,Maven,IDEA。 以下是我的配置: OpenJDK 17 Maven 3.9.11 IDEA 2025 二,开始搭建1,打开Idea工具,File->New-> Project...2,左边选择“Spring Boot"…

最优/极值问题的算法选择

如何选择滑动窗口、二分、动态规划算法 刷leetcode时对于一些最优/极值问题往往不知采用哪一种算法,故借助大模型学习一些算法要点。1. 滑动窗口(Sliding Window) 特点适用于 数组 / 字符串 的 连续子区间 问题。 目…

珠海的门户网站有哪些深圳地址大全

ScottPlot是一款简单易用、高度定制、性能卓越的.NET绘图库&#xff0c;支持跨平台操作。除提供标准图表类型外&#xff0c;还支持交互式操作&#xff0c;呈现生动的数据展示。在工厂数字化系统中&#xff0c;可用于生产数据可视化、设备监测和质量控制。无论用于科学研究、数据…

网站开发和推广方案永康市建设银行网站查询

PHP程序的调试一直是一件让人头疼的事&#xff0c;它既不像VB等高级语言那样有集成的编译调试环境&#xff0c;也不想Perl那样可以在Linux或者DOS环境下直接运行。其实&#xff0c;我们完全可以通过灵活地使用echo语句来完成对PHP的调试工作。下面的几个函数可以让你随时查看程…

梁山网站建设多少钱wordpress文章末尾添加内容

前言 有时遇到这样的需求&#xff0c;就是在表格里面嵌入一个表格&#xff0c;以及要求带有分页&#xff0c;这样在ElementPlus中很好实现。以下使用Vue2语法实现一个简单例子&#xff0c;毕竟Vue3兼容Vue2语法&#xff0c;若想要Vue3版本例子&#xff0c;简单改改就OK了。 一…

外贸建站wordpress昆山网站建设jofuns

以下内容整理于Linux字符设备驱动剖析&#xff0c;如有侵权请告知删除 。 一、应用层的程序 应用程序一般都是open打开设备文件&#xff0c;read、write、ioctl设备文件&#xff0c;最后close设备文件退出。 int main(int argc ,char *argv[]) { unsigned char val[1] 1; …

第三方控件库的添加和使用

添加把第三方控件库先复制到根目录下 ,也就是Debug 的目录下然后再拖到工具箱的空白处下就可以了使用 和之前的控件使用相同 ‍

实用指南:基于 HTML、CSS 和 JavaScript 的智能图像灰度直方图匹配系统

实用指南:基于 HTML、CSS 和 JavaScript 的智能图像灰度直方图匹配系统2025-09-29 19:20 tlnshuju 阅读(0) 评论(0) 收藏 举报pre { white-space: pre !important; word-wrap: normal !important; overflow-x: au…

C4NR PVP服务器1.2 天穹炮塔更新

C4NR PVP服务器1.2 天穹炮塔更新 更新内容在风弹获得处增加了一个建筑,可以使用风弹上去。上面是一个放置压力板的一个平台。踩压力板会获得20点风弹伤害并且生成一个风弹。玩家可以通过向指定方向攻击风弹来控制风弹…

树形dp [JOI Open 2020] 发电站 / Power Plant

作为最强摸鱼人的 BaiBaiShaFeng,这个题解也是发到洛谷上了,希望给过。 先辈们说的太简略了我感觉有点难懂,虽然我的表达能力很弱,估计强不了多少。 注:参考过网上零散题解。 题意很好理解,我们就不过多叙述了。…

深入解析:灵画-AI绘画小程序

深入解析:灵画-AI绘画小程序pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco"…

AT_arc156_b [ARC156B] Mex on Blackboard

首先枚举你花 \(i\) 次操作可以搞到的最大值是什么,然后你剩下的集合就随便取,只要取不超过 \(k - i\) 个即可,用插板法很容易做。

实用指南:CAN邮箱深度解析:从硬件架构到实战应用

实用指南:CAN邮箱深度解析:从硬件架构到实战应用pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas"…

产品排序

考虑区间 $dp$。设 $f_{i,j}$ 表示处理 $[i,j]$ 最小的总惩罚值。分类: - 产品 $i$ 第一个出栈,则有 $f_{i,j}=t_i\times sd_{i,j}+f_{i+1,j}$ - 产品 $i$ 第 $k$ 个出栈,则有 $f_{i,j}=f_{i+1,k}+f_{k + 1,j}+st_{…

众包网站开发网站建设录哪个科目

先拆成链的情况来看。 设B[i]表示i要向i1拿糖果的数量&#xff0c;C为平均数&#xff0c;则B[i] C - A[i] B[i-1] Answer就是B的绝对值之和 现在来看环的情况&#xff0c;也就是说B[n]指的是n要向1拿糖果的数量。不妨设B[n]为K&#xff0c;则B[1] C - A[1] K………… 照着式…

云阳一平米网站建设打开百度网页

LVGL_基础控件label 1、创建一个基础对象 /* 创建一个基础对象 label */ lv_obj_t * label lv_label_create(lv_scr_act()); // 创建一个label部件(对象),他的父对象是活动屏幕对象2、设置显示内容 char * text "www.100ask.net"; // 要显示的文字 /* 展示文…

环形链表II-leetcode

题目描述 给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。 如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系…

ubuntu20.04安装nvidia显卡

24.04内核最新升级6.14.0,但是会出现英伟达驱动问题,现有的版本是6.8 重新安装了ubuntu20.04 先检查可用驱动:ubuntu-drivers devices下面会显示推荐安装的驱动版本,因为我原先的版本太低了,导致ubuntu内核更新一…

搬瓦工的主机可以用来做网站吗如何自己做加盟网站

文章目录 一.进程间通信:进程间通信的本质: 二.Linux管道通信匿名管道:关于管道通信的要点:基于匿名管道构建进程池: 三.System-V共享内存共享内存和命名管道协同通信 参考Linux内核源码版本------linux-2.4.3 一.进程间通信: 操作系统中,为了保证安全性,进程之间具有严格的独…

[线段树系列 #6] 标记永久化

[线段树系列 #6] 标记永久化 标记永久化是线段树的一个技巧,通常用于对主席树等难以 pushdown 的数据结构进行区间修改 具体思想已经体现在名字里了,我们结合例题稍微讲解一下 例题1 P3372 【模板】线段树 1 线段树区…