深入理解计算机系统(一)

helllo程序从源文件到目标文件的转化是由编译器驱动程序来完成的:
unix> gcc -o hello hello.c
在这里,GCC编译器驱动程序读取源文件hello.c,并把它翻译成一个可执行目标文件hello,翻译过程可分为四个阶段来完成,执行这四个阶段的程序(预处理器,编译器,汇编器,链接器)共同构成了编译系统。
hello源程序经过预处理器(cpp)处理,生成被修改过的源程序,例如#include<stdio.h>则告诉预处理器读取stdio.h的内容,并将其加入到hello文件中,最后生成helllo.i程序。
然后编译器(ccl)将hello.i翻译成hello.s。它包含一个汇编语言程序。然后输出给汇编器。汇编器将其翻译成机器语言指令,并将结果打包成一个叫做可重定位目标程序的格式,并将结果保存在目标文件hello.o中,hello.o是一个二进制文件,它的字节编码是机器语言指令而不是字符,因此用文本编辑器打开后会看到一堆乱码。
最后是链接阶段。因为hello程序调用了printf函数,它是每个C编译器都会提供的标准C库的一个函数,printf函数包含在一个名为printf.o的单独编译好的目标文件,然后将其合并到hello.o程序中,链接器则负责这种合并,结果就得到hello文件,他是一个可执行目标文件,就可以被加载到内存中,由系统执行。
进程
像hello.c在系统上执行,你看到的好像只有这个程序在占用系统资源在运行,看上去就像处理器在一条一条的接受指令,即该程序的代码和数据是系统存储器中唯一的对象。这些假象则是通过进程来实现的。
进程是操作系统正在运行的程序的一种抽象。实际在操作系统中,是可以同时运行多个进程,而每个进程都好像在独占的使用硬件。而并发运行,则是一个进程的指令和另一个指令是交错执行的。一个CPU看上去是在并发的执行多个进程。
操作系统跟踪进程运行所需的所有状态信息。这种状态也叫上下文,它包括许多信息,例如PC(程序计数器)和寄存器文件的当前值,以及主存的内容。单处理器只能执行一个进程的代码,因此在执行多个进程时需要进行上下文切换,将当前进程转移到下一个进程。
线程
通常我们认为一个进程只有一个单一的控制流,但是在现代的系统中,一个进程实际上可以由多个称为线程的执行单元构成,每个线程都运行在进程的上下文中,并共享同样的代码和全局数据,因为网络的发展对并行处理的需求,线程成为越来越重要的编程模型,因为多线程比多进程更容易共享数据。
并发和并行
并发是一个通用的概念,指一个同时具有多个活动的系统。而并行指的是用并发使一个系统运行的更快,总之,并发是使计算机运算的更多,并行是运算的更快。
多处理器系统
一个操作系统有多个处理器,则称为多处理器系统。随着多核处理器和超线程的出现,这种系统变的流行。
超线程
也称为同时多线程,是允许一个CPU执行多个控制流的技术。他涉及CPU某些硬件有多个备份,必须PC和寄存器文件,而其他的只有一份,比如执行浮点运算的单元。常规的处理器需要大约20000个时钟周期做不同线程间的切换,而超线程的处理器可以在单个周期的基础上决定要执行哪个线程。这使得CPU可以更好的利用它的资源。例如一个线程必须等到某些数据被加载到高速缓存中才可以运行,那么CPU就可以执行下一个线程。例如inter Core i7可以让一个核执行两个线程,所以4核的就可以执行8个线程。
信息存储
大多数计算机使用8位的块,或者称为字节。机器级程序将存储器视为一个非常大的字节数组,称为虚拟存储器,所有可能地址的集合称为虚拟地址空间。在C语言中,在前面加0x表示是16进制。

