当前位置: 欣欣网 > 码农

.NET9 Linux-X64 Output dup(超级硬核)

2024-02-24码农

点击上方 蓝字 江湖评谈 设为关注




本篇是对前两篇: , 的收尾。关于内核级的WriteFile和Linux-dup会在 分享。

一:Volatile.Write

这个函数是指令级别的操控,它把第二个参数的值赋给第一个参数。返回值为void。

现代化的CPU都是多核,一 汇编,多核同时读取,加载,执行等任务。 有可能某核里的代码某个阶段会延迟,卡顿,以及bit干扰等情况,其它核正常运行。 导致了前面的代码还没加载完毕,后面的代码已经执行完成的情况。 为了不造成这种异常的现象,需要确保在读取或者写入某一条指令的时候,防止执行的操作运行到加载的前面,一切以正常的顺序执行。 Volatile.Write函数就是确保这样情况不再发生,读取或者写入的数据是完整无缺的。这就是 俗说的: 内存屏障

Console.WriteLine里面用这个函数,主要是确保Linux下Dup设置的流指向以及Windows下WriteFile设置的流指向是正确的, 不会错乱。

二:Linux dup+dup2

1.dup

讲了下Console.WriteLine在Linux下调用了dup设置了流指向为终端输出,通过这个流指向把WriteLine参数里面的字符串给它打印到屏幕上。本篇补充一些细节。

.NET9在Linux-x64上面的设置流指向是Dup函数,它在头文件#include <unistd.h>。通过一个文件写入来模拟下 这个 过程。 test.c如下:

#include<unistd.h>#include<stdlib.h>#include<fcntl.h>#include<stdio.h>intmain(int argc,char** argv){int file = open("demo.txt", O_CREAT | O_RDWR | O_TRUNC);int dupoutput = dup(file);char buf[20];ssize_t n; n = read(STDIN_FILENO, buf, sizeof(buf)); write(dupoutput, buf, n);return0;}

编译:

#gcc test.c -o test#./test8888#vim demo.txt //demo.txt会自动创建,跟test.c同一目录8888

可以看到文件demo.txt里面写入了8888,这是因为当前流指向通过dup设置指向到了demo.txt文件。所以当你运行./test的时候,会往demo.txt里面写入数值。

以上是模拟一个键盘-】文件的流指向,Console.WriteLine只需要模拟一个终端的输出即可,原理其实是一样的。C#代码正是这种做法:

//STDOUT_FILENO标准终端输出Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO)

拓展下:

# whereis unistd.hunistd.h: /usr/include/unistd.h#vim /usr/include/unistd.hvim commd search :/STD#define STDIN_FILENO 0 /* Standard input. */#define STDOUT_FILENO 1 /* Standard output. */#define STDERR_FILENO 2 /* Standard error output. */

0,1,2分别为标准格式,上面例子 dupoutput 则是4.STDIN_FILENO标准输入STDOUT_FILENO 标准输出STDERR_FILENO 标准错误

2.dup2(拓展知识)

dup有一个变体dup2,它的作用是把流指向进行重定位,把新的流赋给旧的流指向,这样新的流指向的即 旧流的指向,函数原型: int dup2(int olds,int news)。

一个正常的标准输出是1. dup测试:dup2.c:#include<stdio.h>#include<unistd.h>#include<string.h>intmain(int argc,char** argv){int out=dup(1);constchar* str="hello\r\n"; write(out,str,strlen(str)); close(out);return0;}#gcc dup2.c -o dup2#./dup2hello

现在把流指向其它值,比如下面把3指向标准输出流1,这样当往3里面写入(write)数据的时候,打印出hello.

dum2测试:#include<stdio.h>#include<unistd.h>#include<string.h>intmain(int argc,char** argv){int out=dup2(1,3);constchar* str="hello\r\n"; write(3,str,strlen(str)); close(out);return0;}#./dup3hello

三:托管开头

比如代码:Console.WriteLine("Call Main");它调用了

src\libraries\System.Console\src\System\Console.cs[MethodImplAttribute(MethodImplOptions.NoInlining)]publicstaticvoidWriteLine(stringvalue){ Out.WriteLine(value);}

value参数即是字符串:Call Main 。Out是什么呢?它是个属性,返回的是 s_out。

 src\libraries\System.Console\src\System\Console.cspublicstatic TextWriter Out {get { Debug.Assert(!Monitor.IsEntered(s_syncObject));return Volatile.Read(ref s_out) ?? EnsureInitialized();static TextWriter EnsureInitialized() {lock (s_syncObject) // Ensures Out and OutputEncoding are synchronized. {if (s_out == null) { Volatile.Write(ref s_out, CreateOutputWriter(ConsolePal.OpenStandardOutput())); }return s_out; } }}

s_out实际上就是Linux下面Dup设置的流指向终端输出,这里微软的Console.Wr iteLine用的是STDOUT_FILENO标准输出

Interop.Sys.Dup(Interop.Sys.FileDescriptors.STDOUT_FILENO)

关于这点在上一篇 里面有讲到过。Dup原型如下:

internalstaticpartial classInterop{internalstaticpartial classSys { [LibraryImport(Libraries.SystemNative, EntryPoint = "SystemNative_Dup", SetLastError = true)]internalstaticpartial SafeFileHandle Dup(SafeFileHandle oldfd); }}

整体的就是Dup设置流指向终端,然后通过Console.WriteLine传递的字符串,把这字符串打印到屏幕上。

更多的顶级技术学习和了解可以加入

往期精彩