线程-信号

1,线程与信号

  1. 每个线程都有自己的信号屏蔽字,但是信号的处理是先进程中所有线程共享的。这就意味着单个线程可以阻止某些信号,但当某个线程修改了与某个给定信号相关的处理行为以后,所有的线程都必须共享这个处理行为的改变。
  2. 进程中的信号是递送给单个线程的。(Signals are delivered to a single thread in the process. If the signal is related to a hardware fault, the signal is usually sent to the thread whose action caused the event. Other signals, on the other hand, are delivered to an arbitrary thread.)对于该句的理解是:因为进程中所有线程共享信号的处理,所以发送给任意进程(只要该进程没有屏蔽该信号),对该信号的处理都是相同的。

Makefile

1,makefile的规则

1
2
3
4
5
6
7
8
9
target ...: prerequisites ...
cmd
...
...
其中,target可以是一个obj file,也可以是一个执行文件,还可以是一个label.
prerequisites:为生成该target所依赖的文件或target
cmd(必须以一个Tab键作为开头):为target要执行的命令
注:prerequisites中如果有一个以上的文件比target文件要新的话,cmd所定义的命令就会被执行。

IPC-标识符重用

1,槽位使用情况序列号

ipc_perm结构中含有一个名为seq的变量,是一个槽位使用情况序列号。该变量是一个由内核为系统中每个潜在的IPC对象维持的计数器。每当删除一个IPC对象时,内核就递增相应槽位的使用情况序列号,若溢出则循环回0.

简单理解就是: 存在ipc_perm[max],当创建ipc对象时,在ipc_perm数组中查找第一个ipc_perm元素为空,记为ipc_perm[i].当删除ipc_perm[i]对应的ipc时,ipc_perm[i].seq++,下次再次创建ipc对象时,假设ipc_perm[i]元素为空,则返回ipc id为i+ipc_perm[i].seq*max.
注:槽位使用情况序列号是一个跨进程保持的内核变量。

2,示例代码

2.1 创建ipc,然后立即删除该ipc

对应的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*************************************************************************
> File Name: p26_1.c
> Author: qinchao
> Mail: 1187620726@qq.com
> Created Date:2017-04-07 Time:11:02:25.
************************************************************************/
#include<stdio.h>
#include<sys/msg.h>
#include<sys/types.h>
int main()
{
int i,msgid;
for(i=0;i<10;i++)
{
msgid=msgget(IPC_PRIVATE,0666|IPC_CREAT);
printf("msgid=%d\n",msgid);
msgctl(msgid,IPC_RMID,NULL);
}
return 0;
}

对应的结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
bogon:unp qinchao$ gcc p26_1.c -o p26_1
bogon:unp qinchao$ ./p26_1
msgid=196608
msgid=262144
msgid=327680
msgid=393216
msgid=458752
msgid=524288
msgid=589824
msgid=655360
msgid=720896
msgid=786432
bogon:unp qinchao$

即:创建ipc1之后,ipc_perm[0]维护该ipc对象的相关信息,然后删除ipc1,ipc_perm[0].seq++;当创建ipc2时,仍然是ipc_perm[0]维护ipc2相关信息。

C++对象模型-关于对象

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,关于对象

从这篇博客开始真正介绍C++对象模型,前边BB了那么多没用的,终于开始了C++对模型的分析。关于C++对象模型的介绍,我将根据《深度探索C++对象模型》这本书,其书中的每一章,对应一篇博客,博客内容为自己对这本书的理解和补充吧。

1.1C语言中的struct

我们知道,C语言是面向过程的,即数据和处理数据的函数时分开的,也就是说,struct中不能包含函数(当然也不能包含static变量)。但是我们可以在struct中声明指向函数的指针来模拟数据和处理数据的函数指针。其中,需要指出的是,c语言中struct变量的数据成员的访问方式都是基于该结构体变量的首地址的偏移量来访问的(同样适用于C++中的class/struct)。
即,见如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<stdio.h>
struct point3d;
typedef void (*initfun)(struct point3d*);
struct point3d{
float x;
float y;
float z;
initfun init;//指向初始化数据函数的指针
};
void initp(struct point3d* p3d) //struct point3d变量初始化函数
{
p3d->x=1;
p3d->y=2;
p3d->z=3;
printf("%f %f %f\n",p3d->x,p3d->y,p3d->z);
return;
}
int main()
{
struct point3d pd;
pd.init=initp;//将initp函数地址赋给结构体中指向函数的指针pd.init。
pd.init(&pd);
}

C++对象模型-ObjectSliced

开发环境

gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

1, ObjectSliced

当一个base class object 被直接初始化(copy ctor)/赋值(operator =)为一个derived class object 时,derived object的base 部分会被切割(sliced)以塞入base type内存中,derived type将没有留下任何蛛丝马迹(即:base class object 的vptr不会被derived class object的vptr替换)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<iostream>
using namespace std;
class ZooAnimal{
public:
ZooAnimal(int l):loc(l)
{
}
virtual void print()
{
cout<<"ZooAnimal";
}
private:
int loc;
};
class Bear : public ZooAnimal{
public:
Bear(int c, int l):ZooAnimal(l), cell(c)
{
}
private:
void print()
{
cout<<"Bear";
}
private:
int cell;
};
int main()
{
Bear b(1, 2);
ZooAnimal z = b;
z.print();
}

