从pthread中获得tid及pthread_mutex_unlock本身用户态互斥

一、pthread结构中获取tid

这个问题是由于很多时候我们都是通过gettid来获得一个线程的tid,其实这个是一个非常简单的系统调用,但是即使它非常简单,我们还是要执行进行系统调用而引入的寄存器保存/恢复等操作。但是,在C库的pthread库的实现过程中,我们可以看到,用户态是肯定保存了一个线程的tid的,如果我们能够通过用户态这个一定会存在的pthread结构来获得这个tid,就可以避免大量的系统调用,从而为系统的效率提供方便。

1、C库实现方法

__pthread_create_2_0--->>>__pthread_create_2_1--->>>create_thread

我们可以看到,在通过clone系统调用来创建子线程的时候,创建的参数中传递了CLONE_PARENT_SETTID的标志,也就是让内核在创建子进程的时候将新创建的子线程的线程id放置到指定的用户态地址中

CLONE_PARENT_SETTID
 The kernels writes the thread ID of the newly created thread
 into the location pointed to by the fifth parameters to CLONE.

Note that it would be semantically equivalent to use
 CLONE_CHILD_SETTID but it is be more expensive in the kernel.

if (ARCH_CLONE (fct, STACK_VARIABLES_ARGS, clone_flags,
    pd, &pd->tid, TLS_VALUE, &pd->tid) == -1)

所以用户态是保存了一个线程自己的tid在自己的pthread结构中,所以我们就可以从这个位置获得线程的tid。

struct pthread
{
  union
  {
#if !TLS_DTV_AT_TP
    /* This overlaps the TCB as used for TLS without threads (see tls.h).  */
    tcbhead_t header;
#else
    struct
    {
      int multiple_threads;
      int gscope_flag;
# ifndef __ASSUME_PRIVATE_FUTEX
      int private_futex;
# endif
    } header;
#endif

/* This extra padding has no special purpose, and this structure layout
       is private and subject to change without affecting the official ABI.
       We just have it here in case it might be convenient for some
       implementation-specific instrumentation hack or suchlike.  */
    void *__padding[16];虽然这个结构是私有的,但是这里定义了一个强制的对其,也就是声明了一个union类型,这个类型中定义了一个最大的16字节指针,可以看到,这个是一个比较大的结构。我们可以认为它始终是这个结构中最大的一个成员,所以整个开头的这个结构认为是16个整数当然,作者也说了,这个结构是私有的,并且以后可能还会改变,所以大家不要依赖,但是也可以供一些特殊的应用使用。看来作者当时也是很纠结的说。这就是“犹抱琵琶半遮面”
  };

/* This descriptor‘s link on the `stack_used‘ or `__stack_user‘ list.  */
  list_t list;

/* Thread ID - which is also a ‘is this thread descriptor (and
     therefore stack) used‘ flag.  */
  pid_t tid;这里就是我们说的那个tid结构,加上list_t中的两个指针,所以这个结构应该是pthread的基地址偏移 4 * sizeof(void*)个字节的位置处

/* Process ID - thread group ID in kernel speak.  */
  pid_t pid;

2、pthread的基地址

还好,从代码中看到,当我们通过pthread_create创建一个线程的时候,C库无私的把这个结构给倒出来了。它就保存在pthread_create的第一个参数中,注意,不是返回值。所以我们就不用客气,直接使用这个就行了。有代码为证

int
__pthread_create_2_1 (newthread, attr, start_routine, arg)
     pthread_t *newthread;
     const pthread_attr_t *attr;
     void *(*start_routine) (void *);
     void *arg;

/* Pass the descriptor to the caller.  */
  *newthread = (pthread_t) pd;

3、这个tid内核何时填入

如果clone执行之后,子进程一致没有被调度到,那么这个值还有效吗?或者说,是不是这个值在pthread_create之后就直接可用呢?

我们看一下内核代码的实现:

do_fork--->>>copy_process

if (clone_flags & CLONE_PARENT_SETTID)
  if (put_user(p->pid, parent_tidptr
))
   goto bad_fork_cleanup_delays_binfmt;

从这里可以看到,这里执行的代码还是在父进程的上下文中,它不依赖子进程的调度和执行,所以当pthread_create返回的时候,用户态pthread结构的tid已经是可用的了,不依赖新创建的线程是否被调度。

二、pthread_mutex_lock及pthread_mutex_unlock本身数据结构用户态互斥问题

1、问题的引出

因为futex机制是为了尽量避免进入内核,这样就需要在用户态实现用户态本身的互斥,那么它是怎么在用户态保证pthread_mutex结构本身的数据结构在多线程之间是如何共享的和互斥的呢?

我们看glibc-2.7\nptl\pthread_mutex_unlock.c本身的代码:

int
internal_function attribute_hidden
__pthread_mutex_unlock_usercnt (mutex, decr)
     pthread_mutex_t *mutex;
     int decr;
{
  int newowner = 0;

switch (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex),
       PTHREAD_MUTEX_TIMED_NP))
    {
    case PTHREAD_MUTEX_RECURSIVE_NP:
      /* Recursive mutex.  */
      if (mutex->__data.__owner != THREAD_GETMEM (THREAD_SELF, tid))
 return EPERM;

if (--mutex->__data.__count != 0)
 /* We still hold the mutex.  */
 return 0;
      goto normal;

case PTHREAD_MUTEX_ERRORCHECK_NP:
      /* Error checking mutex.  */
      if (mutex->__data.__owner != THREAD_GETMEM (THREAD_SELF, tid)
   || ! lll_islocked (mutex->__data.__lock))
 return EPERM;
      /* FALLTHROUGH */

case PTHREAD_MUTEX_TIMED_NP:
    case PTHREAD_MUTEX_ADAPTIVE_NP:
      /* Always reset the owner field.  */
    normal:
      mutex->__data.__owner = 0;
      if (decr)
 /* One less user.  */
 --mutex->__data.__nusers;这里没有使用任何机制就大摇大摆的对pthread结构本身的内容进行操作,而这个是一个典型的费多线程安全代码,它涉及到 内存读+ 寄存器递减+内存写 三个步骤(若干条指令),如果在这之间切换,一定会造成这个结构数值的不一致

/* Unlock.  */
      lll_unlock (mutex->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex));
      break;
