【问题标题】:Can RAND_event() in message handler cause little-freezes in UI?消息处理程序中的 RAND_event() 会导致 UI 中的小冻结吗?
【发布时间】:2016-06-13 08:02:16
【问题描述】:

有一个像这样的消息处理程序(在 C++ Builder 中):

void __fastcall TMainForm::HandleMessages(tagMSG &Msg, bool &Handled)
{
    RAND_event(Msg.message, Msg.wParam, Msg.lParam);
    //...
}

RAND_event() 会导致 UI 出现小冻结吗?

谢谢!

编辑:

RAND_event() 来自 OpenSSL,下面是它的描述:

RAND_event() 从鼠标等 Windows 事件中收集熵 动作和其他用户交互。它应该被调用 发送到窗口的所有消息的 iMsg、wParam 和 lParam 参数 程序。它将估计事件消息中包含的熵 (如果有),并将其添加到 PRNG。然后程序可以处理 像往常一样发消息。

【问题讨论】:

  • 仅供参考,您展示的是Application->OnMessage 事件处理程序,它与“窗口过程”不同。 OnMessage 事件仅针对发布到主 UI 消息队列的消息触发。它不会看到直接发送到实际窗口过程并绕过消息队列的消息。话虽如此,如果您查看rand_event() 的实际源代码,它的开销相当小,不太可能导致明显的 UI 冻结。此外,您正在为开始排队的消息调用它,因此它们已经稍微延迟了。
  • 谢谢。是的,它不是一个窗口过程。在我们的例子中,它只是处理后台线程发布的一些自定义消息。

标签: c++ windows winapi openssl c++builder


【解决方案1】:

如有疑问 - 验证源代码。

来自\crypto\rand\rand_win.c(OpenSSL 1.0.2f)

int RAND_event(UINT iMsg, WPARAM wParam, LPARAM lParam)
{
    double add_entropy = 0;

    switch (iMsg) {
    case WM_KEYDOWN:
        {
            static WPARAM key;
            if (key != wParam)
                add_entropy = 0.05;
            key = wParam;
        }
        break;
    case WM_MOUSEMOVE:
        {
            static int lastx, lasty, lastdx, lastdy;
            int x, y, dx, dy;

            x = LOWORD(lParam);
            y = HIWORD(lParam);
            dx = lastx - x;
            dy = lasty - y;
            if (dx != 0 && dy != 0 && dx - lastdx != 0 && dy - lastdy != 0)
                add_entropy = .2;
            lastx = x, lasty = y;
            lastdx = dx, lastdy = dy;
        }
        break;
    }

    readtimer();
    RAND_add(&iMsg, sizeof(iMsg), add_entropy);
    RAND_add(&wParam, sizeof(wParam), 0);
    RAND_add(&lParam, sizeof(lParam), 0);

    return (RAND_status());
}

如您所见,这里没有循环、等待或类似的事情,只是一些基本的比较、分配和添加。您还可以自己验证 RAND_event() 中使用的一些函数,例如 readtimer(),但它们也不包含任何可能导致性能显着下降的内容(至少与使用 UI 相比)。

编辑: 哇,我刚才看到了雷米的评论,我的错。无论如何,我把它作为一个扩展的答案(已经作为评论存在)从一点(非常少)不同的角度来看。