以上代码对应的汇编如下(g++ -S -m32 p27.cc):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
_ZN9ZooAnimalC2ERKS_: //ZooAnimal默认copy ctor
.LFB1031:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
movl $_ZTV9ZooAnimal+8, %edx //edx=ZooAnimal.vptr
movl 8(%ebp), %eax //eax=[ebp+8],即eax=z.this
movl %edx, (%eax) //设置z的vptr
movl 12(%ebp), %eax //eax=b.this
movl 4(%eax), %edx //edx = b.loc
movl 8(%ebp), %eax //eax=z.this
movl %edx, 4(%eax) //z.loc = b.loc
nop
popl %ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc

C++对象模型-Non-Staitc成员函数

开发环境

gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

1, non-static成员函数调用过程

class X定义了一个virtual function foo:函数调用过程(栈帧)class X定义了一个virtual function foo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include<iostream>
using namespace std;
//不分析virtual dctor
class X{
public:
virtual void foo();
private:
int _x;
};
void X::foo()
{
cout<<_x<<endl;
}
X foobar()
{
X xx;
X *px = new X;
xx.foo();
px->foo();
delete px;
return xx;
}
int main()
{
foobar();
return 0;
}

该程序对应的汇编代码如下(g++ -S -m32 p13.cc):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
_ZN1X3fooEv: //X::foo()
.LFB1021:
.cfi_startproc
pushl %ebp //ebp压栈([esp]=ebp, esp=esp-4)
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp //ebp=esp
.cfi_def_cfa_register 5
subl $8, %esp //esp=esp-8
movl 8(%ebp), %eax //eax=[ebp+8],即eax=this
movl 4(%eax), %eax //eax=[eax+4], 即eax=this->_x
subl $8, %esp //esp=esp-8
pushl %eax //[esp]=eax, esp=esp-4, 即this->_x压栈
pushl $_ZSt4cout //std::cout压栈
call _ZNSolsEi //operator<< 对应两步操作:1,retAddr压栈 2,IP=operator<< func
addl $16, %esp
subl $8, %esp
pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
pushl %eax
call _ZNSolsEPFRSoS_E
addl $16, %esp
nop
leave //ebp=esp, pop ebp
.cfi_restore 5
.cfi_def_cfa 4, 4
ret //pop IP
.cfi_endproc
.LFE1021:
.size _ZN1X3fooEv, .-_ZN1X3fooEv
.section .text._ZN1XC2Ev,"axG",@progbits,_ZN1XC5Ev,comdat
.align 2
.weak _ZN1XC2Ev
.type _ZN1XC2Ev, @function

C++对象模型-Default Constructor

0.测试环境

gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

1, 默认构造函数在编译器需要的时产生出来

在如下片段的代码中, 通过分析汇编代码,发现并不会合成出来一个Default Constructor函数,因为如下代码是代码逻辑需要一个默认构造函数来初始化val和pnext数据成员。而不是编译器需要合成一个Default Constructor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<iostream>
using namespace std;
class Foo{
public:
int val;
Foo *pnext;
};
void foobar()
{
Foo bar;
if(bar.val || bar.pnext) //代码逻辑需要一个Default Constructor来初始化val和pnext数据成员
{
cout<<bar.val<<endl;
}
}

以上代码对应的汇编代码如下(g++ -S -m32 p39.cc):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
_Z6foobarv: //foobar(), c++filt _Z6foobarv
.LFB1021:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl -16(%ebp), %eax //eax=[ebp-16],即eax = bar.val ,NOTICE:NO Default Constructor is Called
testl %eax, %eax //eax&eax
jne .L2 // 如果testl的结果非0,jmp .L2 ,jne means jmp if not equal
movl -12(%ebp), %eax //eax=[ebp-12], 即eax=bra.pnext
testl %eax, %eax
je .L4 //如果testl的结果为0, jmp .L4
.L2:
movl -16(%ebp), %eax //eax=[ebp-16], 即bar.val
subl $8, %esp
pushl %eax
pushl $_ZSt4cout
call _ZNSolsEi
addl $16, %esp
subl $8, %esp
pushl $_ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
pushl %eax
call _ZNSolsEPFRSoS_E
addl $16, %esp
.L4:
nop
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc

C++对象模型-引用

0.测试环境

gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

1.引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Widget{
public:
virtual void show()=0;
int _w;
};
class Bell:public Widget{
public:
void show()
{
cout<<"Bell Bell..."<<endl;
}
};
void test()
{
Bell b;
Widget &w =b;
w.show();
}

