请选择 进入手机版 | 继续访问电脑版
开启辅助访问
QQ登录|微信登录|登录 |立即注册

盖茨网区块链技术社区

xmrig_GPU门罗币挖矿代码详解

xmrig_GPU挖矿代码详解

github上有很多挖矿的代码,今天我们详解一个门罗币挖矿代码xmrig,前面我们也提及了该项目在windows的编译方法,今天我们就从代码的角度来分析该项目。
代码下载方法:
  1. https://github.com/xmrig/xmrig-amd
复制代码

windows编译方法:
  1. http://www.gaici.com/forum.php?mod=viewthread&tid=184&extra=page%3D1
复制代码

代码分析
  1. int main(int argc, char **argv) {
  2.     App app(argc, argv);
  3.     return app.exec();
  4. }
复制代码
主函数比较简单,构造APP类,在构造方法中
  1. App::App(int argc, char **argv) :
  2.     m_console(nullptr),
  3.     m_httpd(nullptr),
  4.     m_network(nullptr),
  5.     m_options(nullptr)
  6. {
  7.     m_self = this;

  8.     Cpu::init();
  9.     m_options = Options::parse(argc, argv);
  10.     if (!m_options) {
  11.         return;
  12.     }

  13.     Log::init();

  14.     if (!m_options->background()) {
  15.         Log::add(new ConsoleLog(m_options->colors()));
  16.         m_console = new Console(this);
  17.     }

  18.     if (m_options->logFile()) {
  19.         Log::add(new FileLog(m_options->logFile()));
  20.     }

  21. #   ifdef HAVE_SYSLOG_H
  22.     if (m_options->syslog()) {
  23.         Log::add(new SysLog());
  24.     }
  25. #   endif

  26.     Platform::init(m_options->userAgent());

  27.     m_network = new Network(m_options);

  28.     uv_signal_init(uv_default_loop(), &m_sigHUP);
  29.     uv_signal_init(uv_default_loop(), &m_sigINT);
  30.     uv_signal_init(uv_default_loop(), &m_sigTERM);
  31. }
复制代码
做了一些列初始化工作,和参数解析,信号初始化等。

此后执行
  1. app.exec()
复制代码
进入主程序

  1. int App::exec()
  2. {
  3.     if (!m_options) {
  4.         return 2;
  5.     }

  6.     uv_signal_start(&m_sigHUP,  App::onSignal, SIGHUP);
  7.     uv_signal_start(&m_sigINT,  App::onSignal, SIGINT);
  8.     uv_signal_start(&m_sigTERM, App::onSignal, SIGTERM);

  9.     background();

  10.     if (!CryptoNight::init(m_options->algo(), m_options->algoVariant())) {
  11.         LOG_ERR(""%s" hash self-test failed.", m_options->algoName());
  12.         return 1;
  13.     }
  14.     Summary::print();
  15.     m_options->oclInit();
  16. #   ifndef XMRIG_NO_API
  17.     Api::start();
  18. #   endif

  19. #   ifndef XMRIG_NO_HTTPD
  20.     m_httpd = new Httpd(m_options->apiPort(), m_options->apiToken());
  21.     m_httpd->start();
  22. #   endif

  23.     if (!Workers::start(m_options->threads())) {
  24.         LOG_ERR("Failed to start threads");
  25.         return 1;
  26.     }

  27.     m_network->connect();
  28.         LOG_INFO("RUN UV");
  29.     const int r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
  30.     uv_loop_close(uv_default_loop());

  31.     delete m_network;

  32.     Options::release();
  33.     Platform::release();

  34.     return r;
  35. }
复制代码
在主程序中首先注册了几个信号的处理方法,background()初始化了控制台窗口的相关设置。
CryptoNight::init(m_options->algo(), m_options->algoVariant())设置了hash函数的处理方法,从cryptonight_variations【】函数数组中选择了一个hash函数的处理方法。
Summary::print();打印出了系统环境中相关信息。 m_options->oclInit();对GPU的相关信息进行了检索和检查。


  1. #   ifndef XMRIG_NO_API
  2.     Api::start();
  3. #   endif

  4. #   ifndef XMRIG_NO_HTTPD
  5.     m_httpd = new Httpd(m_options->apiPort(), m_options->apiToken());
  6.     m_httpd->start();
  7. #   endif
复制代码
宏开关,设置了相应宏后对HTTP服务进行了初始化。

  1. if (!Workers::start(m_options->threads())) {
  2.         LOG_ERR("Failed to start threads");
  3.         return 1;
  4.     }

  5.     m_network->connect();
  6.         LOG_INFO("RUN UV");
  7.     const int r = uv_run(uv_default_loop(), UV_RUN_DEFAULT);
  8.     uv_loop_close(uv_default_loop());

  9.     delete m_network;

  10.     Options::release();
  11.     Platform::release();