【讨论】:

    【解决方案2】:

    RAND_event() 会导致 UI 出现小冻结吗?

    也许吧。熵通过RAND_add添加,这意味着熵被提取然后消化。因此,每次调用 RAND_add 时都会调用一个哈希函数。

    如果您使用的是默认 RAND 引擎,则使用的引擎是来自 <openssl src>\crypto\rand\md_rand.c 的引擎。这意味着RAND_add 调用ssleay_rand_add。功能如下图。

    如果正在发送大量 Windows 消息,那么我可以设想一个桌面在消化熵的同时变慢的情况。这个问题在移动操作系统上可能会变得更加严重。分析工具应该能够为您提供更多信息。


    我认为 Windows 上的RAND_event 可能有一个优化...应该累积消息然后批量添加。即累积熵,然后在第 8 次或第 16 次或第 32 次调用时调用 RAND_add

    生成器的运行状况不应该受到影响,因为发送到应用程序的 Windows 消息如此之多。您可以使用Spy++ 了解正在发送的消息数量。


    如果你在某处使用RAND_poll,那么你会看到一个明显的块。有关这方面的详细信息,请参阅 OpenSSL 错误跟踪器上的 Issue #2100: RAND_poll can be incredibly slow on Windows7 due to Heap32Next


    static void ssleay_rand_add(const void *buf, int num, double add)
    {
        int i, j, k, st_idx;
        long md_c[2];
        unsigned char local_md[MD_DIGEST_LENGTH];
        EVP_MD_CTX m;
        int do_not_lock;
    
        if (!num)
            return;
    
        /*
         * (Based on the rand(3) manpage)
         *
         * The input is chopped up into units of 20 bytes (or less for
         * the last block).  Each of these blocks is run through the hash
         * function as follows:  The data passed to the hash function
         * is the current 'md', the same number of bytes from the 'state'
         * (the location determined by in incremented looping index) as
         * the current 'block', the new key data 'block', and 'count'
         * (which is incremented after each use).
         * The result of this is kept in 'md' and also xored into the
         * 'state' at the same locations that were used as input into the
         * hash function.
         */
    
        /* check if we already have the lock */
        if (crypto_lock_rand) {
            CRYPTO_THREADID cur;
            CRYPTO_THREADID_current(&cur);
            CRYPTO_r_lock(CRYPTO_LOCK_RAND2);
            do_not_lock = !CRYPTO_THREADID_cmp(&locking_threadid, &cur);
            CRYPTO_r_unlock(CRYPTO_LOCK_RAND2);
        } else
            do_not_lock = 0;
    
        if (!do_not_lock)
            CRYPTO_w_lock(CRYPTO_LOCK_RAND);
        st_idx = state_index;
    
        /*
         * use our own copies of the counters so that even if a concurrent thread
         * seeds with exactly the same data and uses the same subarray there's
         * _some_ difference
         */
        md_c[0] = md_count[0];
        md_c[1] = md_count[1];
    
        memcpy(local_md, md, sizeof md);
    
        /* state_index <= state_num <= STATE_SIZE */
        state_index += num;
        if (state_index >= STATE_SIZE) {
            state_index %= STATE_SIZE;
            state_num = STATE_SIZE;
        } else if (state_num < STATE_SIZE) {
            if (state_index > state_num)
                state_num = state_index;
        }
        /* state_index <= state_num <= STATE_SIZE */
    
        /*
         * state[st_idx], ..., state[(st_idx + num - 1) % STATE_SIZE] are what we
         * will use now, but other threads may use them as well
         */
    
        md_count[1] += (num / MD_DIGEST_LENGTH) + (num % MD_DIGEST_LENGTH > 0);
    
        if (!do_not_lock)
            CRYPTO_w_unlock(CRYPTO_LOCK_RAND);
    
        EVP_MD_CTX_init(&m);
        for (i = 0; i < num; i += MD_DIGEST_LENGTH) {
            j = (num - i);
            j = (j > MD_DIGEST_LENGTH) ? MD_DIGEST_LENGTH : j;
    
            MD_Init(&m);
            MD_Update(&m, local_md, MD_DIGEST_LENGTH);
            k = (st_idx + j) - STATE_SIZE;
            if (k > 0) {
                MD_Update(&m, &(state[st_idx]), j - k);
                MD_Update(&m, &(state[0]), k);
            } else
                MD_Update(&m, &(state[st_idx]), j);
    
            /* DO NOT REMOVE THE FOLLOWING CALL TO MD_Update()! */
            MD_Update(&m, buf, j);
            /*
             * We know that line may cause programs such as purify and valgrind
             * to complain about use of uninitialized data.  The problem is not,
             * it's with the caller.  Removing that line will make sure you get
             * really bad randomness and thereby other problems such as very
             * insecure keys.
             */
    
            MD_Update(&m, (unsigned char *)&(md_c[0]), sizeof(md_c));
            MD_Final(&m, local_md);
            md_c[1]++;
    
            buf = (const char *)buf + j;
    
            for (k = 0; k < j; k++) {
                /*
                 * Parallel threads may interfere with this, but always each byte
                 * of the new state is the XOR of some previous value of its and
                 * local_md (itermediate values may be lost). Alway using locking
                 * could hurt performance more than necessary given that
                 * conflicts occur only when the total seeding is longer than the
                 * random state.
                 */
                state[st_idx++] ^= local_md[k];
                if (st_idx >= STATE_SIZE)
                    st_idx = 0;
            }
        }
        EVP_MD_CTX_cleanup(&m);
    
        if (!do_not_lock)
            CRYPTO_w_lock(CRYPTO_LOCK_RAND);
        /*
         * Don't just copy back local_md into md -- this could mean that other
         * thread's seeding remains without effect (except for the incremented
         * counter).  By XORing it we keep at least as much entropy as fits into
         * md.
         */
        for (k = 0; k < (int)sizeof(md); k++) {
            md[k] ^= local_md[k];
        }
        if (entropy < ENTROPY_NEEDED) /* stop counting when we have enough */
            entropy += add;
        if (!do_not_lock)
            CRYPTO_w_unlock(CRYPTO_LOCK_RAND);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-10-06
      • 1970-01-01
      • 2012-08-02
      • 1970-01-01
      • 1970-01-01
      • 2013-02-17
      • 2016-11-04
      • 2021-11-22
      相关资源
      最近更新 更多