每台计算机都有一个字长。指明整数和指针数据的标称大小,因为虚拟地址是以这样一个字来编码的,所以字长决定的系统参数就是虚拟地址空间的最大大小。今天的计算机大多是32位的,这就限定了虚拟地址空间位4GB.
寻址和字节顺序
在几乎所有的机器上,多字节对象都被存储为连续的字节序列,对象的地址为所使用的字节中最小的地址。例如一个int类型的变量x的地址为0x100,也就是说&x的值为0x100,那么它的4个字节被存储在0x100,0x101,0x102,0x103这几个位置。
int和float表示在二进制数字中的相似
例如12345和12345.0
表示出来有13个相匹配的序列
1 //1000000111001
10001100//1000000111001//0000000000
可以看出来有一样的序列
表示字符串
C语言字符串是以0x00结尾的,即值为0的字符结尾。(在文件读取行的时候最后一个字符为换换行符)
位运算
&表示两个位数字都为1时表示1,否则为0. | 表示只要有1位是1,则运算后就是1. ^表示不同时取1,相同时取0. ~ 表示按位取反。逻辑运算可以实现掩码操作,例如掩码0xFF表示最低的8位为1,位级运算x&0xFF生成一个由x的最低有效位组成的值,而其他字节被设置为0.比如x=0x89ABCDEF,其表达式将得到0x000000EF。
逻辑运算:如果逻辑运算为真,返回1,反之。返回0.
C语言中的移位运算。
x >>4,表示将x转化为二进制后进行向右移4位.
机器为了表示更大的数,可以增加他们所占的字节数,如果4字节,则最大范围为2^32-1。例如4位1111,最大表示的数字为15.因为最小表示为[0000…],但是最大表示为[111111…],计算的话为对2 ^(n-1)依次求和。因此最后得到的最大表示数字为2 ^(n-1)。
补码编码
在计算机中,表示负数利用的是补码表示,在补码表示中,将最高位设置为0或1.1表示负权,它的权重为-2^(n-1),接下来是负数的表示范围,现在最高位表示位负权,[1000],表示最小负数,即-8,后面加的正权表示为0.因此负数表示最大为-2 ^(n-1).同理,正数最大则为[0111],即2 ^(n-1)-1,因此正数的最大范围比负数少一位,这也就平衡了整数可表示的最大范围。2 ^ (n-1) + 2 ^(n-1) -1 = 2 ^n -1.而且通过观察,可以看出1000表示-8,0111表示7,对1000进行反码得到0111为7,要得到8则在0111上加1就可以。即得到相反数的方法为反码加1。
有符号数和无符号数的转化,
C语言规定在转化时不改变位值,只改变解释位的方式。例如原来有符号表示为1000表示-8,转化为有符号的后第一位不再表示负权重,则表示正权重。因此转化后为8.并且有T2U16(-12345)=53191,U2T16(53191)=-12345.这个表示将int 转化为unsigned int 的16位模式
扩展一个数字的位表示
一种常见的运算是在不同字长整数之间的转化,同时又保持数值不变。有两种方法可以实现扩展,有零扩展和符号扩展。零扩展为在开头加0.将一个补码数字转换为一个更大的数据类型可以执行符号扩展,规则是在表示中添加最高有效位的值的副本。例如符号扩展则是在最开头加了16位都是1,表示出来就是0xFFFF,后者无符号扩展则在开头加16个0来扩展,表示为16进制则是0x0000。例如位向量[101]表示值位-4+1=-3.对他进行符号扩展,则为[1101],表示出来则是-3,具体证明过程感兴趣的可以自己推导,过程很简单,这里就不赘述了。无符号扩展则很容易理解了,直接增加它的存储空间,是他可以表达更大的数字。

#include<cstdio>

using namespace std;

typedef unsigned char *byte_pointer;

void show_bytes(byte_pointer start, int len)
{
	int i;
	for (int i = 0; i < len; i++)
	{
		printf(" %.2x", start[i]);
	}
	cout << endl;
}

void show_int(int x)
{
	show_bytes((byte_pointer)&x, sizeof(int));
}

void show_float(float x)
{
	show_bytes((byte_pointer)&x, sizeof(float));
}

void show_pointer(void *x)
{
	show_bytes((byte_pointer)&x, sizeof(void *));
}

int main()
{
	short sx = -12345;		//-12345
	unsigned short usx = sx;	//53191
	int x = sx;		//-12345
	unsigned ux = usx;	//53191
	printf("sx = %d", sx);
	show_bytes((byte_pointer)&sx, sizeof(short));
	printf("usx = %u", usx);
	show_bytes((byte_pointer)&usx, sizeof(unsigned short));
	printf("x = %d", x);
	show_bytes((byte_pointer)&x, sizeof(int));
	printf("ux = %u", ux);
	show_bytes((byte_pointer)&ux, sizeof(unsigned));
}

结果为:

sx = -12345 c7 cf
usx = 53191 c7 cf
x = -12345 c7 cf ff ff
ux = 53191 c7 cf 00 00

可以看出符号转化过程中没有变化位值,而是变化了表达方法。符号扩展时无符号的会在末尾加上16个1,16进制表示出来就是0xFFFF,而无符号的表达则是在末尾加16个1,16进制表示出来就是0x0000.
截断数字
假如不用额外的位来扩展一个数值,而是减少表示一个数字的位数。例如下面的情况:

int x = 53191;
	short sx = (short)x;
	int y = sx;
	printf("x = %d\t", x);
	show_bytes((byte_pointer)&x, sizeof(int));
	printf("sx = %d\t", sx);
	show_bytes((byte_pointer)&sx, sizeof(short));
	printf("y = %d\t", y);
	show_bytes((byte_pointer)&y, sizeof(int));

输出为

x = 53191        c7 cf 00 00
sx = -12345      c7 cf
y = -12345       c7 cf ff ff

可以看出对代码进行了截断。并且在对short扩展的时候在后面补了0xFFFF。
最后加上一个大端法和小端法。无论是大端法还是小端法,计算机存储的顺序都是从低地址到高地址。大端法是首先取高字节的数据存在低地址,小端法则相反。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值