程序的加载链接和库-运行库和系统调用


main函数并不是第一个执行的,ELF会有一个启动的入口函数
大致过程如下
1.操作系统创建好进程之后,把控制权交给程序的入口函数
2.入口函数对运行库和程序环境进行初始化,包括堆,I/O,线程,全局变量构造等
3.入口函数初始化之后,调用main函数,正式开始执行程序主体部分
4.mian函数执行完毕后,返回到入口函数,入口函数进行清理工作,包括全局变量析构,堆销毁,关闭I/O等,然后进行系统调用结束进程

atexit是一个特殊函数,相当于退出程序的hook

void foo(void) {
    printf("byte\n");
}

int main() {
    atexit(&foo);
    printf("main...\n");
}





glic的入口
_start函数,由汇编实现
最终的调用关系是
_start -> libc_start_main -> 
-> _lib_csu_init -> _init -> _do_global_ctors_aux 
->  exit -> _exit
libc_start_main中调用了初始化工作,注册收尾工作,也就是exit()函数

_exit最终是调用系统中断实现的

_do_global_ctors_aux 得到所有的.init段然后挨个执行




从C程序员角度看,数据在线程之间是否私有有如下表格


I/O函数,malloc,信号,数学函数,字符串处理都可能会有并发问题
改进方式
1.使用TLS 线程本地私有 thread local storage,
2.加锁
线程局部存储的方式,就是加一个 __thread关键字,比如
__thread int number;   //定义number为线程局部存储




Linux内核提供了很多系统调用,如读写I/O,创建进程,线程,创建文件,定时器等

linux使用0x80作为中断号,rax寄存器用来传递中断号码,下图是linux提供的中断号


直接调用系统函数性能不好,于是出现了一个中间层,比如glic,由这个库函数来管理最终的系统调用
中断一般有两个属性,中断号,一个中断处理程序
在内核中有一个中断向量表,当中断到来时,CPU会暂停当前执行的代码,根据中断的中断号,在中断向量表中找到对应的中断处理程序并调用它
中断处理程序执行完之后,CPU会继续执行之前的代码





中断时会出现 程序从用户态 到内核态的切换,这时候就需要保存用户态运行时的一些寄存器值,这些值是保存到内核态空间的,等运行结束后会返回



linux内核中有 trap_init函数,是用来初始化中断向量表的


一个fork的调用流程如下

main -> fork -> int 0x80 -> system_call  ,调用流程如下图




linux新的系统使用Inter的新指令调用系统函数
sysenter 和 sysexit 两个指令
通过 ldd libfoo.so 查看结果

        linux-vdso.so.1 =>  (0x00007ffefadec000)
        libc.so.6 => /lib64/libc.so.6 (0x00007f2c1958e000)
        /lib64/ld-linux-x86-64.so.2 (0x00007f2c19b5c000)

通过 cat /proc/[PID]/maps 将vdso那块的内存导出来
dd if=/proc/self/mem of=linux-gate.dso bs=4096 skip=1048574 count=1

再通过 objdump -T linux-gate.dso 来分析这个文件





猜你喜欢

转载自blog.csdn.net/hixiaoxiaoniao/article/details/80910534
今日推荐