线程-函数调用

本文介绍了函数调用的原理,从C语言、汇编和编译器的角度分析了函数调用过程,强调了线程调用实际上就是函数调用的并行执行。通过理解栈基址寄存器和栈顶寄存器的作用,以及编译器如何处理函数调用,为理解线程的实现打下基础。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

本专题是手动实现一个“线程”, 写这个项目的起因主要是为了为学习操作系统的人而准备的练手和巩固线程和进程调用的知识. 而这个小项目是从我所写的玩具操作系统中抽离出来再加以改善的, 所以并不乎涉及更加底层的东西, 毕竟我也不会.

前言

在讲解小项目之前要先明白什么是函数调用? 函数又是怎么实现调用并返回到调用函数的函数体中?

可能你不明白为什么线程会跟函数调用有关, 那是因为我们实现的功能只是并行的, 说白就是函数调用, 并不是并发.

小概念

并行 : 宏观上是并发, 微观是串行. 比如3(1, 2, 3行, 每行表示一个进程)个进程, 但只有一个CPU, 3个进程很快的进行来回切换, 这样我们感觉就是每个进程都在同时的运行(这是宏观感受), 其实他们是每个进程用一段时间的CPU(这个微观).

-----                   ------------                            --------
     -----     ---------                      ---------
          -----                     ----------         ---------

并发 : 多个进程使用多个CPU, 真正的同时执行.

函数调用

通过以上应该明白并行, 而我们实现的就是函数不停的被调用, 让人感受是不停切换. 现在我们就来着手分析一个函数调用的过程吧.

1. 从c语言角度来看

以下的例子来看, 函数调用时会先从main跳转到fun中区执行, 执行结束后在回到main中执行剩下的指令.

void fun(int i)
{
}

int main()
{
	fun(0);
 
    exit(EXIT_SUCCESS);
}
2. 从汇编看

执行gcc -S func.c将c代码转为汇编代码, 如果看不懂AT&T汇编格式可以执行gcc -S -masm=intel func.c转为intel汇编就行了.

我将汇编代码简化一下, 只留重点.

  1. main函数中执行call之指令, 调用fun函数, 在函数执行之前现将rbp(栈基址寄存器)压入栈中保存, 然后将函数栈的栈顶rsp(栈顶寄存器)赋值给rbp .

  2. 我们传入了一个了参数, 可以看到edi存放的是参数的值, 将它存放在当前栈-4的位置, 这样就实现了函数参数的传递了.

  3. 以上函数执行前的准备工作执行完后才开始真正的执行函数体中的操作, 但是这里我们什么都没有写, 所以指令只有一条nop(空指令).

  4. 函数退出 : 将函数最开始压入栈中的rbp弹栈再赋值给rbp, 这样rbp又重新指向main了.

所以函数被调用前将main函数的地址压栈, 函数执行完后再将main地址弹栈, 这样就实现一个完整的函数调用, 而且还能回到调用之前的函数.

rbp : 栈基址寄存器

rsp : 栈顶寄存器

fun:
.LFB2:
	.cfi_startproc
	pushq	%rbp
	movq	%rsp, %rbp
	
	movl 	%edi, -4%rbp	; 传入一个参数
	
	nop
	popq	%rbp
	ret

main:
.LFB3:
	call	fun
	movl	$0, %edi
	call	exit@PLT
3. 从编译器来看

这里做一个简单的了解就行了.

在编译期间, 编译器将每个文件声明函数名放在该文件的ELF中, 链接时将函数名与函数体关联链接, 当检测到相同函数名时编译器和链接器并不知道怎么处理, 所以相同声明的函数签名就会报错.

当没有冲突的函数时, 就会重定位ELF中函数的信息, 当检测到该函数被调用就会去查ELF表中该函数名是否存在, 如果存在再去定位函数所在的内存地址, 最后将定位到的地址替换函数名即可.

输入objdump a.out -x命令你就可以看到该程序所有可用的头部信息, 包含符号表, 重定位入口. 我这里的fun函数的信息是这样的, 存放在ELF中的.text代码段中.

00000000000006b0 g     F .text  0000000000000007              fun

小结

本节最重要的是明白汇编层次的函数调用是怎么实现的, 明白这些才容易理解下面我们所写线程调用的过程. 而汇编层次的函数调用涉及到的就是将rbp(栈基址寄存器)压栈, 最后弹栈恢复现场就实现了函数调用.

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值