复制代码
之后Workers::start(m_options->threads())开始了矿工任务
m_network->connect()用TCP连接矿池,最后调用uv_run, uv_loop_close启动了libuv服务。
libuv的相关操作请看:
  1. http://www.gaici.com/forum.php?mod=viewthread&tid=187&extra=page%3D1
复制代码


我们先从connect开始分析。
  1. void Network::connect()
  2. {
  3.         m_strategy->connect();
  4. }
复制代码
connect调用了一个多态的connect(),根据配置文件中矿池的数量构造了不同类。
最终调用CLinet的connect();
  1. void Client::connect()
  2. {
  3.         resolve(m_url.host());
  4. }
复制代码

  1. int Client::resolve(const char *host)
  2. {
  3.         setState(HostLookupState);

  4.         m_expire = 0;
  5.         m_recvBufPos = 0;

  6.         if (m_failures == -1) {
  7.                 m_failures = 0;
  8.         }

  9.         const int r = uv_getaddrinfo(uv_default_loop(), &m_resolver, Client::onResolved, host, nullptr, &m_hints);
  10.         if (r) {
  11.                 if (!m_quiet) {
  12.                         LOG_ERR("[%s:%u] getaddrinfo error: "%s"", host, m_url.port(), uv_strerror(r));
  13.                 }
  14.                 return 1;
  15.         }

  16.         return 0;
  17. }
复制代码
resolve中对输入url进行了域名解析,回掉函数为Client:nResolved

  1. void Client::onResolved(uv_getaddrinfo_t *req, int status, struct addrinfo *res)
  2. {
  3.         auto client = getClient(req->data);
  4.         if (!client) {
  5.                 return;
  6.         }

  7.         if (status < 0) {
  8.                 if (!client->m_quiet) {
  9.                         LOG_ERR("[%s:%u] DNS error: "%s"", client->m_url.host(), client->m_url.port(), uv_strerror(status));
  10.                 }

  11.                 return client->reconnect();
  12.         }

  13.         addrinfo *ptr = res;
  14.         std::vector<addrinfo*> ipv4;
  15.         std::vector<addrinfo*> ipv6;

  16.         while (ptr != nullptr) {
  17.                 if (ptr->ai_family == AF_INET) {
  18.                         ipv4.push_back(ptr);
  19.                 }

  20.                 if (ptr->ai_family == AF_INET6) {
  21.                         ipv6.push_back(ptr);
  22.                 }

  23.                 ptr = ptr->ai_next;
  24.         }

  25.         if (ipv4.empty() && ipv6.empty()) {
  26.                 if (!client->m_quiet) {
  27.                         LOG_ERR("[%s:%u] DNS error: "No IPv4 (A) or IPv6 (AAAA) records found"", client->m_url.host(), client->m_url.port());
  28.                 }

  29.                 uv_freeaddrinfo(res);
  30.                 return client->reconnect();
  31.         }

  32.         client->connect(ipv4, ipv6);
  33.         uv_freeaddrinfo(res);
  34. }
复制代码
首先getClient得到发送消息的Clinet,根据返回状态判断DNS解析是否成功,然后依次解析所有IP地址,分类将IPV4,IPV6存入容器,最后调用connect。
往里追踪,找到
  1. void Client::connect(sockaddr *addr)
  2. {
  3.         setState(ConnectingState);

  4.         reinterpret_cast<struct sockaddr_in*>(addr)->sin_port = htons(m_url.port());
  5.         delete m_socket;

  6.         uv_connect_t *req = new uv_connect_t;
  7.         req->data = this;

  8.         m_socket = new uv_tcp_t;
  9.         m_socket->data = this;

  10.         uv_tcp_init(uv_default_loop(), m_socket);
  11.         uv_tcp_nodelay(m_socket, 1);

  12. #   ifndef WIN32
  13.         uv_tcp_keepalive(m_socket, 1, 60);
  14. #   endif

  15.         uv_tcp_connect(req, m_socket, reinterpret_cast<const sockaddr*>(addr), Client::onConnect);
  16. }
复制代码
该函数完成TCP连接创建,连接成功回调 Client:nConnect
  1. void Client::onConnect(uv_connect_t *req, int status)
  2. {
  3.         auto client = getClient(req->data);
  4.         if (!client) {
  5.                 return;
  6.         }

  7.         if (status < 0) {
  8.                 if (!client->m_quiet) {
  9.                         LOG_ERR("[%s:%u] connect error: "%s"", client->m_url.host(), client->m_url.port(), uv_strerror(status));
  10.                 }

  11.                 delete req;
  12.                 client->close();
  13.                 return;
  14.         }

  15.         client->m_stream = static_cast<uv_stream_t*>(req->handle);
  16.         client->m_stream->data = req->data;
  17.         client->setState(ConnectedState);

  18.         uv_read_start(client->m_stream, Client::onAllocBuffer, Client::onRead);
  19.         delete req;

  20.         if (client->m_stratumv1) {
  21.                 client->subscribe();
  22.         }
  23.         else {
  24.                 client->login();
  25.         }
  26. }
