點選上方 藍字 江湖評談 設為關註
本篇是對前兩篇: , 的收尾。關於內核級的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傳遞的字串,把這字串打印到螢幕上。
更多的頂級技術學習和了解可以加入
往期精彩