【问题标题】:what's the getpid work procedure in glibc?glibc中的getpid工作流程是什么?
【发布时间】:2017-09-01 03:49:56
【问题描述】:

情况如下:

我正在尝试执行project 来破解 github 中的内核。内核版本为linux-3.18.6。

QEMU用于模拟环境。

在我的应用程序中,我尝试通过跟随它们来理解系统调用过程。完成我的目标的方式就像shell程序一样。我只是创建一些命令来运行相对系统调用。也许通过图片很简单。 some commands

代码简单如下:

1 使用 API getpid。

int Getpid(int argc, char **argv)
{
    pid_t pid;
    pid = getpid();
    printf("current process's pid:%d\n",pid);
    return 0;
}

2 直接使用 int $0x80。

int GetpidAsm(int argc, char **argv)
{
    pid_t pid;
    asm volatile(
    "mov $20, %%eax\n\t"
    "int $0x80\n\t"
    "mov %%eax, %0\n\t"
    :"=m"(pid)
    );
    printf("current process's pid(ASM):%d\n",pid);
    return 0;
}

因为我的应用程序只是在pid为1的进程中运行,所以每次我输入命令getpid时,它都会返回1。当然是这样的。

奇怪的是,当我使用 gdb 调试 syscall 进程时,它只会在我键入 getpid 执行时在 berakpoint sys_getpid 处停止一次。当我一次又一次地这样做时,它只是不停地输出。

显然,据我所知,使用 int $0x80 是绝对正确的。

为了解决这个问题,我做了一些研究。 我下载了 glibc 源代码(glibc-2.25)代码来查看 api getpid 如何包装 int $0x80。不幸的是,它不在那里,或者我只是没有找到正确的位置。

glibc 中的一些代码。

pid_t getpid(void)
{
  pid_t (*f)(void);
  f = (pid_t (*)(void)) dlsym (RTLD_NEXT, "getpid");
  if (f == NULL)
    error (EXIT_FAILURE, 0, "dlsym (RTLD_NEXT, \"getpid\"): %s", dlerror ());
  return (pid2 = f()) + 26;
}

如果我得到了错误的代码,请告诉我,tks。

如代码所示,glibc 中不包含 getpid 的定义。看了一些资料,有人说the VDSO...

请注意,AFAIK 是简单系统调用成本的重要组成部分 正在从用户空间到内核并返回。因此,对于一些系统调用 (可能是 gettimeofday、getpid ...)VDSO 甚至可以避免这种情况 (并且在技术上可能会避免进行真正的系统调用)。

在 man getpid pgae 中:

C 库/内核差异 从 glibc 版本 2.3.4 开始,glibc 的 getpid() 包装函数 缓存 PID,以避免在进程时额外的系统调用 反复调用 getpid()。通常这种缓存是不可见的,但它的 正确的操作依赖于包装函数的支持 fork(2)、vfork(2) 和 clone(2):如果应用程序绕过 glibc 使用 syscall(2) 封装这些系统调用,然后调用 孩子中的 getpid() 将返回错误的值(准确地说:它 将返回父进程的PID)。另请参阅 clone(2) 以了解 解决 getpid() 可能返回错误值的情况,即使 通过 glibc 包装函数调用 clone(2)。

虽然exit这么多解释,但我搞不懂API getpid的工作流程。

相比之下,API 时间很容易理解。 时间的定义:

time_t
time (time_t *t)
{
  INTERNAL_SYSCALL_DECL (err);
  time_t res = INTERNAL_SYSCALL (time, err, 1, NULL);
  /* There cannot be any error.  */
  if (t != NULL)
    *t = res;
  return res;
}

那么,

#define INTERNAL_SYSCALL(name, err, nr, args...)            \
    internal_syscall##nr ("li\t%0, %2\t\t\t# " #name "\n\t",    \
                  "IK" (SYS_ify (name)),            \
                  0, err, args)

最后是嵌入式asm,正常使用内核源码的方式。

#define internal_syscall1(v0_init, input, number, err, arg1)        \
({                                  \
    long _sys_result;                       \
                                    \
    {                               \
    register long __s0 asm ("$16") __attribute__ ((unused))     \
      = (number);                           \
    register long __v0 asm ("$2");                  \
    register long __a0 asm ("$4") = (long) (arg1);          \
    register long __a3 asm ("$7");                  \
    __asm__ volatile (                      \
    ".set\tnoreorder\n\t"                       \
    v0_init                             \
    "syscall\n\t"                           \
    ".set reorder"                          \
    : "=r" (__v0), "=r" (__a3)                  \
    : input, "r" (__a0)                     \
    : __SYSCALL_CLOBBERS);                      \
    err = __a3;                         \
    _sys_result = __v0;                     \
    }                               \
    _sys_result;                            \
})

有人可以清楚地解释 API getpid 的工作原理吗?为什么 getpid 只陷入系统调用 sys_getpid 一次?如果可能,推荐一些参考资料。

感谢您的帮助。

【问题讨论】:

  • 您的问题到底是什么?您已阅读手册:glibc 确实缓存了 getpid-syscall 返回的值。显然这个缓存必须在子进程的 fork(2) 之后重新设置。
  • 感谢您的回答。使用 dlsym 的 getpid 机制是什么?为什么getpid的实现与其他的不同?这就是我想知道的。谢谢。