复制代码
设置了tcp读取回调,和登陆矿池操作,成功登陆矿池后矿池会定期以json格式发布任务给矿机,矿机收到后调用 Client:nRead将任务信息存入一个job类中。
至此connect所有工作做完,然后看矿机如何工作的。


  1. bool Workers::start(const std::vector<OclThread*> &threads)
  2. {
  3.     const size_t count = threads.size();
  4.     m_hashrate = new Hashrate((int) count);

  5.     if (count == 0) {
  6.         return false;
  7.     }

  8.     uv_mutex_init(&m_mutex);
  9.     uv_rwlock_init(&m_rwlock);

  10.     m_sequence = 1;
  11.     m_paused   = 1;

  12.     uv_async_init(uv_default_loop(), &m_async, Workers::onResult);
  13.     contexts.resize(count);

  14.     for (size_t i = 0; i < count; ++i) {
  15.         const OclThread *thread = threads[i];
  16.         contexts[i] = GpuContext(thread->index(), thread->intensity(), thread->worksize());
  17.     }

  18.     if (InitOpenCL(contexts.data(), count, Options::i()->platformIndex()) != OCL_ERR_SUCCESS) {
  19.         return false;
  20.     }

  21.     for (size_t i = 0; i < count; ++i) {
  22.         Handle *handle = new Handle((int) i, threads[i], &contexts[i], (int) count, Options::i()->algo() == xmrig::ALGO_CRYPTONIGHT_LITE);
  23.         m_workers.push_back(handle);
  24.         handle->start(Workers::onReady);
  25.     }

  26.     uv_timer_init(uv_default_loop(), &m_timer);
  27.     uv_timer_start(&m_timer, Workers::onTick, 500, 500);

  28.     const int printTime = Options::i()->printTime();
  29.     if (printTime > 0) {
  30.         uv_timer_init(uv_default_loop(), &m_reportTimer);
  31.         uv_timer_start(&m_reportTimer, Workers::onReport, (printTime + 4) * 1000, printTime * 1000);
  32.     }

  33.     Options::i()->save();
  34.         LOG_INFO("start OK");
  35.     return true;
  36. }
复制代码
该函数传入一个整数,是你要开启多少条线程进行挖矿。
m_hashrate = new Hashrate((int) count);创建一个类,该类主要用于计算HASH算力。


  1. uv_async_init(uv_default_loop(), &m_async, Workers::onResult);
复制代码
设置了一个libuv处理方法,当执行uv_async_send后则会执行 Workers:nResult回调函数。


  1. for (size_t i = 0; i < count; ++i) {
  2. const OclThread *thread = threads[i];
  3. contexts[i] = GpuContext(thread->index(), thread->intensity(), thread->worksize());
  4. }

  5. if (InitOpenCL(contexts.data(), count, Options::i()->platformIndex()) != OCL_ERR_SUCCESS) {
  6. return false;
  7. }
复制代码
之后对GPU进行初始化。

  1. for (size_t i = 0; i < count; ++i) {
  2.         Handle *handle = new Handle((int) i, threads[i], &contexts[i], (int) count, Options::i()->algo() == xmrig::ALGO_CRYPTONIGHT_LITE);
  3.         m_workers.push_back(handle);
  4.         handle->start(Workers::onReady);
  5.     }
复制代码
在这里创建了count个挖矿线程。


  1. uv_timer_init(uv_default_loop(), &m_timer);
  2.     uv_timer_start(&m_timer, Workers::onTick, 500, 500);
复制代码
创建了2个定时器线程,第一个每500ms计算一次hash算力。
第二个每(printTime + 4) 秒输出hash算力至屏幕。


Workers:nReady是挖矿线程的处理方法。
  1. void Workers::onReady(void *arg)
  2. {
  3.     auto handle = static_cast<Handle*>(arg);
  4.     handle->setWorker(new OclWorker(handle));

  5.     handle->worker()->start();
  6. }
