点击上方 蓝字 江湖评谈 设为关注
本篇是对前两篇: , 的收尾。关于内核级的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
#./test
8888
#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.h
unistd.h: /usr/include/unistd.h
#vim /usr/include/unistd.h
vim 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
#./dup2
hello
现在把流指向其它值,比如下面把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;
}
#./dup3
hello
三:托管开头
比如代码:Console.WriteLine("Call Main");它调用了
src\libraries\System.Console\src\System\Console.cs
[MethodImplAttribute(MethodImplOptions.NoInlining)]
publicstaticvoidWriteLine(string? value)
{
Out.WriteLine(value);
}
value参数即是字符串:Call Main 。Out是什么呢?它是个属性,返回的是 s_out。
src\libraries\System.Console\src\System\Console.cs
publicstatic 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传递的字符串,把这字符串打印到屏幕上。
更多的顶级技术学习和了解可以加入
往期精彩