标签: linux linux-kernel system-calls glibc vdso


【解决方案1】:

首先请注意,glibc 源代码几乎无法导航。

如您所见,文档指出 getpid() 会缓存其结果。 你发现的代码看起来像

pid_t getpid(void)
{
  pid_t (*f)(void);
  f = (pid_t (*)(void)) dlsym (RTLD_NEXT, "getpid");
  if (f == NULL)
    error (EXIT_FAILURE, 0, "dlsym (RTLD_NEXT, \"getpid\"): %s", dlerror ());
  return (pid2 = f()) + 26;
}

只是一个包装器。它查找getpid 符号,并调用该函数。 该功能是您需要找到的。它是 __getpid() 函数的别名,您可以在 sysdeps/unix/sysv/linux/getpid.c 文件中找到该函数,并且也显示在本文底部。

现在 - 您可能正在查看与您当前 glibc 不匹配的 glibc 源代码 - 关于 2016 年 11 月 this commit 中的 getpid() 缓存发生了很大变化,据我所知,这种变化会成为 2017 年 2 月发布的 glibc-2.25 的一部分

缓存其值以避免多次调用 getpid() 系统调用的较旧的 getpid() 实现可以在此处看到: http://repo.or.cz/glibc.git/blob/93eb85ceb25ee7aff432ddea0abf559f53d7a5fc:/sysdeps/unix/sysv/linux/getpid.c 看起来像

static inline __attribute__((always_inline)) pid_t
really_getpid (pid_t oldval)
{
  if (__glibc_likely (oldval == 0))
    {
      pid_t selftid = THREAD_GETMEM (THREAD_SELF, tid);
      if (__glibc_likely (selftid != 0))
    return selftid;
    }

  INTERNAL_SYSCALL_DECL (err);
  pid_t result = INTERNAL_SYSCALL (getpid, err, 0);

  /* We do not set the PID field in the TID here since we might be
     called from a signal handler while the thread executes fork.  */
  if (oldval == 0)
    THREAD_SETMEM (THREAD_SELF, tid, result);
  return result;
}
#endif

pid_t
__getpid (void)
{
#if !IS_IN (libc)
  INTERNAL_SYSCALL_DECL (err);
  pid_t result = INTERNAL_SYSCALL (getpid, err, 0);
#else
  pid_t result = THREAD_GETMEM (THREAD_SELF, pid);
  if (__glibc_unlikely (result <= 0))
    result = really_getpid (result);
#endif
  return result;
}

libc_hidden_def (__getpid)
weak_alias (__getpid, getpid)
libc_hidden_def (getpid)

【讨论】:

  • 感谢您的详细解答。也许我明白你的意思。收到您的消息后,我刚刚使用 ldd 检查了我的 ubuntu glibc 版本,我用它编译了内核。正如您所指出的,它是 glibc-2.23,我在 glibc-2.23 中找到了您指出的代码。所以pizzle应该解决了。但是我还是想不通为什么getpid的实现和time或者其他需要进入内核态的api不一样。我也不知道 getpid 使用的 dlsym 机制。如果可能的话,你能提供一些细节的参考吗?我想深入挖掘,Tks。
  • @a-thorn 进程的 pid 永远不会改变。因此 glibc 可以从内核中获取一次 pid,然后将其缓存以备将来使用,而无需执行另一个系统调用。我希望很明显为什么不能以相同的方式缓存对 time() 的调用——大多数其他系统调用也是如此——缓存它们的结果是没有意义的。您发布的使用 dlsym() 函数查找 getpid 的代码似乎来自 ./elf/restest2.c ,该文件是 glibc 测试套件的一部分,而不是 getpid() 实现。
  • 感谢您的回答,我佩服您的效率。是的,现在我终于知道区别了。请原谅我的愚蠢。现在我知道它是 getpid 的测试套件,但我真的找不到 getpid() 实现。你能给我指出来吗?我使用 getpid,但我只是找到了 __getpid 实现。另外,我理解时间是因为 time() 使用 int $0x80 进入内核模式,然后内核找到实现 sys_time。至于 getpid(),我很困惑。感谢您的关注。
  • 也许我明白你的意思了,因为getpid是__getpid的别名,第一次调用getpid的时候,其实调用的是__getpid,因为在libc中,所以调用的是INTERNAL_SYSCALL。宏INTERNAL_SYSCALL的实现也是嵌入式asm。如果再次调用,则使用缓存。感谢您的考虑。
  • 所有发生的事情是你的 getpid() 调用总是以 __getpid () 结束,在这里你看到它是 pid_t result = THREAD_GETMEM (THREAD_SELF, pid) ,这是缓存的值。如果该调用返回 really_getpid ,后者调用实际的内核系统调用并存储/缓存系统调用的返回值。 real_getpid() 函数执行 INTERNAL_SYSCALL (getpid, err, 0) ,它执行 int $0x80 或平台上执行内核系统调用所需的任何内容。
猜你喜欢
  • 2019-01-07
  • 2020-03-08
  • 2011-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-03-28
  • 2022-01-04
相关资源
最近更新 更多