2、原因分析

①pthread_mutex_unlock无需互斥的原因

对于pthread_mutex_unlock来说,由于这个mutex本身是一个互斥锁,我们在这里假设只能有一个线程能够通过pthread_mutex_lock执行到这里,所以在unlock执行这段代码的时候,它可以认为他是万里两天一棵苗,它就是可以大大咧咧的操作这个数据,不用担心被其它线程抢占,丫包场了

②pthread_mutex_lock的原因

这样问题就被推给了pthread_mutex_lock,虽然它表示压力很大。glibc-2.7\nptl\pthread_mutex_lock.c

int
__pthread_mutex_lock (mutex)
     pthread_mutex_t *mutex;
{
  assert (sizeof (mutex->__size) >= sizeof (mutex->__data));

int oldval;
  pid_t id = THREAD_GETMEM (THREAD_SELF, tid);

int retval = 0;
  switch (__builtin_expect (PTHREAD_MUTEX_TYPE (mutex),
       PTHREAD_MUTEX_TIMED_NP))
    {
      /* Recursive mutex.  */
    case PTHREAD_MUTEX_RECURSIVE_NP:
      /* Check whether we already hold the mutex.  */
      if (mutex->__data.__owner == id)
 {
   /* Just bump the counter.  */
   if (__builtin_expect (mutex->__data.__count + 1 == 0, 0))
     /* Overflow of the counter.  */
     return EAGAIN;

++mutex->__data.__count;

return 0;
 }

/* We have to get the mutex.  */
      LLL_MUTEX_LOCK (mutex);可以看到,这个兄弟成为了数据保护的最后一道防线了。只要通过这个检测,线程就算安全了,可以认为是偷渡的最后一道门槛,过了这个,就到米国了。这就涉及到了那个经典的“用户态原子操作”了,这个问题在 ulrich dreppler的文章中有论述,这个依赖于体系结构,例如386下的cmpx本身是多CPU原子操作,所以可以实现原子性操作,而对于PowerPC和MIPS这类RISC机型,人家也有自己的玩法,就是lwarx和swarx这个姐妹花,从而可以完成用户态原子操作

assert (mutex->__data.__owner == 0);
      mutex->__data.__count = 1;
      break;

/* Error checking mutex.  */
    case PTHREAD_MUTEX_ERRORCHECK_NP:
      /* Check whether we already hold the mutex.  */
      if (__builtin_expect (mutex->__data.__owner == id, 0))
 return EDEADLK;

/* FALLTHROUGH */

case PTHREAD_MUTEX_TIMED_NP:
    simple:
      /* Normal mutex.  */
      LLL_MUTEX_LOCK (mutex);
      assert (mutex->__data.__owner == 0);
      break;

③ 一些处理器用户态原子操作的实现

为了本着“撞死南墙”的精神和大家的偷窥欲望,我们再看看这个LLL_MUTEX_LOCK的不同实现

#define LLL_MUTEX_LOCK(mutex) \
  lll_cond_lock ((mutex)->__data.__lock, PTHREAD_MUTEX_PSHARED (mutex))

看i386的实现

/* Special version of lll_lock which causes the unlock function to
   always wakeup waiters.  */
#define lll_cond_lock(futex, private) \
  (void)              \
    ({ int ignore1, ignore2, ignore3;           \
       __asm __volatile (LOCK_INSTR "cmpxchgl %1, %2\n\t"        \当头就是这个苦B的cmpxchg,从而完成用户态原子性比较替换操作
    "jnz _L_cond_lock_%=\n\t"         \
    ".subsection 1\n\t"          \
    ".type _L_cond_lock_%=,@function\n"        \
    "_L_cond_lock_%=:\n"          \
    "1:\tleal %2, %%edx\n"          \
    "0:\tmovl %7, %%ecx\n"          \
    "2:\tcall __lll_lock_wait\n"         \
    "3:\tjmp 18f\n"          \
    "4:\t.size _L_cond_lock_%=, 4b-1b\n\t"        \
    ".previous\n"           \
    LLL_STUB_UNWIND_INFO_4          \
    "18:"            \
    : "=a" (ignore1), "=c" (ignore2), "=m" (futex),      \
      "=&d" (ignore3)          \
    : "0" (0), "1" (2), "m" (futex), "g" (private)       \
    : "memory");           \
    })

powerPC也客串一下

#define __arch_compare_and_exchange_val_32_acq(mem, newval, oldval)       \
  ({               \
      __typeof (*(mem)) __tmp;            \
      __typeof (mem)  __memp = (mem);           \
      __asm __volatile (            \
          "1: lwarx %0,0,%1" MUTEX_HINT_ACQ "\n"       \
          " cmpw %0,%2\n"         \
          " bne 2f\n"          \
          " stwcx. %3,0,%1\n"         \这两个难兄难弟,就是这么实现原子性操作滴
          " bne- 1b\n"          \
          "2: " __ARCH_ACQ_INSTR         \
          : "=&r" (__tmp)           \
          : "b" (__memp), "r" (oldval), "r" (newval)       \
          : "cr0", "memory");          \
      __tmp;              \
  })

原文地址:https://www.cnblogs.com/tsecer/p/10485776.html

时间: 2024-10-05 13:51:24

从pthread中获得tid及pthread_mutex_unlock本身用户态互斥的相关文章

转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解

Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解 多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁,但是对条件变量完全不知道或者不完全了解的人群.如果您对这些都没什么概念,可能需要先了解一些基础知识) 关于条件变量典型的实际应用,可以参考非常精简的Linux线程池实现(一)——使用互斥锁和条件变量,但如果对条件变量不熟悉最好先看完本文. Pthread库的条件变量机制的主要API有三个: int p

在php中,如何将一个页面中的标签,替换为用户想输出的内容

前言:釜山行,暴露人性, ———————————————————————————————————————————————————————————————————————————— 今天说一个最简单的例子,就是在php中如何读取另一个html页面中的标签,并显示用户想输出的内容. 首先建立一个页面,命名为:test.html如下图所示: <!doctype html> <html lang="en"> <head> <meta charset=&

“System.Exception”类型的异常在 NHibernate.dll 中发生,但未在用户代码中进行处理

“System.Exception”类型的异常在 NHibernate.dll 中发生,但未在用户代码中进行处理 其他信息: OCIEnvCreate 失败,返回代码为 -1,但错误消息文本不可用. 如有适用于此异常的处理程序,该程序便可安全地继续运行.

“System.NullReferenceException”类型的异常在 App_Web_j2s3gau3.dll 中发生,但未在用户代码中进行处理的Bug解决方案

这个异常报错的原因也许在不同的项目中,也有很多种原因导致出错.在这里我针对我的项目,看了报错的提示,找了大半天.终于找到了..唉..虽然简单,但还是写写. 我的项目里实现了URL的重写,URL重写类里面做了一个“网站访问日志”的功能,该功能只不过是记录来网站的URL,并保存数据库中.在这里我用到了,缓存(HttpRuntime.Cache),在整个应用程序中都可以访问该值.之前的是 Microsoft .NET Framework 3.5,现在我 升到Microsoft .NET Framewo

Oracle_11g中解决被锁定的scott用户的方法(转载)

转自:http://www.2cto.com/database/201402/277206.html Oracle 11g中修改被锁定的用户:scott 在安装完Oracle11g和创建完oracle数据库之后,想用数据库自带的用户scott登录,看看连接是否成功. 在cmd命令中,用“sqlplus  scott/ tiger”登录时, 老是提示如下信息: ERROR:ORA-28000:账户已被锁定. 在cmd命令提示符中可直接登录oracle,输入如下命令: sqlplus / as sy

在ASP.NET中动态加载内容(用户控件和模板)

在ASP.NET中动态加载内容(用户控件和模板) 要点: 1. 使用Page.ParseControl 2. 使用base.LoadControl 第一部分:加载模板 下 面是一个模板“<table width=100%><tr><td width=100% colspan=2 runat=server id=ContainerTop></td></tr><tr><td width=30% runat=server id=Con

Linux中的栈:用户态栈/内核栈/中断栈

http://blog.chinaunix.net/uid-14528823-id-4136760.html Linux中有多种栈,很容易弄晕,简单说明一下: 1.用户态栈:在进程用户态地址空间底部,跟平时我们简单和理解的一样,就是虚拟地址空间中的一段,不多说~ 2.内核栈:     跟用户态栈是独立的,在用户态和内核态切换时,需要进行切换.     默认8k,可以通过内核配置项修改     跟thread_info结构放在一起,公用一个union:thread_union, 点击(此处)折叠或

SharedObject使用:在FluorineFx.net与Flex中使用共享对象维护在线用户列表实例【转】

一.添加一个新的FluorineFx的服务类项目OnLineService,删除原有的Sample.cs,并添加一个用户类定义与一个ApplicationAdpater类:如下: /*-- User.cs --*/ namespace OnLineService { public class User { public string UserName { get; set; } public string UserPsw { get; set; } } } /* --  ApplicationA

PHP中实现支持显示格式化的用户输入

你可以在这个页面下载这个文档附带的文件,也可以在文件下载中的字符处理中下载这个文档描述如何安全显示的有格式的用户输入.我们将讨论没有经过过滤的输出的危险,给出一个安全的显示格式化输出的方法. 没有过滤输出的危险 如果你仅仅获得用户的输入然后显示它,你可能会破坏你的输出页面,如一些人能恶意地在他们提交的输入框中嵌入javascript脚本: This is my comment. <script language="javascript: alert('Do something bad he