test函数对应的汇编代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
_Z4testv:
.LFB1022:
.cfi_startproc
pushl %ebp
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp
.cfi_def_cfa_register 5
subl $24, %esp
movl %gs:20, %eax
movl %eax, -12(%ebp)
xorl %eax, %eax
subl $12, %esp
leal -20(%ebp), %eax //eax=ebp-20,即eax=b.this
pushl %eax //b首地址压栈
call _ZN4BellC1Ev //Bell::Bell()
addl $16, %esp
leal -20(%ebp), %eax //eax=b.this
movl %eax, -24(%ebp) //Widget &w=b
movl -24(%ebp), %eax //eax=&w
movl (%eax), %eax //eax=vptr4Bell
movl (%eax), %eax //eax = vptr4Bell[0]
subl $12, %esp
pushl -24(%ebp) //w.this压栈
call *%eax //virtual机制,w.show()
addl $16, %esp
nop
movl -12(%ebp), %eax
xorl %gs:20, %eax
je .L5
call __stack_chk_fail
.L5:
leave
.cfi_restore 5
.cfi_def_cfa 4, 4
ret
.cfi_endproc

C++对象模型-virtual继承

0.测试环境

gcc version 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.10)

1.虚继承

1
2
3
4
5
6
7
8
9
10
11
12
X
/ \
virtual / \virtual
/ \
A B
\ /
\ /
\ /
C
`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
class X{
public:
int i;
virtual void show()
{
cout<<"X"<<endl;
}
virtual ~X()
{
cout<<"~X";
}
};
class A:public virtual X{
public:
int j;
virtual void show()
{
cout<<"A"<<endl;
}
virtual ~A()
{
cout<<"~A";
}
};
class B:public virtual X{
public:
int d;
virtual void show()
{
cout<<"B"<<endl;
}
virtual ~B()
{
cout<<"~B";
}
};
class C:public A, public B{
public:
int k;
virtual void show()
{
cout<<"C"<<endl;
}
virtual ~C()
{
cout<<"~C";
}
};
void test()
{
B *b= new C;
b->show();
delete b;
}

1.1 test()函数对应的汇编代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
_Z4testv: //test
.LFB1041:
.cfi_startproc
pushl %ebp //ebp压栈
.cfi_def_cfa_offset 8
.cfi_offset 5, -8
movl %esp, %ebp //ebp=esp
.cfi_def_cfa_register 5
pushl %ebx //ebx压栈
subl $20, %esp //esp=esp-20
.cfi_offset 3, -12
subl $12, %esp
pushl $28 //malloc的内存大小, Class C Object的大小为28
call _Znwj //malloc内存
addl $16, %esp
movl %eax, %ebx //ebx=eax, eax为malloc的内存的首地址,即C.this
subl $12, %esp
pushl %ebx //C的首地址压栈
call _ZN1CC1Ev //C::C()
addl $16, %esp
testl %ebx, %ebx //ebx&ebx
je .L36 //if ebx&ebx=0, jmp
leal 8(%ebx), %eax //eax=C.this+8
/*
|-------|<-----C.this
| A |
|-------|<-----b
| B |
|-------|
| C |
|-------|
| X |
|-------|
*/
jmp .L37
.L36:
movl $0, %eax
.L37:
movl %eax, -12(%ebp) //b=C.this+8, 即b=BastTypeB.this, 对应的代码为b=new C;
movl -12(%ebp), %eax //eax=b
movl (%eax), %eax //eax=vptr
movl (%eax), %eax //eax=vptr[0]
subl $12, %esp
pushl -12(%ebp) //b的地址压栈
call *%eax //b->show()
addl $16, %esp
cmpl $0, -12(%ebp)
je .L39
movl -12(%ebp), %eax //eax=b
movl (%eax), %eax //eax=vptr
addl $8, %eax //eax=vptr+8
movl (%eax), %eax //eax=vptr[2], virtual机制
subl $12, %esp
pushl -12(%ebp) //b压栈
call *%eax //call vptr[2], delete b
addl $16, %esp
.L39:
//略

基础类型的内存表示

开发环境
  • Ubuntu 14.04(32bits)
  • GCC
  • 编辑器 Cmd Markdown
  • 画图工具 Processon
1,基础类型内存表示

上一节 介绍了swich/if-else的实现机制。本文准备介绍一下基础类型的内存表示。

1.1大端法和小端法

小端法,即数字的低位在低地址。大端法,即数字高位在低地址。大小端法的最小单位为byte,而不是bit.
举例:

1
2
3
4
5
int i=-16;
对应的小端法表示为(从低地址到高地址(从左到右为地址增长方向)对应的每个byte):
0xf0 ff ff ff
大端法表示为(从低地址到高地址(从左到右为地址增长方向)对应的每个byte):
0x FF FF FF F0

在实际开发中,可以需要做大小端法互换,其代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void swap1(int *rhs)
{
unsigned char *p=rhs;
unsigned char tmp;
tmp=p[0];
p[0]=[3];
p[3]=tmp;
tmp=p[1];
p[1]=[2];
p[2]=tmp;
return ;
}

在小端机上,如下程序的输出结果为-16.

1
2
3
4
5
6
7
8
9
10
int main()
{
int i;
unsigned char *p=(unsigned char*)&i;
p[0]=0xF0;
p[1]=0XFF;
p[2]=0XFF;
p[3]=0Xff;
printf("%d\n",i);
}