复制代码
程序中以handle构造Worker类,最后执行start()

  1. void OclWorker::start()
  2. {
  3.     cl_uint results[0x100];

  4.     while (Workers::sequence() > 0) {
  5.         if (Workers::isPaused()) {
  6.             do {
  7.                 std::this_thread::sleep_for(std::chrono::milliseconds(200));
  8.             }
  9.             while (Workers::isPaused());

  10.             if (Workers::sequence() == 0) {
  11.                 break;
  12.             }

  13.             consumeJob();
  14.         }
  15.         while (!Workers::isOutdated(m_sequence)) {
  16.             memset(results, 0, sizeof(cl_uint) * (0x100));

  17.             XMRRunJob(m_ctx, results);

  18.             for (size_t i = 0; i < results[0xFF]; i++) {
  19.                 *m_job.nonce() = results[i];
  20.                 Workers::submit(m_job);
  21.             }
  22.             m_count += m_ctx->rawIntensity;

  23.             storeStats();
  24.             std::this_thread::yield();
  25.         }
  26.         consumeJob();
  27.     }
  28. }
复制代码
cl_uint results[0x100];为nonce集合,也就是需要计算hash的运算。
程序会先停在第一个do循环
  1. do {
  2. std::this_thread::sleep_for(std::chrono::milliseconds(200));
  3. }
  4. while (Workers::isPaused());
复制代码
知道矿池推送任务后退出循环执行consumeJob();设置好需要工作任务
在接下来的循环中开始推送任务
  1. while (!Workers::isOutdated(m_sequence)) {
  2. memset(results, 0, sizeof(cl_uint) * (0x100));

  3. XMRRunJob(m_ctx, results);

  4. for (size_t i = 0; i < results[0xFF]; i++) {
  5. *m_job.nonce() = results[i];
  6. Workers::submit(m_job);
  7. }
  8. m_count += m_ctx->rawIntensity;

  9. storeStats();
  10. std::this_thread::yield();
  11. }
复制代码

XMRRunJob(m_ctx, results);利用GPU计算出需要运算的所有nonce
在for循环中将所有nonce循环复制到m_job中,最后执行该任务。

在submit中主要将job推入到m_queue队列中,最后执行uv_async_send()激活libuv处理方法onResult
  1. void Workers::onResult(uv_async_t *handle)
  2. {
  3.     JobBaton *baton = new JobBaton();
  4.     uv_mutex_lock(&m_mutex);
  5.     while (!m_queue.empty()) {
  6.         baton->jobs.push_back(std::move(m_queue.front()));
  7.         m_queue.pop_front();
  8.     }
  9.     uv_mutex_unlock(&m_mutex);

  10.     uv_queue_work(uv_default_loop(), &baton->request,
  11.         [](uv_work_t* req) {
  12.             JobBaton *baton = static_cast<JobBaton*>(req->data);
  13.             cryptonight_ctx *ctx = static_cast<cryptonight_ctx*>(_mm_malloc(sizeof(cryptonight_ctx), 16));

  14.             for (const Job &job : baton->jobs) {
  15.                 JobResult result(job);

  16.                 if (CryptoNight::hash(job, result, ctx)) {
  17.                     baton->results.push_back(result);
  18.                 }
  19.                 else {
  20.                     baton->errors++;
  21.                 }
  22.             }

  23.             _mm_free(ctx);
  24.         },
  25.         [](uv_work_t* req, int status) {
  26.             JobBaton *baton = static_cast<JobBaton*>(req->data);

  27.             for (const JobResult &result : baton->results) {
  28.                 m_listener->onJobResult(result);
  29.             }

  30.             if (baton->errors > 0 && !baton->jobs.empty()) {
  31.                 LOG_ERR("GPU #%d COMPUTE ERROR", baton->jobs[0].threadId());
  32.             }

  33.             delete baton;
  34.         }
  35.     );
  36. }
复制代码
在该方法中
首先将队列中所有待计算的job改存到baton->jobs
然后:
  1. for (const Job &job : baton->jobs) {
  2.                 JobResult result(job);

  3.                 if (CryptoNight::hash(job, result, ctx)) {
  4.                     baton->results.push_back(result);
  5.                 }
  6.                 else {
  7.                     baton->errors++;
  8.                 }
  9.             }
复制代码
循环计算每个job的hash结果,如果满足条件则将满足条件的job放入
baton->results.push_back(result);



  1.       [](uv_work_t* req, int status) {
  2.             JobBaton *baton = static_cast<JobBaton*>(req->data);

  3.             for (const JobResult &result : baton->results) {
  4.                 m_listener->onJobResult(result);
  5.             }

  6.             if (baton->errors > 0 && !baton->jobs.empty()) {
  7.                 LOG_ERR("GPU #%d COMPUTE ERROR", baton->jobs[0].threadId());
  8.             }

  9.             delete baton;
  10.         }
复制代码
执行完后进行任务回报,将计算好的job推回给矿池。

至此改代码主要流程已经走完。

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则