GLIBC源码——putchar
GLIBC源码——putchar
GLIBC源码——从我认为最简单的putchar
开始
putchar
放在putchar.c
中,而putchar.c
放在libio
文件夹里
加上注释,一共只有36
行
#include "libioP.h"
#include "stdio.h"#undef putcharint
putchar (int c)
{int result;_IO_acquire_lock (stdout);result = _IO_putc_unlocked (c, stdout);_IO_release_lock (stdout);return result;
}#if defined weak_alias && !defined _IO_MTSAFE_IO
#undef putchar_unlocked
weak_alias (putchar, putchar_unlocked)
#endif
首先是putchar
的定义,然后是一个编译宏:如果定义了weak_alias
并且没有定义_IO_MTSFAE_IO
,即线程安全的IO,则,putchar_unlocked
就和putchar
没有区别
我理解为,当没有定义线程安全的IO后,putchar所使用的锁就失效了,所以和putchar_unlocked
功能上就一样了
所有带有_unlocked
后缀的函数都没有对自己的锁,因此不是线程安全的
putchar
的流程大致如下:申请锁,把真正的工作交给_IO_putc_unlocked
函数,释放锁
其中,和IO锁有关的函数在libio/libioP.h
里面定义
打开这个文件,查找对应的定义:
static inline void
__attribute__ ((__always_inline__))
_IO_acquire_lock_fct (FILE **p)
{FILE *fp = *p;if ((fp->_flags & _IO_USER_LOCK) == 0)_IO_funlockfile (fp);
}#if !defined _IO_MTSAFE_IO && IS_IN (libc)
# define _IO_acquire_lock(_fp) \do {# define _IO_release_lock(_fp) \} while (0)
#endif
让我比较震惊的是,不论在哪里都找不到_IO_release_lock
有关的定义
很显然的是,_IO_acquire_lock
也不是在这里定义的
进行查找后发现在sysdeps
里面的文件里有定义,说明这个锁的实现和具体的架构有关
在sysdeps/generic/stdio-lock.h
里面,我找到了定义
#if defined _LIBC && IS_IN (libc)# ifdef __EXCEPTIONS
# define _IO_acquire_lock(_fp) \do { \FILE *_IO_acquire_lock_file \__attribute__((cleanup (_IO_acquire_lock_fct))) \= (_fp); \_IO_flockfile (_IO_acquire_lock_file);
# else
# define _IO_acquire_lock(_fp) _IO_acquire_lock_needs_exceptions_enabled
# endif
# define _IO_release_lock(_fp) ; } while (0)#endif
这里的__EXCEPTIONS
宏,我只在ChangeLog
里面找到了一句话:[!__EXCEPTIONS]: Mangle local variable not_first_call.
Cleanup修饰一个变量在该变量作用域结束后, 自动调用一个指定的方法。
所谓作用域结束,包括大括号结束、return、goto、break、exception等各种情况。
https://blog.csdn.net/u013062716/article/details/52145692?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control
完整地把这一段连在一起:
do {FILE *_IO_acquire_lock_file __attribute__((cleanup(_IO_acquire_lock_fct))) = (_fp);_IO_flock_file(_IO_acquire_lock_file);// some code} while(0);
首先,声明了一个FILE*
类型的智能指针,它在出作用域的时候,会自动调用_IO_acquire_lock_fct
这个函数
但是在这个函数出作用域之前,会先调用_IO_flockfile
这个宏
这个宏定义在htl/flockfile.c
中
void
__flockfile (FILE *stream)
{#ifdef SHARED__libc_ptf_call (_IO_flockfile, (stream), 0);
#endif
}
weak_alias (__flockfile, _IO_flockfile)
weak_alias (__flockfile, flockfile)
即_IO_flockfile
是__flockfile
的弱别名
它调用了这个宏:__libc_ptf_call
,即pthread function call,它定义在libc-lockP.h
中
# define __libc_ptf_call(FUNC, ARGS, ELSE) \(__libc_pthread_functions_init ? PTHFCT_CALL (ptr_##FUNC, ARGS) : ELSE)
即这个宏会让__flockfile
执行ptr__IO_flockfile
这个函数
这个函数定义在pthread_function.h
中,它是结构体pthread_functions
的一员
在htl/pt-initialize.c
中,这个函数指针指向了这个函数:_cthreads_flockfile
该函数在sysdeps/htl/pthread-functions.h
里面有一个声明,而定义的内容则放在htl/lockfile.c
中:
void
_cthreads_flockfile (FILE *fp)
{_IO_lock_lock (*fp->_lock);
}
这个宏,_IO_lock_lock
,在多个地方都有定义:
#define _IO_lock_lock(_name) \do { \void *__self = THREAD_SELF; \if ((_name).owner != __self) \{ \lll_lock ((_name).lock, LLL_PRIVATE); \(_name).owner = __self; \} \++(_name).cnt; \} while (0)
这里有一个lll_lock
和一个THREAD_SELF
THREAD_SELF
大概只是一个身份标记之类的宏,它的实现细节涉及到底层汇编
而lll_lock
,lll
的意思就是,low level lock
它定义在sysdeps/nptl/lowlevellock.h
中
它才是真正的上锁的工具
它的底层实现使用了原子操作,具体细节因为体力优先,不再深究
现在让我们梳理一下路径:
putchar需要调用_IO_acquire_lock
这个宏
这个宏在libio/libioP.h
中有相关的一个函数,叫_IO_acquire_lock_fct
,理解为_IO_acquire_lock
的fact
函数
而_IO_acquire_lock
,在sysdeps/generic/stdio-lock.h
里面,有一个定义,它声明了一个在出了定义域后就会自动调用_IO_acquire_lock_fct
的变量,然后调用了一个函数_IO_flockfile
而这个_IO_flockfile
其实是__flockfile
的别名,它定义在sysdeps/htl/flockfile.c
里,调用了一个函数,名为ptr__IO_flockfile
,这个函数是sysdeps/htl/pthread-functions.h
里面的一个结构体pthread_functions
里面的一员
而这个变量的初始化放在htl/pt-initialize.c
中,它指向_cthreads_flockfile
,定义在htl/lockfile.c
中,调用了_IO_lock_lock
,这个宏使用了lll_lock
和THREAD_SELF
,它们几乎是最底层的操作,使用了内嵌汇编和原子操作等
_IO_acquire_lock_fct
里面调用了_IO_funlockfile
,它是__funlockfile
的别名,定义在sysdeps/pthread/funlockfile.c
中,调用了unlock
至此,整个文件加锁的流程都在我们面前展示出来了(我们只是简单地跟踪了调用的函数流程,并没有深究,很多细节都被我们忽略,只能知其然)
任何要加锁的函数都要调用_IO_acquire_lock
和_IO_release_lock
这一对宏,其中_IO_release_lock
基本上什么都没干,主要工作放在_IO_acquire_lock
里面
这一对宏构建了一个像这样子的结构
do {IO_filelock// some codeIO_fileunlock
} while(0);
_IO_acquire_lock
一个人差不多就实现了加锁和放锁的所有功能
加锁是怎么实现的呢?使用了_IO_flockfile
这个函数,它会调用_cthreads_flockfile
,而这个函数会调用两个底层的宏,通过原子操作和自旋锁来实现加锁的功能
而放锁的功能使用了GNU的扩展:__attribute__((cleanup))
,只要变量出了定义域,就自动调用_IO_acquire_lock_fct
函数,这个函数名字起得好像是要加锁一样,其实执行的是解锁的功能
为什么要用这个扩展呢?而不是直接放在_IO_release_lock
这个宏里呢?
主要是因为,只要这个变量出了有效区域,不管是以什么形式出去的,都会失效
如果放在_IO_release_lock
里面,则会因为执行到一般的时候出错了或者其他原因而退出
至于自旋锁具体是怎么用的,我觉得不适合今天的研究,毕竟今天的研究甚至没有进入正题:研究putchar是怎么输出的
int
putchar (int c)
{int result;_IO_acquire_lock (stdout);result = _IO_putc_unlocked (c, stdout);_IO_release_lock (stdout);return result;
}
如上,主要工作其实是_IO_putc_unlocked
这个宏在做
这个宏定义在libio/libio.h
中
#define _IO_putc_unlocked(_ch, _fp) __putc_unlocked_body (_ch, _fp)
而__putc_unlocked_body
定义在libio/bits/types/strcut_FILE.h
中
#define __putc_unlocked_body(_ch, _fp) \(__glibc_unlikely ((_fp)->_IO_write_ptr >= (_fp)->_IO_write_end) \? __overflow (_fp, (unsigned char) (_ch)) \: (unsigned char) (*(_fp)->_IO_write_ptr++ = (_ch)))
首先判断是否越界,如果没有越界,则将_IO_write_ptr
赋值并后移一位,于是文件内容就被修改了
如果越界,则调用__overflow
它的声明:
libio/stdio.h:extern int __overflow (FILE *, int);
它定义在libio/genops.c
中
int
__overflow (FILE *f, int ch)
{/* This is a single-byte stream. */if (f->_mode == 0)_IO_fwide (f, -1);return _IO_OVERFLOW (f, ch);
}
具体的实现过程,因为太复杂了,而且体力有限,没有办法继续深究
总结
这次探索给我带来的收获有:
- 学习了
__attribute__((cleanup))
这个扩展 - 进一步熟悉了怎么在浩大的源码里找到自己需要的东西
- 感受到了源码的巧妙的构思,学习到了一些奇妙的方法
GLIBC源码——putchar相关推荐
- 【glibc源码分析】--strcpy.c 字符串复制
strcpy是常用的字符串复制函数,经常在面试中考到.该文件位于glibc源码的string目录中. 在线资源路径: http://www.oschina.net/code/explore/glibc ...
- Linux中C语言标准库glibc源码下载
在这篇文章理清gcc.libc.glibc.libc++.libstdc++的关系,我们大概理解了libc,glibc之间的一些关系. 下面我们就开了解一些Linux中C语言标准库glibc源码. 在 ...
- 【精选】uboot/linux/qt/busybox/opengl/yaffs/lzop/glibc源码下载地址
随着Linux.Android等开源平台和开源项目的推广和应用,它们所带来的开源思维也更加深入的普及到更多领域.现在,非常多的项目都推崇开源分享了(当然,商业盈利模式的应用仍然是要收费的,此处不讨论这 ...
- glibc源码编译安装
文章目录 1.检查已有版本: 2.下载源码: 3.编译源码: 4.再次检查版本: 1.检查已有版本: strings /lib64/libc.so.6 |grep GLIBC_ 2.下载源码: 官方链 ...
- glibc源码分析之utime系列函数
glibc中关于utime的函数有utime,utimes.它们都是系统调用的封装函数. utime函数由脚本生成.生成的.S文件的内容为: #define SYSCALL_NAME utime #d ...
- 源码解析glibc中的pclose与fclose函数
文章目录 pclose 和 fclose 的阻塞问题 测试代码 问题 pclose与fclose的关系 fclose函数的定义 pclose函数的定义 总结 fclose中的block 有嫌疑的地方 ...
- glibc-2.23学习笔记(一)—— malloc部分源码分析
glibc-2.23学习笔记(一)-- malloc部分源码分析 搭建Glibc源码调试环境 1.下载并解压glibc源码 2.配置gdb 3.编译测试程序 第一次调用 源码分析 __libc_mal ...
- Linux线程同步(三)---互斥锁源码分析
先给自己打个广告,本人的微信公众号:嵌入式Linux江湖,主要关注嵌入式软件开发,股票基金定投,足球等等,希望大家多多关注,有问题可以直接留言给我,一定尽心尽力回答大家的问题. 一 源码分析 1.li ...
- socket 源码解析之创建
数据结构 /*** struct socket - general BSD socket* @state: socket state (%SS_CONNECTED, etc)* @flags: soc ...
最新文章
- 行为模式之Observer模式
- jsp文件通常用common_29.jsp-动态生活之用Commons-FileUpload组件控制文件上传
- 二叉树最大深度:给定一个二叉树,找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
- 在软件开发中应用80:20原则
- ▲数据结构 【2012】五2 C++版
- 生活与工作原则-Ray Dalio
- SCM提升供应链管理效率
- 等保测评--网络安全等级保护定级指南
- 机房短信通(SMS Alert System)
- 解决Vue中的对象属性无法实现动态响应
- java 倒计时_Java倒计时实现的三种简单方式
- 解决本地图片上传正常,服务端上传图片报500
- 数论(继续补充)(gcd + lcm + qpow + prime+qmul)
- Metasploit 渗透测试01-背景和功能介绍
- 数据报告 | 2017年中国汽车后市场app研究报告
- Scala编程(一)
- 局域网中资源共享(FTP)
- css中div超出自动换行
- 计算机管理员权限无法粘贴,win10, windows10 复制文件需要管理员权限 才能复制的解决方法...
- 【鸿蒙操作系统刷机教程】你知道安卓(小米,红米,一加,OPPO,vivo,荣耀,魅族手机)怎么刷华为鸿蒙操作系统(HarmonyOS)刷机包吗?