NTMinerRoot.partials.MinerProcess.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. using NTMiner.Core;
  2. using NTMiner.Core.Kernels;
  3. using NTMiner.Core.Profiles;
  4. using System;
  5. using System.Collections;
  6. using System.Diagnostics;
  7. using System.IO;
  8. using System.Runtime.InteropServices;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. namespace NTMiner {
  13. // ReSharper disable once InconsistentNaming
  14. public partial class NTMinerRoot : INTMinerRoot {
  15. private static class MinerProcess {
  16. #region CreateProcess
  17. private static readonly object _locker = new object();
  18. public static void CreateProcessAsync(IMineContext mineContext) {
  19. Task.Factory.StartNew(() => {
  20. lock (_locker) {
  21. try {
  22. Write.UserInfo("清理内核进程");
  23. #if DEBUG
  24. Write.Stopwatch.Restart();
  25. #endif
  26. // 清理除当前外的Temp/Kernel
  27. Cleaner.Clear();
  28. #if DEBUG
  29. Write.DevWarn($"耗时{Write.Stopwatch.ElapsedMilliseconds}毫秒 {nameof(MinerProcess)}.{nameof(CreateProcessAsync)}[{nameof(Cleaner)}.{nameof(Cleaner.Clear)}]");
  30. #endif
  31. Write.UserOk("内核进程清理完毕");
  32. // 应用超频
  33. if (GpuProfileSet.Instance.IsOverClockEnabled(mineContext.MainCoin.GetId())) {
  34. Write.UserInfo("应用超频,如果CPU性能较差耗时可能超过1分钟,请耐心等待");
  35. VirtualRoot.Execute(new CoinOverClockCommand(mineContext.MainCoin.GetId(), isJoin: true));
  36. }
  37. Thread.Sleep(1000);
  38. Write.UserInfo($"解压内核包{mineContext.Kernel.Package}");
  39. // 解压内核包
  40. if (!mineContext.Kernel.ExtractPackage()) {
  41. VirtualRoot.Happened(new StartingMineFailedEvent("内核解压失败,请卸载内核重试。"));
  42. }
  43. else {
  44. Write.UserOk("内核包解压成功");
  45. }
  46. // 执行文件书写器
  47. mineContext.ExecuteFileWriters();
  48. Write.UserInfo("总成命令");
  49. // 组装命令
  50. BuildCmdLine(mineContext, out string kernelExeFileFullName, out string arguments);
  51. bool isLogFile = arguments.Contains("{logfile}");
  52. // 这是不应该发生的,如果发生很可能是填写命令的时候拼写错误了
  53. if (!File.Exists(kernelExeFileFullName)) {
  54. Write.UserError(kernelExeFileFullName + "文件不存在,可能是小编拼写错误或是挖矿内核被杀毒软件删除导致,请退出杀毒软件重试或者QQ群联系小编。");
  55. }
  56. if (isLogFile) {
  57. Logger.InfoDebugLine("创建日志文件型进程");
  58. // 如果内核支持日志文件
  59. // 推迟打印cmdLine,因为{logfile}变量尚未求值
  60. CreateLogfileProcess(mineContext, kernelExeFileFullName, arguments);
  61. }
  62. else {
  63. Logger.InfoDebugLine("创建管道型进程");
  64. // 如果内核不支持日志文件
  65. CreatePipProcess(mineContext, kernelExeFileFullName, arguments);
  66. }
  67. VirtualRoot.Happened(new MineStartedEvent(mineContext));
  68. }
  69. catch (Exception e) {
  70. Logger.ErrorDebugLine(e);
  71. Write.UserFail("挖矿内核启动失败,请联系开发人员解决");
  72. }
  73. }
  74. });
  75. }
  76. #endregion
  77. #region BuildCmdLine
  78. private static void BuildCmdLine(IMineContext mineContext, out string kernelExeFileFullName, out string arguments) {
  79. var kernel = mineContext.Kernel;
  80. if (string.IsNullOrEmpty(kernel.Package)) {
  81. throw new InvalidDataException();
  82. }
  83. string kernelDir = Path.Combine(SpecialPath.KernelsDirFullName, Path.GetFileNameWithoutExtension(kernel.Package));
  84. string kernelCommandName = kernel.GetCommandName();
  85. kernelExeFileFullName = Path.Combine(kernelDir, kernelCommandName);
  86. if (!kernelExeFileFullName.EndsWith(".exe")) {
  87. kernelExeFileFullName += ".exe";
  88. }
  89. var args = mineContext.CommandLine;
  90. arguments = args.Substring(kernelCommandName.Length).Trim();
  91. }
  92. #endregion
  93. #region Daemon
  94. private static Bus.DelegateHandler<Per1MinuteEvent> _sDaemon = null;
  95. private static void Daemon(IMineContext mineContext, Action clear) {
  96. if (_sDaemon != null) {
  97. VirtualRoot.UnPath(_sDaemon);
  98. _sDaemon = null;
  99. clear?.Invoke();
  100. }
  101. string processName = mineContext.Kernel.GetProcessName();
  102. _sDaemon = VirtualRoot.On<Per1MinuteEvent>("周期性检查挖矿内核是否消失,如果消失尝试重启", LogEnum.DevConsole,
  103. action: message => {
  104. if (mineContext == Instance.CurrentMineContext) {
  105. if (!string.IsNullOrEmpty(processName)) {
  106. Process[] processes = Process.GetProcessesByName(processName);
  107. if (processes.Length == 0) {
  108. mineContext.AutoRestartKernelCount = mineContext.AutoRestartKernelCount + 1;
  109. Logger.ErrorWriteLine(processName + $"挖矿内核进程消失");
  110. if (Instance.MinerProfile.IsAutoRestartKernel && mineContext.AutoRestartKernelCount <= Instance.MinerProfile.AutoRestartKernelTimes) {
  111. Logger.WarnWriteLine($"尝试第{mineContext.AutoRestartKernelCount}次重启,共{Instance.MinerProfile.AutoRestartKernelTimes}次");
  112. Instance.RestartMine();
  113. Instance.CurrentMineContext.AutoRestartKernelCount = mineContext.AutoRestartKernelCount;
  114. }
  115. else {
  116. Instance.StopMineAsync();
  117. }
  118. if (_sDaemon != null) {
  119. VirtualRoot.UnPath(_sDaemon);
  120. clear?.Invoke();
  121. }
  122. }
  123. }
  124. }
  125. else {
  126. if (_sDaemon != null) {
  127. VirtualRoot.UnPath(_sDaemon);
  128. clear?.Invoke();
  129. }
  130. }
  131. });
  132. }
  133. #endregion
  134. #region CreateLogfileProcess
  135. private static void CreateLogfileProcess(IMineContext mineContext, string kernelExeFileFullName, string arguments) {
  136. string logFile = Path.Combine(SpecialPath.LogsDirFullName, $"{mineContext.Kernel.Code}_{DateTime.Now.ToString("yyyy-MM-dd_HH-mm-ss_fff")}.log");
  137. arguments = arguments.Replace("{logfile}", logFile);
  138. Write.UserOk($"\"{kernelExeFileFullName}\" {arguments}");
  139. Write.UserInfo("有请内核上场");
  140. ProcessStartInfo startInfo = new ProcessStartInfo(kernelExeFileFullName, arguments) {
  141. UseShellExecute = false,
  142. CreateNoWindow = true,
  143. WorkingDirectory = AssemblyInfo.LocalDirFullName
  144. };
  145. // 追加环境变量
  146. foreach (var item in mineContext.CoinKernel.EnvironmentVariables) {
  147. startInfo.EnvironmentVariables.Add(item.Key, item.Value);
  148. }
  149. Process process = new Process {
  150. StartInfo = startInfo
  151. };
  152. process.Start();
  153. ReadPrintLoopLogFileAsync(mineContext, logFile);
  154. Daemon(mineContext, null);
  155. }
  156. #endregion
  157. #region ReadPrintLoopLogFile
  158. private static void ReadPrintLoopLogFileAsync(IMineContext mineContext, string logFile) {
  159. Task.Factory.StartNew(() => {
  160. bool isLogFileCreated = true;
  161. int n = 0;
  162. while (!File.Exists(logFile)) {
  163. if (n >= 20) {
  164. // 20秒钟都没有建立日志文件,不可能
  165. isLogFileCreated = false;
  166. Write.UserFail("呃!意外,竟然20秒钟未产生内核输出文件,请联系开发人员解决。");
  167. break;
  168. }
  169. Thread.Sleep(1000);
  170. if (n == 0) {
  171. Write.UserInfo("等待内核出场");
  172. }
  173. if (mineContext != Instance.CurrentMineContext) {
  174. Write.UserInfo("挖矿上下文变更,结束内核输出等待。");
  175. isLogFileCreated = false;
  176. break;
  177. }
  178. n++;
  179. }
  180. if (isLogFileCreated) {
  181. Write.UserOk("内核已上场,下面把舞台交给内核。");
  182. StreamReader sreader = null;
  183. try {
  184. sreader = new StreamReader(File.Open(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), Encoding.Default);
  185. while (mineContext == Instance.CurrentMineContext) {
  186. string outline = sreader.ReadLine();
  187. if (string.IsNullOrEmpty(outline) && sreader.EndOfStream) {
  188. Thread.Sleep(1000);
  189. }
  190. else {
  191. string input = outline;
  192. Guid kernelOutputId = mineContext.Kernel.KernelOutputId;
  193. Instance.KernelOutputFilterSet.Filter(kernelOutputId, ref input);
  194. ConsoleColor color = ConsoleColor.White;
  195. Instance.KernelOutputTranslaterSet.Translate(kernelOutputId, ref input, ref color, isPre: true);
  196. // 使用Claymore挖其非ETH币种时它也打印ETH,所以这里需要纠正它
  197. if ("Claymore".Equals(mineContext.Kernel.Code, StringComparison.OrdinalIgnoreCase)) {
  198. if (mineContext.MainCoin.Code != "ETH" && input.Contains("ETH")) {
  199. input = input.Replace("ETH", mineContext.MainCoin.Code);
  200. }
  201. }
  202. Instance.KernelOutputSet.Pick(kernelOutputId, ref input, mineContext);
  203. if (IsUiVisible) {
  204. Instance.KernelOutputTranslaterSet.Translate(kernelOutputId, ref input, ref color);
  205. }
  206. if (!string.IsNullOrEmpty(input)) {
  207. if (Instance.KernelOutputSet.TryGetKernelOutput(kernelOutputId, out IKernelOutput kernelOutput)) {
  208. if (kernelOutput.PrependDateTime) {
  209. Write.UserLine($"{DateTime.Now} {input}", color);
  210. }
  211. else {
  212. Write.UserLine(input, color);
  213. }
  214. }
  215. else {
  216. Write.UserLine(input, color);
  217. }
  218. }
  219. }
  220. }
  221. }
  222. catch (Exception e) {
  223. Logger.ErrorDebugLine(e);
  224. }
  225. finally {
  226. sreader?.Close();
  227. sreader?.Dispose();
  228. }
  229. Write.UserInfo("内核表演结束");
  230. }
  231. }, TaskCreationOptions.LongRunning);
  232. }
  233. #endregion
  234. #region CreatePipProcess
  235. // 创建管道,将输出通过管道转送到日志文件,然后读取日志文件内容打印到控制台
  236. private static void CreatePipProcess(IMineContext mineContext, string kernelExeFileFullName, string arguments) {
  237. SECURITY_ATTRIBUTES saAttr = new SECURITY_ATTRIBUTES {
  238. bInheritHandle = true,
  239. lpSecurityDescriptor = IntPtr.Zero,
  240. length = Marshal.SizeOf(typeof(SECURITY_ATTRIBUTES))
  241. };
  242. //set the bInheritHandle flag so pipe handles are inherited
  243. saAttr.lpSecurityDescriptor = IntPtr.Zero;
  244. //get handle to current stdOut
  245. IntPtr mypointer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(STARTUPINFO)));
  246. Marshal.StructureToPtr(saAttr, mypointer, true);
  247. var bret = CreatePipe(out var hReadOut, out var hWriteOut, mypointer, 0);
  248. //ensure the read handle to pipe for stdout is not inherited
  249. SetHandleInformation(hReadOut, HANDLE_FLAG_INHERIT, 0);
  250. ////Create pipe for the child process's STDIN
  251. STARTUPINFO lpStartupInfo = new STARTUPINFO {
  252. cb = (uint)Marshal.SizeOf(typeof(STARTUPINFO)),
  253. dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW,
  254. wShowWindow = SW_HIDE, // SW_HIDE; //SW_SHOW
  255. hStdOutput = hWriteOut,
  256. hStdError = hWriteOut,
  257. hStdInput = IntPtr.Zero
  258. };
  259. string cmdLine = $"\"{kernelExeFileFullName}\" {arguments}";
  260. Write.UserOk(cmdLine);
  261. Write.UserInfo("有请内核上场");
  262. StringBuilder lpEnvironment = new StringBuilder();
  263. // 复制父进程的环境变量
  264. IDictionary dic = Environment.GetEnvironmentVariables();
  265. // 追加环境变量
  266. foreach (var item in mineContext.CoinKernel.EnvironmentVariables) {
  267. dic.Add(item.Key, item.Value);
  268. }
  269. foreach (var key in dic.Keys) {
  270. if (key == null || key.ToString().Contains("\0")) {
  271. continue;
  272. }
  273. var value = dic[key];
  274. if (value == null || value.ToString().Contains("\0")) {
  275. continue;
  276. }
  277. lpEnvironment.Append($"{key.ToString()}={value.ToString()}\0");
  278. }
  279. if (CreateProcess(
  280. lpApplicationName: null,
  281. lpCommandLine: new StringBuilder(cmdLine),
  282. lpProcessAttributes: IntPtr.Zero,
  283. lpThreadAttributes: IntPtr.Zero,
  284. bInheritHandles: true,
  285. dwCreationFlags: NORMAL_PRIORITY_CLASS,
  286. lpEnvironment: lpEnvironment,
  287. lpCurrentDirectory: Path.GetDirectoryName(kernelExeFileFullName),
  288. lpStartupInfo: ref lpStartupInfo,
  289. lpProcessInformation: out _)) {
  290. if (bret == false) {
  291. int lasterr = Marshal.GetLastWin32Error();
  292. VirtualRoot.Happened(new StartingMineFailedEvent($"管道型进程创建失败 lasterr:{lasterr}"));
  293. }
  294. else {
  295. Bus.DelegateHandler<MineStopedEvent> closeHandle = null;
  296. bool isHWriteOutHasClosed = false;
  297. Daemon(mineContext, () => {
  298. if (!isHWriteOutHasClosed) {
  299. CloseHandle(hWriteOut);
  300. isHWriteOutHasClosed = true;
  301. }
  302. VirtualRoot.UnPath(closeHandle);
  303. });
  304. closeHandle = VirtualRoot.On<MineStopedEvent>("挖矿停止后关闭非托管的日志句柄", LogEnum.DevConsole,
  305. action: message => {
  306. // 挖矿停止后摘除挖矿内核进程守护器
  307. if (_sDaemon != null) {
  308. VirtualRoot.UnPath(_sDaemon);
  309. _sDaemon = null;
  310. }
  311. if (!isHWriteOutHasClosed) {
  312. CloseHandle(hWriteOut);
  313. isHWriteOutHasClosed = true;
  314. }
  315. VirtualRoot.UnPath(closeHandle);
  316. });
  317. string pipLogFileFullName = Path.Combine(SpecialPath.LogsDirFullName, mineContext.PipeFileName);
  318. Task.Factory.StartNew(() => {
  319. FileStream fs = new FileStream(pipLogFileFullName, FileMode.OpenOrCreate, FileAccess.ReadWrite);
  320. using (StreamReader sr = new StreamReader(fs)) {
  321. byte[] buffer = new byte[1024];
  322. int ret;
  323. // Read会阻塞,直到读取到字符或者hWriteOut被关闭
  324. while ((ret = Read(buffer, 0, buffer.Length, hReadOut)) > 0) {
  325. fs.Write(buffer, 0, ret);
  326. if (buffer[ret - 1] == '\r' || buffer[ret - 1] == '\n') {
  327. fs.Flush();
  328. }
  329. }
  330. }
  331. CloseHandle(hReadOut);
  332. }, TaskCreationOptions.LongRunning);
  333. ReadPrintLoopLogFileAsync(mineContext, pipLogFileFullName);
  334. }
  335. }
  336. else {
  337. VirtualRoot.Happened(new StartingMineFailedEvent($"内核启动失败,请重试"));
  338. }
  339. }
  340. private struct STARTUPINFO {
  341. public uint cb;
  342. public string lpReserved;
  343. public string lpDesktop;
  344. public string lpTitle;
  345. public uint dwX;
  346. public uint dwY;
  347. public uint dwXSize;
  348. public uint dwYSize;
  349. public uint dwXCountChars;
  350. public uint dwYCountChars;
  351. public uint dwFillAttribute;
  352. public uint dwFlags;
  353. public short wShowWindow;
  354. public short cbReserved2;
  355. public IntPtr lpReserved2;
  356. public IntPtr hStdInput;
  357. public IntPtr hStdOutput;
  358. public IntPtr hStdError;
  359. }
  360. private struct PROCESS_INFORMATION {
  361. public IntPtr hProcess;
  362. public IntPtr hThread;
  363. public uint dwProcessId;
  364. public uint dwThreadId;
  365. }
  366. private struct SECURITY_ATTRIBUTES {
  367. public int length;
  368. public IntPtr lpSecurityDescriptor;
  369. public bool bInheritHandle;
  370. }
  371. [DllImport("kernel32.dll")]
  372. private static extern int CloseHandle(IntPtr hObject);
  373. [DllImport("kernel32.dll")]
  374. private static extern bool CreatePipe(out IntPtr phReadPipe, out IntPtr phWritePipe, IntPtr lpPipeAttributes, uint nSize);
  375. [DllImport("kernel32.dll", SetLastError = true)]
  376. private static extern unsafe bool ReadFile(
  377. IntPtr hfile,
  378. void* pBuffer,
  379. int NumberOfBytesToRead,
  380. int* pNumberOfBytesRead,
  381. NativeOverlapped* lpOverlapped
  382. );
  383. /// <summary>
  384. ///
  385. /// </summary>
  386. /// <param name="lpEnvironment">
  387. /// A pointer to the environment block for the new process. If this parameter is NULL, the new process uses the environment of the calling process.
  388. /// An environment block consists of a null-terminated block of null-terminated strings. Each string is in the following form:
  389. /// name=value\0
  390. /// Because the equal sign is used as a separator, it must not be used in the name of an environment variable.
  391. /// An environment block can contain either Unicode or ANSI characters. If the environment block pointed to by lpEnvironment contains Unicode characters, be sure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT. If this parameter is NULL and the environment block of the parent process contains Unicode characters, you must also ensure that dwCreationFlags includes CREATE_UNICODE_ENVIRONMENT.
  392. /// The ANSI version of this function, CreateProcessA fails if the total size of the environment block for the process exceeds 32,767 characters.
  393. /// Note that an ANSI environment block is terminated by two zero bytes: one for the last string, one more to terminate the block. A Unicode environment block is terminated by four zero bytes: two for the last string, two more to terminate the block.
  394. /// A parent process can directly alter the environment variables of a child process during process creation. This is the only situation when a process can directly change the environment settings of another process. For more information, see Changing Environment Variables.
  395. ///
  396. /// If an application provides an environment block, the current directory information of the system drives is not automatically propagated to the new process.
  397. /// For example, there is an environment variable named = C: whose value is the current directory on drive C.An application must manually pass the current directory
  398. /// information to the new process.To do so, the application must explicitly create these environment variable strings, sort them alphabetically(because the system
  399. /// uses a sorted environment), and put them into the environment block.Typically, they will go at the front of the environment block, due to the environment block sort order
  400. /// </param>
  401. /// <returns></returns>
  402. [DllImport("kernel32.dll")]
  403. private static extern bool CreateProcess(
  404. string lpApplicationName,
  405. StringBuilder lpCommandLine,
  406. IntPtr lpProcessAttributes,
  407. IntPtr lpThreadAttributes,
  408. bool bInheritHandles,
  409. uint dwCreationFlags,
  410. StringBuilder lpEnvironment,
  411. string lpCurrentDirectory,
  412. ref STARTUPINFO lpStartupInfo,
  413. out PROCESS_INFORMATION lpProcessInformation);
  414. [DllImport("kernel32.dll")]
  415. private static extern bool SetHandleInformation(IntPtr hObject, int dwMask, uint dwFlags);
  416. private static unsafe int Read(byte[] buffer, int index, int count, IntPtr hStdOut) {
  417. int n = 0;
  418. fixed (byte* p = buffer) {
  419. if (!ReadFile(hStdOut, p + index, count, &n, (NativeOverlapped*)0))
  420. return 0;
  421. }
  422. return n;
  423. }
  424. private const uint STARTF_USESHOWWINDOW = 0x00000001;
  425. private const uint STARTF_USESTDHANDLES = 0x00000100;
  426. private const uint STARTF_FORCEONFEEDBACK = 0x00000040;
  427. private const uint SF_USEPOSITION = 0x00000004;
  428. private const uint STARTF_USESIZE = 0x00000002;
  429. private const uint STARTF_USECOUNTCHARS = 0x00000008;
  430. private const uint NORMAL_PRIORITY_CLASS = 0x00000020;
  431. private const uint CREATE_BREAKAWAY_FROM_JOB = 0x01000000;
  432. private const uint CREATE_NO_WINDOW = 0x08000000;
  433. private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
  434. private const short SW_SHOW = 5;
  435. private const short SW_HIDE = 0;
  436. private const int STD_OUTPUT_HANDLE = -11;
  437. private const int HANDLE_FLAG_INHERIT = 1;
  438. private const uint GENERIC_READ = 0x80000000;
  439. private const uint FILE_ATTRIBUTE_READONLY = 0x00000001;
  440. private const uint FILE_ATTRIBUTE_NORMAL = 0x00000080;
  441. private const int OPEN_EXISTING = 3;
  442. private const uint CREATE_NEW_CONSOLE = 0x00000010;
  443. private const uint STILL_ACTIVE = 0x00000103;
  444. #endregion
  445. }
  446. }
  447. }