0
点赞
收藏
分享

微信扫一扫

98-模拟 errno


这一篇,让我们搞懂 errno 的实现原理,不过为了防止名字冲突,我们换一个名字,叫 myerrno.

1. 思路

讲一下用到的技术吧:

  • pthread once,参考​​《只被执行一次的函数》​​
  • 线程私有变量,参考​​《线程私有变量》​​

本质上 errno 并不是一个真正意义上的变量,而是通过宏定义扩展为语句,而这一行语句实际上是在调用函数,该函数返回保存了指向 errno 变量的指针。

这里我们直接看程序就行了。

2. 程序清单

2.1 代码

// myerrno.c
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>

// 实际上 myerrno 就是一个宏定义
#define myerrno (*_myerrno())

pthread_key_t key;
pthread_once_t init_done = PTHREAD_ONCE_INIT;

// 使用 pthread once 对键进行初始化
void thread_init() {
puts("I'm thread_init");
pthread_key_create(&key, free); // 这里注册了析构函数就是 free
}

// 该函数用来获取真正的 myerrno 的地址
int *_myerrno() {
int *p;
pthread_once(&init_done, thread_init);
// 如果根据键拿到的是一个空地址,说明之前还未分配内存
p = (int*) pthread_getspecific(key);
if (p == NULL) {
p = (int*)malloc(sizeof(int));
pthread_setspecific(key, (void*)p);
}
/**************************************/
return p;
}

void* fun1() {
errno = 5;
myerrno = 5; // 这一行被扩展成 (*_myerrno()) = 5
sleep(1);
// printf 后面的 myerrno 会被扩展成 (*_myerrno())
printf("fun1: errno = %d, myerrno = %d\n", errno, myerrno);
return NULL;
}

void* fun2() {
errno = 10;
myerrno = 10; // 这一行被扩展成 (*_myerrno()) = 10
printf("fun2: errno = %d, myerrno = %d\n", errno, myerrno);
return NULL;
}

int main() {
pthread_t tid1, tid2;
pthread_create(&tid1, NULL, fun1, NULL);
pthread_create(&tid2, NULL, fun2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}

2.2 编译和运行

  • 编译和运行

$ gcc myerrno.c -o myerrno -lpthread
$ ./myerrno

  • 运行结果


98-模拟 errno_宏定义


图1 运行结果


图1 的结果也正是我们期望的。上面的程序也很容易读懂,关键技术在于宏定义上的一个小 trick,这里就不再分析了。

再看一下 bits/errno.h 中的 errno 就知道,它也是这么做的:


98-模拟 errno_linux_02


图2 errno 变量,在多线程环境中就是一个宏定义


图 2 中的 __errno_location 函数就相当于我们程序的 _myerrno 函数。

3. 总结

  • 理解 errno 并不是真正意义上的变量


举报

相关推荐

0 条评论