最近开始系统学习操作系统相关的知识,为了及时作总结,这里会记录整个学习的笔记。学习基于的教材是《深入理解计算机系统第三版》,同时参考南京大学袁春风老师的《计算机系统基础》的视频课。
- C语言程序举例
举一些令人觉得反常的例子。
案例1:建立main.c文件,内容如下
#include
int main()
{
/*case 1*/
printf("%d\n", -2147483648 < 2147483647);
/*case 2*/
int i = -2147483648;
printf("%d\n", i < 2147483647);
/*case 3*/
printf("%d\n", -2147483647 - 1 < 2147483647);
return 0;
}
ISO C90标准下,在32位系统上,上面三个C表达式的结果是什么?
我的机器是linux64位的,我现在把main.c编译链接成一个32位的基于C90的可执行文件,输入:
gcc -m32 -std=c90 main.c
编译的时候会有告警:
main.c: In function ‘main’:
main.c:5:5: warning: this decimal constant is unsigned only in ISO C90
printf("%d\n", -2147483648 < 2147483647);
^
main.c:8:5: warning: this decimal constant is unsigned only in ISO C90
int i = -2147483648;
^
运行后结果为:
0
1
1
实际上,上面结果有问题的原因就是告警提示的,要具体理解该问题需要知道:
编译器如何处理字面量;
高级语言中运算规则;
高级语言与指令之间的对应;
机器指令的执行过程;
机器级数据的表示和运算。
案例2:修改main.c文件,内容如下
#include
int sum (int a[], unsigned len)
{
int i, sum =0;
for (i =0;i<=len-1;i++) {
sum += a[i];
}
return sum;
}
int main()
{
int array[]={1,2,3};
sum(array,0);
return 0;
}
编译运行后结果为:
aitian@aitian-CW65S:~/at/OS/week1$ gcc main.c
aitian@aitian-CW65S:~/at/OS/week1$ ./a.out
段错误 (核心已转储)
当参数len为0时,返回值应该是0,但是在机器上执行时,却发生访存异常。但当len为int型时则正常。理解该问题需要知道:
高级语言中运算规则;
机器指令的含义和执行;
计算机内部的运算电路;
异常的检测和处理;
虚拟地址空间。
案例3:修改main.c文件,内容如下
#include
int main()
{
// case 1
int x = 65535;
int y = x*x;
printf("y=%d\n", y);
// case 2
int z = -2147483648;
int m = -2147483649;
printf("%d,%d\n",z > m,-z < -m);
return 0;
}
编译运行后结果为:
aitian@aitian-CW65S:~/at/OS/week1/exam2$ gcc main.c
main.c: In function ‘main’:
main.c:12:13: warning: overflow in implicit constant conversion [-Woverflow]
int m = -2147483649;
^
aitian@aitian-CW65S:~/at/OS/week1/exam2$ ./a.out
y=-131071
0,1
若x和y为int型, 当x=65535时, y=x*x; y的值为多少?y= -131071。
现实世界中,x的平方≥0,但在计算机世界并不一定成立。
对于任何int型变量x和y,(x>y) == (-x<-y) 总成立吗?当x=-2147483648,y任意(除-2147483648外)时不成立。
Why?
在现实世界中成立,但在计算机世界中并不一定成立。
理解该问题需要知道:
机器级数据的表示;
机器指令的执行;
计算机内部的运算电路。
案例4:建立建立文件,分别是main.c和p1.c
aitian@aitian-CW65S:~/at/OS/week1/exam3$ cat main.c
#include
int d = 100;
int x = 200;
int main()
{
p1();
printf("d=%d,x=%d\n",d,x);
return 0;
}
double d;
void p1()
{
d=1.0;
}
编译运行后结果为:
aitian@aitian-CW65S:~/at/OS/week1/exam3$ gcc main.c p1.c -o main
main.c: In function ‘main’:
main.c:6:5: warning: implicit declaration of function ‘p1’ [-Wimplicit-function-declaration]
p1();
^
/usr/bin/ld: Warning: alignment 4 of symbol `d' in /tmp/ccMboWJv.o is smaller than 8 in /tmp/ccJppd2b.o
aitian@aitian-CW65S:~/at/OS/week1/exam3$ ./main
d=0,x=1072693248
理解该问题需要知道:
机器级数据的表示;
数据的大端 变量的存储空间分配;
小端存储方式;
链接器的符号解析规则。
案例5:建立main.c如下
aitian@aitian-CW65S:~/at/OS/week1$ cat main.c
#include
#include
int copy_array(int * array,int count)
{
int i;
printf ("%d\n", count*sizeof(int));
int*myarray=(int*)malloc(count*sizeof(int));
if (myarray == NULL) {
return -1;
}
for (i = 0;i< count ;i++) {
printf ("index = %d\n",i);
myarray[i] = array[i];
}
return count;
}
int main()
{
int array[4400]={0};
copy_array(array, 1073741824+1);
return 0;
}
编译后运行结果为:
aitian@aitian-CW65S:~/at/OS/week1$ gcc main.c
main.c: In function ‘copy_array’:
main.c:7:13: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘long unsigned int’ [-Wformat=]
printf ("%d\n", count*sizeof(int));
^
aitian@aitian-CW65S:~/at/OS/week1$ ./a.out
4
1
2
3
4
...(省略一些输出)
index = 6854
index = 6855
index = 6856
index = 6857
index = 6858
index = 6859
index = 6860
段错误 (核心已转储)
当参数count很大时,则countsizeof(int)会溢出。如count=2的30次方 +1时,countsizeof(int)=4。从而会导致 堆(heap)中大量数据被破坏!
理解该问题需要知道:
乘法运算及溢出;
虚拟地址空间;
存储空间映射。
案例6:新建main.c如下
#include
int main()
{
{
int a = 0x80000000;
int b = a/-1;
printf("result = %d\n", b);
}
{
int a = 0x80000000;
int b = -1;
int c = a/b;
printf("result = %d\n", c);
}
return 0;
}
运行编译后结果为:
aitian@aitian-CW65S:~/at/OS/week1/exam5$ gcc -g main.c
aitian@aitian-CW65S:~/at/OS/week1/exam5$ ./a.out
result = -2147483648
浮点数例外 (核心已转储)
运行结果为“Floating poin为什么两者结果不同!
后者显示浮点数的异常,显然CPU检测到了溢出异常
objdump反汇编代码,负指令neg,得知除以 被优化成取-1故未发生除法溢出.
a/b用除法指令IDIV实现,但它不生成OF标志,那么如何判断溢出异常的呢?
实际上是“除法错”异常#DE(类型0)Linux中,对#DE类型发SIGFPE信号。
输入objdump -S a.out
理解该问题需要知道:
编译器如何优化
机器指令的含义和执行
机器级数据的表示
计算机内部的运算电路
除法错异常的处理
案例7:建立main.c
#include
int main()
{
double a = 10;
printf("a = %d\n", a);
return 0;
}
先编译成32位机器上运行的程序,然后编译成64位机器上运行的程序
aitian@aitian-CW65S:~/at/OS/week1/exam6$ gcc -m32 main.c
main.c: In function ‘main’:
main.c:6:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
printf("a = %d\n", a);
^
aitian@aitian-CW65S:~/at/OS/week1/exam6$ ./a.out
a = 0
aitian@aitian-CW65S:~/at/OS/week1/exam6$ gcc main.c
main.c: In function ‘main’:
main.c:6:12: warning: format ‘%d’ expects argument of type ‘int’, but argument 2 has type ‘double’ [-Wformat=]
printf("a = %d\n", a);
^
aitian@aitian-CW65S:~/at/OS/week1/exam6$ ./a.out
a = 1774780312
aitian@aitian-CW65S:~/at/OS/week1/exam6$ ./a.out
a = -150387448
在IA-32上运行时,打印结果为a=0
在x86-64上运行时,打印出来的a是一个不确定值
IEEE 理解该问题需要知道:
754 的表示;
X87 FPU的体系结构;
IA-32和x86-64中过程;
调用的参数传递;
计算机内部的运算电路。
案例8:建立main.c
#include
double fun(int i)
{
volatile double d[1]={3.14};
volatile long int a[2];
a[i] = 1073741824;// Possibly out of bounds
return d[0];
}
int main()
{
for (int i =0;i<=5;i++) {
printf("fun(%d)->%lf\n", i, fun(i));
}
return 0;
}
编译运行后:
aitian@aitian-CW65S:~/at/OS/week1/exam7$ gcc main.c
aitian@aitian-CW65S:~/at/OS/week1/exam7$ ./a.out
fun(0)->3.140000
fun(1)->3.140000
fun(2)->3.140000
*** stack smashing detected ***: ./a.out terminated
已放弃 (核心已转储)
理解该问题需要知道:
机器级数据的表示;
过程调用机制;
栈帧中数据的布局。
案例9:建立main.c
#include
#include
#define NUM 5000
#define COUNT 20
void copyij (int src[NUM][NUM],int dst[NUM][NUM])
{
int i,j;
for (i = 0; i < NUM; i++)
for ( j = 0; j < NUM; j++)
dst[i][j] = src[i][j];
}
void copyji (int src[NUM][NUM],int dst[NUM][NUM])
{
int i,j;
for ( j = 0; j < NUM; j++)
for (i = 0; i < NUM; i++)
dst[i][j] = src[i][j];
}
int main()
{
int* src = (int*)malloc(NUM*sizeof(int));
for (int i = 0;i < NUM;i++) {
src[i] = (int*)malloc(NUM*sizeof(int));
}
int* dst = (int*)malloc(NUM*sizeof(int));
for (int i = 0;i < NUM;i++) {
dst[i] = (int*)malloc(NUM*sizeof(int));
}
time_t t1 = time(NULL);
for (int i =0;i < COUNT;i++) {
copyij(src,dst);
}
time_t t2 = time(NULL);
printf("copyij cost %lds\n",t2-t1);
time_t t3 = time(NULL);
for (int i =0;i < COUNT;i++) {
copyji(src,dst);
}
time_t t4 = time(NULL);
printf("copyji cost %lds\n",t4-t3);
return 0;
}
编译运行后:
aitian@aitian-CW65S:~/at/OS/week1/exam8$ gcc main.c
aitian@aitian-CW65S:~/at/OS/week1/exam8$ ./a.out
copyij cost 1s
copyji cost 12s
以上两个程序功能完全一样,算法完全一样,因此,时间和空间复杂度完全一样,执行时间一样吗?
空间复杂度一样,但是第一个子函数比第二个子函数快很多倍。
理解该问题需要知道:
数组的存放方式
Cache机制
访问局部性
案例10:
这个要用老的gcc去编译,我没有下载对应的版本。
案例11:建立main.c
#include
void main()
{
int a = 10;
double *p=(double*)&a;
printf("%lf\n", *p);
printf("%lf\n", (double)a);
}
运行后结果为:
aitian@aitian-CW65S:~/at/OS/week1/exam9$ ./a.out
-0.000000
10.000000
理解该问题需要知道:
数据的表示
编译(程序的转换)
局部变量在栈中的位置
使用gdb进入a.out,然后查看汇编
aitian@aitian-CW65S:~/at/OS/week1/exam9$ gdb a.out
(gdb) disassemble /m main
Dump of assembler code for function main:
3 {
0x0000000000400596 <+0>: push %rbp
0x0000000000400597 <+1>: mov %rsp,%rbp
0x000000000040059a <+4>: sub $0x30,%rsp
=> 0x000000000040059e <+8>: mov %fs:0x28,%rax
0x00000000004005a7 <+17>: mov %rax,-0x8(%rbp)
0x00000000004005ab <+21>: xor %eax,%eax
4 int a = 10;
0x00000000004005ad <+23>: movl $0xa,-0x14(%rbp)
5 double *p=(double*)&a;
0x00000000004005b4 <+30>: lea -0x14(%rbp),%rax
0x00000000004005b8 <+34>: mov %rax,-0x10(%rbp)
6 printf("%lf\n", *p);
0x00000000004005bc <+38>: mov -0x10(%rbp),%rax
0x00000000004005c0 <+42>: mov (%rax),%rax
0x00000000004005c3 <+45>: mov %rax,-0x28(%rbp)
0x00000000004005c7 <+49>: movsd -0x28(%rbp),%xmm0
0x00000000004005cc <+54>: mov $0x400694,%edi
0x00000000004005d1 <+59>: mov $0x1,%eax
0x00000000004005d6 <+64>: callq 0x400470
7 printf("%lf\n", (double)a);
0x00000000004005db <+69>: mov -0x14(%rbp),%eax
0x00000000004005de <+72>: pxor %xmm0,%xmm0
0x00000000004005e2 <+76>: cvtsi2sd %eax,%xmm0
0x00000000004005e6 <+80>: mov $0x400694,%edi
0x00000000004005eb <+85>: mov $0x1,%eax
0x00000000004005f0 <+90>: callq 0x400470
...(后面的省略)
关键在于命令movsd和命令cvtsi2sd的区别。
movsd:传送一个双字
cvtsi2sd命令把源存储器1个双字有符号整数变成1个双精度浮点数,结果送入目的寄存器的低64位,高64位不变.
-
什么是计算机系统
333.png
-
计算机系统组成与基本功能
3.1 现代计算机的原型:冯·诺依曼结构
222.png
3.2 现代计算机结构模型
3.3 计算机是如何工作的
4.程序开发和执行过程简介
4.1 程序开发语言
4.2 程序转换
-
编程语言和计算机体系层次
1.png