📝 概述
程序的开发与执行涉及计算机的各个不同层面。我们在编写好程序后通常是被称为用户程序(user program)或应用程序(application program)。
我们以最常使用,也是很经典的C源程序为例,逐步刨析程序的执行过程,源程序如何成功再计算机上运行,中间到底有多少细节。
为了更好的逐步看清执行过程,我们将在Linux系统环境下进行C程序的预处理、编译、汇编、连接,最终将生成可执行目标文件。GCC编译驱动程序对程序进行处理。
📝 连接Linux
随着技术不断发展,对于萌新小白来讲,利用SSH来连接到远端Linux系统并没有想象中那么复杂。
Secure Shell(SSH) 是由 IETF(The Internet Engineering Task Force) 制定的建立在应用层基础上的安全网络协议。它是专为远程登录会话(甚至可以用Windows远程登录Linux服务器进行文件互传)和其他网络服务提供安全性的协议,可有效弥补网络中的漏洞。通过SSH,可以把所有传输的数据进行加密,也能够防止DNS欺骗和IP欺骗。还有一个额外的好处就是传输的数据是经过压缩的,所以可以加快传输的速度。目前已经成为Linux系统的标准配置。
SSH只是一种协议,存在多种实现,既有商业实现,也有开源实现。
显然我们是想要更加简单易用的方式编写test.c程序,并且可以通过gcc命令编译我们的源程序。下面将分享一种傻瓜式的操作方法,我们使用Visual Studio Code来连接远程的Linux主机。
下面以Windows用户为例(作者本人也是虚拟机选手)
-
安装VMware
-
下载好Centos 7 镜像文件或者 Ubuntu 20以上版本(当然根据自身情况选择版本)
-
在VMware里新建虚拟机,使用Centos或者Ubuntu都可以(这里我们使用Centos为例)
注意(以下几点务必设置准确无误):
-
虚拟机网络适配器选择NAT
-
进入系统后打开远程连接(其实在安装好Centos后系统会默认打开远程连接)
🌟 配置VSCode中的SSH文件
在拓展商店里搜索Remote-SSH插件,并安装
安装好后在左侧窗口会显示远程资源管理器,点击进入,在上方的命令窗口输入>Remote-SSH
选择连接主机,ssh root@ip地址
得到一个要连接的目标主机,接下来命令窗口将自动提示配置SSH文件
到这里不需要其他任何操作,进入config
配置文件:
Host 主机名(无所谓,只是标识这份创建的主机)
HostName 服务器的IP地址
User root 你的Linux用户名称(注意这里务必准去无误,且必填)
Port 22 连接的端口号(这里不需要更改,默认就行,当然如果考虑到安全可以根据需求设定)
下面进入Centos系统,打开终端
- 安装openssh-server服务(注意这一步可以忽略,因为现在一般来讲系统是默认安装好的)
systemctl start sshd.service #启动ssh服务
systemctl restart sshd.service #重启ssh服务
systemctl enable sshd.service #开机ssh自启
-
得到Linux主机的IP地址
在终端中输入命令:
ifconfig
第一栏里找到下图所在位置
inet即为当前主机的IP地址,记下来并写到VSCode里的HostName后面
在VMWare的Centos右击编辑配置SSH,将里面的端口设置为默认的22,用户名根据自己创建虚拟机
的情况设定,这是要与VSCode里User选项填写一致。
🌟 建立连接
找到右侧新建连接,接下来在新窗口里底部终端将会提示输入密码(这里密码不是Centos的锁屏密
码,是带有字母数字符号的用户密码,这是在安装Centos完成后强制需要你设定的)
到这里就说明连接成功了,那么左下角也将会显示你连接到的主机名称。之后回到资源管理器,就已
经提示连接到了远程服务器,打开文件夹就是在你虚拟机里的Centos系统的文件夹,一般选择把整个
根目录都打开,便于后续程序编写执行。
📝 创建文件
在右侧加号+ 新建一个终端
在VSCode里创建一个test.c文件并保存
如果我们直接将test.c文件编译一步到位,那么在命令行输入如下指令:
gcc test.c
结果会生成一个a.out文件,这是一个二进制文件,使用vim来打开看:vim a.out,结果如下:
📝从源程序到可执行程序
!!!对于整个过程详解如下:
当我们在VS里看到LNK报错时,一般都是链接阶段出现错误。
- 预处理 选项 gcc -E test.c -o test.i
预处理完成之后就停下来,预处理之后产生的结果都放在test.i文件中。(预处理完成后其本质还是C代码)
- 编译 选项 gcc -S test.c
编译完成之后就停下来,结果保存在test.s中。(形成汇编代码)
.file "test.c"
.section .rodata
.LC0:
.string "hello world "
.text
.globl main
.type main, @function
main:
.LFB0:
.cfi_startproc
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl $.LC0, %edi
call puts
movl $0, %eax
popq %rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size main, .-main
.ident "GCC: (GNU) 4.8.5 20150623 (Red Hat 4.8.5-44)"
.section .note.GNU-stack,"",@progbits
- 汇编 gcc -c test.c
汇编完成之后就停下来,结果保存在test.o中。
(读取查看elf格式的文件)
readelf test.o -s
📝 运行环境
最终的可执行文件保存在磁盘上,可以以某种方式启动该文件并执行。
程序执行的过程:
-
程序必须载入内存中。在有操作系统的环境中:一般这个由操作系统完成。在独立的环境中,程序的载入必须由手工安排,也可能是通过可执行代码置入只读内存来完成。
-
程序的执行便开始。接着便调用main函数。
-
开始执行程序代码。这个时候程序将使用一个运行时堆栈(stack),存储函数的局部变量和返回
地址。程序同时也可以使用静态(static)内存,存储于静态内存中的变量在程序的整个执行过程
一直保留他们的值。 -
终止程序。正常终止main函数;也有可能是意外终止。