内核中的 Futex 和阻塞

Posted

技术标签:

【中文标题】内核中的 Futex 和阻塞【英文标题】:Futexes and blocking in the kernel 【发布时间】:2016-01-17 10:56:52 【问题描述】:

我正在阅读一些文档并尝试了一些在 Linux 中发出 futex 系统调用的代码示例。我读到如果thread_a 使用FUTEX_LOCK_PI 获取互斥锁,并说如果另一个线程thread_b 尝试获取相同的,后者(thread_b)在内核中被阻止。

“在内核中阻塞”到底是什么意思?是不是thread_b 在用户空间的执行没有恢复?在以下示例中,第二个线程似乎也在系统调用之后发出 printf。这不是表明它没有在内核中被阻止吗?我错过了什么?

/*
 * This example demonstrates that when futex_lock_pi is called twice, the
 * second call is blocked inside the kernel.
 */

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

int mutex = 0;

#define  __NR_futex          240
#define FUTEX_WAIT 0
#define FUTEX_WAKE 1
#define FUTEX_FD 2
#define FUTEX_REQUEUE 3
#define FUTEX_CMP_REQUEUE 4
#define FUTEX_WAKE_OP 5
#define FUTEX_LOCK_PI 6
#define FUTEX_UNLOCK_PI 7
#define FUTEX_TRYLOCK_PI 8
#define FUTEX_WAIT_REQUEUE_PI 11
#define FUTEX_CMP_REQUEUE_PI  12


void *thread(void *arg) 
  int ret = 0;
  pid_t tid = gettid();
  printf("Entering thread[%d]\n", tid);

  ret = syscall(__NR_futex, &mutex, FUTEX_LOCK_PI, 1, NULL, NULL, 0);
  printf("Thread %d returned from kernel\n", tid);
  printf("Value of mutex=%d\n", mutex);
  printf("Return value from syscall=%d\n", ret);


int main(int argc, const char *argv[]) 
  pthread_t t1, t2;

  if (pthread_create(&t1, NULL, thread, NULL)) 
    printf("Could not create thread 1\n");
    return -1;
  

  if (pthread_create(&t2, NULL, thread, NULL)) 
    printf("Could not create thread 2\n");
    return -1;
  

  // Loop infinitely
  while ( 1 )  

  return 0;

输出如下:-

Entering thread[952]
Thread 952 returned from kernel
Entering thread[951]
Value of mutex=-2147482696
Return value from syscall=0
Thread 951 returned from kernel
Value of mutex=-1073740873
Return value from syscall=0

【问题讨论】:

你使用futexes in priority-inversion mode,它实际上与互斥锁一起工作(这就是为什么LOCK futex 操作名称中的子字符串)。在该模式下,futex 值转换方案已完全定义,并且不依赖于 futex 系统调用的 valval2 参数。 未锁定状态下futex的值必须为0。检查一下。 另外,在 PI 模式下,内核检查进程(线程)是否对应于存储在 futex 中的 tid,并实际访问它以执行优先级反转。据我了解,如果退出 futex 的所有者,则 mutex 被视为已解锁。尝试在线程函数末尾添加sleep(),并检查第二个线程是否在第一个线程(futex 所有者)休眠时从futex() 调用返回。 (这不应该发生)。 你把互斥锁初始化成什么?你能给我们完整的、可编译的代码吗? @DavidSchwartz 我已将代码更新为完整且可编译,谢谢。 您的代码和输出不匹配。您能否显示此代码的输出,并在理想情况下添加系统调用返回值? 【参考方案1】:

在内核中阻塞意味着线程进入睡眠状态,直到有事件唤醒它,在你的情况下,事件是可用的互斥锁。

我稍微修改了你的代码来说明:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/syscall.h>

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void *thread(void *unused) 
  printf("Entering thread %d\n", syscall(SYS_gettid));

  pthread_mutex_lock(&mutex);

  sleep(2);

  pthread_mutex_unlock(&mutex);

  return NULL;


int main(int argc, const char *argv[]) 
  pthread_t t1, t2;

  printf("using mutex @%p\n", &mutex);

  if (pthread_create(&t1, NULL, thread, &t1)) 
    printf("Could not create thread 1\n");
    return -1;
  

  if (pthread_create(&t2, NULL, thread, &t2)) 
    printf("Could not create thread 2\n");
    return -1;
  

  pthread_join(t1, NULL);
  pthread_join(t2, NULL);

  return 0;

代码基本相同,但我明确使用 pthread 库的互斥锁。

如果我用 strace 运行这段代码,我会得到:

using mutex @0x6010a0
Process 19688 attached
Entering thread 19688
Process 19689 attached
Entering thread 19689
[pid 19689] futex(0x6010a0, FUTEX_WAIT_PRIVATE, 2, NULL <unfinished ...>
[pid 19688] futex(0x6010a0, FUTEX_WAKE_PRIVATE, 1) = 1
[pid 19689] <... futex resumed> )       = 0
[pid 19688] +++ exited with 0 +++
[pid 19689] futex(0x6010a0, FUTEX_WAKE_PRIVATE, 1) = 0
[pid 19689] +++ exited with 0 +++
+++ exited with 0 +++

您可能会注意到我们没有看到第一个线程使用互斥锁,但我们可以看到下一个正在等待的 futex (FUTEX_WAIT_PRIVATE)。这是因为在使用互斥锁时不会调用 futex。

但是你可以看到第一个线程(这里的 id 19688)最终调用了 futex(FUTEX_WAKE_PRIVATE),它告诉内核在 utex 空闲时唤醒另一个线程。

您可能已经注意到第一次调用 [pid 19689] futex(0x6010a0, FUTEX_WAIT_PRIVATE, 2, NULL &lt;unfinished ...&gt; 是未完成的,这意味着进程被挂起,等待内核完成工作并交还手。 然后[pid 19689] &lt;... futex resumed&gt; ) = 0 勾画出调用最终完成(显然是因为互斥体被释放了)

【讨论】:

以上是关于内核中的 Futex 和阻塞的主要内容,如果未能解决你的问题,请参考以下文章

v79.01 鸿蒙内核源码分析(用户态锁篇) | 如何使用快锁Futex(上) | 百篇博客分析OpenHarmony源码

linux 内核的futex - requeue 以及 requeue-pi

2.3. Futex系统调用

2.3. Futex系统调用

转载:futex同步机制详解

v80.01 鸿蒙内核源码分析(内核态锁篇) | 如何实现快锁Futex(下) | 百篇博客分析OpenHarmony源码