GDT 的结构


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* segment descriptors */
struct segdesc {
unsigned sd_lim_15_0 : 16; // low bits of segment limit
unsigned sd_base_15_0 : 16; // low bits of segment base address
unsigned sd_base_23_16 : 8; // middle bits of segment base address
unsigned sd_type : 4; // segment type (see STS_ constants)
unsigned sd_s : 1; // 0 = system, 1 = application
unsigned sd_dpl : 2; // descriptor Privilege Level
unsigned sd_p : 1; // present
unsigned sd_lim_19_16 : 4; // high bits of segment limit
unsigned sd_avl : 1; // unused (available for software use)
unsigned sd_rsv1 : 1; // reserved
unsigned sd_db : 1; // 0 = 16-bit segment, 1 = 32-bit segment
unsigned sd_g : 1; // granularity: limit scaled by 4K when set
unsigned sd_base_31_24 : 8; // high bits of segment base address
};

组成:

  1. 32 位的段基址
  2. 20 位的段界限
  3. 1 位的D/B Flag:说明使用16位/32位的段。为1即可。
  4. 1 位的G(Granularity,粒度):为 1 ,段的大小以 4 KB 为单位。为0,段的大小为1byte。
  5. 1 位的L:1为64位系统。0为32位。
  6. 1 位的P(segment-Present,段占用?) : 用于标志段是否在内存中。
  7. 2 位的DPL:DPL(Descriptor Privilege Level)域标志着段的特权级,0为特权级,3为用户。
  8. 1 位的S:S(descriptor type) flag 标志着该段是否系统段:置为 0 代表该段是系统段;置为 1 代表该段是代码段或者数据段。
  9. 4 位的Type:设置数据/代码段时的读写等权限。
  10. 1位的AVL:保留给操作系统软件使用的位。
    共64位。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    #define SEG(type, base, lim, dpl)                        \
    (struct segdesc){ \
    ((lim) >> 12) & 0xffff, (base) & 0xffff, \
    ((base) >> 16) & 0xff, type, 1, dpl, 1, \
    (unsigned)(lim) >> 28, 0, 0, 1, 1, \
    (unsigned) (base) >> 24 \
    }

    /* Application segment type bits */
    #define STA_X 0x8 // Executable segment
    #define STA_E 0x4 // Expand down (non-executable segments)
    #define STA_C 0x4 // Conforming code segment (executable only)
    #define STA_W 0x2 // Writeable (non-executable segments)
    #define STA_R 0x2 // Readable (executable segments)
    #define STA_A 0x1 // Accessed

初始化GDT

1
2
3
4
5
6
7
8
9
10
11
12
13
static struct segdesc gdt[] = {
SEG_NULL,
[SEG_KTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_KERNEL),
[SEG_KDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_KERNEL),
[SEG_UTEXT] = SEG(STA_X | STA_R, 0x0, 0xFFFFFFFF, DPL_USER),
[SEG_UDATA] = SEG(STA_W, 0x0, 0xFFFFFFFF, DPL_USER),
[SEG_TSS] = SEG_NULL,
};

static struct pseudodesc gdt_pd = {
sizeof(gdt) - 1, (uint32_t)gdt
};
lgdt(&gdt_pd);

系统启动时cs为8H,ds为10H.

gdt[x]描述了该段的工作方式。其下标x指名当cs/ds为x<<3时,该描述符生效。

IDT的结构


IDT是一个最大为256项的表,每个表项为8字节。称为中断门。CPU通过IDT.base+n*8来寻找门。

1
2
3
4
5
6
7
8
9
10
11
12
/* Gate descriptors for interrupts and traps */
struct gatedesc {
unsigned gd_off_15_0 : 16; // low 16 bits of offset in segment
unsigned gd_ss : 16; // segment selector
unsigned gd_args : 5; // # args, 0 for interrupt/trap gates
unsigned gd_rsv1 : 3; // reserved(should be zero I guess)
unsigned gd_type : 4; // type(STS_{TG,IG32,TG32})
unsigned gd_s : 1; // must be 0 (system)
unsigned gd_dpl : 2; // descriptor(meaning new) privilege level
unsigned gd_p : 1; // Present
unsigned gd_off_31_16 : 16; // high bits of offset in segment
};

组成:(陷阱门、中断门)

  1. 32 位中断地址: 中断服务程序的地址
  2. 16 位段选择地址:中断服务程序的段地址
  3. 4 位类型: STS_{TG,IG32,TG32}
  4. 2 位的DPL:DPL(Descriptor Privilege Level)域标志着中断的特权级,0为特权级,3为用户。
1
2
3
4
5
6
7
8
9
10
11
#define SETGATE(gate, istrap, sel, off, dpl) {            \
(gate).gd_off_15
_0 = (uint32_t)(off) & 0xffff; \
(gate).gd_ss = (sel); \
(gate).gd_args = 0; \
(gate).gd_rsv1 = 0; \
(gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \
(gate).gd_s = 0; \
(gate).gd_dpl = (dpl); \
(gate).gd_p = 1; \
(gate).gd_off_31_16 = (uint32_t)(off) >> 16; \
}

初始化

1
2
3
4
5
6
7
8
9
10
11
static struct gatedesc idt[256] = {{0}};

static struct pseudodesc idt_pd = {
sizeof(idt) - 1, (uintptr_t)idt
};
while ( ++i < 256 ) {
SETGATE(idt[i],1,GD_KTEXT,__vectors[i],DPL_KERNEL);
}
SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);

lidt(&idt_pd);

通过2个例子:实现矩阵(访问二维数组)和 求和 来讲述闭包与尾递归的优点。

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
#!/usr/bin/escript
main(_) ->
X=make_matrix({1,2,3,4},2),
Y=make_matrix({2,3,4,5},2),
Z=mul_matrix(X,Y,2),
print_matrix(X,2),io:format("* ~n"),
print_matrix(Y,2),io:format("= ~n"),
print_matrix(Z,2).


%% generate a matrix

make_matrix(Data,Size) when erlang:is_list(Data) -> make_matrix(list_to_tuple(Data),Size);
make_matrix(Data,Size) ->
fun(I,J) when I =< Size , J =< Size -> element(I*Size+J-Size,Data) end.

%% X , Y: matrix -> matrix

mul_matrix(X,Y,Size) ->
make_matrix([ lists:sum([ X(X1,T)*Y(T,Y1)|| T <- lists:seq(1,Size) ])
|| X1 <- lists:seq(1,Size), Y1 <- lists:seq(1,Size) ],Size).

print_matrix([],Size) -> ok;
print_matrix(Matrix,Size) when is_function(Matrix) ->
print_matrix([Matrix(X1,Y1) || X1 <- lists:seq(1,Size), Y1 <- lists:seq(1,Size) ],Size);
print_matrix(Matrix,Size) ->
{L,R}=lists:split(Size,Matrix),
io:format("~w ~n",[L]),
print_matrix(R,Size).


sum(Start,End) ->
sum(Start+1,End,Start).
sum(End,End,Sum) ->
Sum+End;
sum(Start,End,Sum) ->
sum(Start+1,End,Sum+Start).

闭包:

由于erlang只有2种数据容器(list和tuple),2者都是一维的。在访问上tuple是顺序访问的,而链表需要遍历节点。 因此使用tuple优于list。2维数组退化为一维数组也不是什么难事,只要知道Size然后使用t=i*size+j进行转化。 这样可以很自然地得到Xij =element(I*Size+J-Size,Data)。可是这样我们需要使用4个变量来访问矩阵,并且必须时刻记住Data 与Size ,十分不方便也容易犯错。

1
2
make_matrix(Data,Size) ->
fun(I,J) when I =< Size , J =< Size -> element(I*Size+J-Size,Data) end.

采用闭包将Data与Size存入函数体内部,隐藏细节。

访问矩阵就变成了:

1
2
Matrix=make_matrix(Data,Size),
Matrix(i,j)

尾递归:

尾调用指的是一个函数最后一次执行的方法。而尾递归是 尾调用为递归函数时的特殊情况。当一个函数为尾递归时,在将参数复制给下一个函数后,原函数可以安全地退出,因而能够充分地利用堆栈。但也并不是说尾递归更好。如果返回的结果简单,就放心的使用尾递归吧。如果是这样

1
2
3
4
5
q_sort([]) -> [];
q_sort([H|R]) ->
q_sort([X || X <-R ,X =< H])
++ [H] ++
q_sort([X || X <-R ,X > H]).

挖空心思来实现尾递归并不太明智。

这里要提的不是简单的基本类型。主要内容包括:

####1.左值右值 2.引用 3. const 关键字 4.类型转换

##左值、右值

这是一种很隐含的类型。因为不能简单的通过关键字来进行修饰。为什么需要严格区分左值、右值?右值可以理解为一个临时的数据,它即将销毁,如果我们能够对它加以区分可以重新利用这部分资源。同时他们是理解 move, forward 等新语义的基础。或许这是c++11最重要的特性,因为如线程库一类只是语言的扩展,右值的引入使得程序设计有了新的思路。

完整的LValue和RValue的界定

一般来说左值对应于修改操作,右值对应于赋值操作。

  1. lvalue(左值):

    lvalue指代一个函数或者对象。例如:

    E是指针,则*E是lvalue
    一个函数的返回值是左值引用,其返回值是lvalue。例如int& foo();

  2. xvalue(expiring value,临终值):

    xvalue指代一个对象,但是和lvalue不同,这个对象即将消亡。具体来说,xvalue是包含了右值引用的表达式。因为右值引用是C++11新引入的东西,所以xvalue也是一个新玩意。例如:

    一个函数的返回值是右值引用,其返回值是xvalue。例如int&& foo();

  3. glvalue(generalized lvalue,泛左值)
    glvalue即lvalue和xvalue的统称。

  4. rvalue(右值)
    rvalue是xvalue和prvalue的统称。因为引入了右值引用,rvalue的定义在C++中被扩大化了。

  5. prvalue(pure rvalue,纯右值)
    prvalue指代一个临时对象、一个临时对象的子对象或者一个没有分配给任何对象的。prvalue即老标准中的rvalue。例如:

一个函数的返回值是平常类型,其返回值是rvalue。例如int foo();
没有分配给任何对象的值。如5.3,true。

以上都是偏学术的东西,下面来点干货。

左值是对象操作的实体,而右值意味着它即将消亡。

只有左值才能进行相应的操作如赋值,调用成员函数。左值是一个实实在在的东西。

1
2
3
> int a=1,b=2;
> a=b;
>

显然a是左值,那么b呢?
因为b内存储着资源,占用着空间,所以它也是一个左值。

我凭什么这样说呢?因为int&& c=b;是一个错误的句子,所以我很肯定。

而右值就是那些马上就没的东西。如函数的返回值,没有声明变量的类。

1
2
3
> widget f1() { return widget();}
> widget&& w1=widget();
>

需要注意的是右值与左值是可以相互转化的。比如我们可以调用f1返回的对象中的 成员函数,总的来说右值转换为左值容易一些,但是无法改变右值即将消亡的事实。对于右值我们必须马上救济widget w0=f1();

一个简单判断右值的方法就是:能不能去初始化右值引用

一个函数的参数总是左值。即使声明为右值引用void f(Widget&& w);

引用都是左值,右值引用不是右值,而是绑定到右值的左值。

##const

  1. 修饰普通变量
    1
    2
    int const a = 2;
    const int b=0;

都是不可修改变量值

  1. 修饰指针,函数返回值
    1
    2
    3
    4
    5
    6
       int test = 0;
    int* const a = 0;
    const int* b=0;
    *a = 10;
    b = &test;
    a = b; //wrong

此时需要注意const 后面的内容。 const 修饰变量名称表示该变量的值不可更改。const 修饰前面的类型(int)
表示其指向的内容不可更改,即 `
b=1`是非法的

1
2
int* const fun(int& const a, const int&  b); //由于函数返回值不可作为左值,所以无意义
const int* fun1(int& const a, const int& b);//返回的对象必须是const int*
  1. 修饰引用

    1
    2
    3
       int& const a = test;//vc编译能过,但是没有任何意义。clang不行。 所以避免出现这种用法。
    const int& b=test;//1
    a = 8; a = t1;

    [1]成为常左值引用,不可做任何修改。

  2. 转换

    1
    2
    3
    void f2(const int& a);
    int c=1;
    f2(c);

    非 常 类型数据可以隐式地转化为 常 类型。

##引用
在传统c中,如果要将数据作为参数交给函数体修改,则必须使用指针。在c++中,引用是更加安全有效的办法。相比于指针,引用没有内存泄路漏的问题。再配合新的智能指针,传统的指针应当被取代弃用。

引用作为一种类型也需要空间,可以理解为 T* const

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
   T fun();
int
main()
{
auto&& ref_r = fun<func>(); //fun:0
{
auto& ref = fun<func>();
}//~fun:0
func f;/
/fun:1
fun<func>() = f;/
/~fun:1
/
*
exit:
~fun:1
~
fun:0
~
fun:0
*
/
return 0;
}

右值引用为fun()返回临时变量延长了生命周期,变为该引用的生命周期。

详细的内容请看这里 引用

类型推导

c++中使用了3种类型推导方法:auto,template,decltype.

####template

1
2
3
template<typename T>
void f(ParamType param);
f(expr); // deduce T and ParamType from expr
  1. ParamType is a Pointer or Reference
    1
    2
    template<typename T>
    void f(T& param); // param is a reference

T的类型与调用时参数的类型一致,基本上和我们设想的结果一样

  1. ParamType is a Universal Reference
    1
    2
    template<typename T>
    void f(T&& param); // param is now a universal reference

只要了解泛引用这个问题就好理解了,详情看引用

param 的实际类型由调用时的参数决定。

1
2
3
4
5
6
7
8
9
10
11
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // x is lvalue, so T is int&,
// param's type is also int&
f(cx); // cx is lvalue, so T is const int&,
// param's type is also const int&
f(rx); // rx is lvalue, so T is const int&,
// param's type is also const int&
f(27); // 27 is rvalue, so T is int,
// param's type is therefore int&&

  1. ParamType is Neither a Pointer nor a Reference
    1
    2
    template<typename T>
    void f(T param); // param is now passed by value

这种情况会去掉调用参数的类型修饰,如const,reference

1
2
3
4
5
6
7
8
9
10
11
int x = 27; // as before
const int cx = x; // as before
const int& rx = x; // as before
f(x); // T and param are both int
f(cx); // T and param are again both int
f(rx); // T and param are still both int

const char* const ptr = // ptr is const pointer to const object
"Fun with pointers";
f(ptr); // pass arg of type const char * const
// ptr的类型是const char* ,被const修饰表示变量值不可改变
  1. 最后还有需要注意的一点是如果参数是数组或者函数指针,那么情况会变得有点奇怪。
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
int keyVals[] = { 1, 3, 7, 9, 11, 22, 35 };
fun(keyVals);
//调用以上语句
//如果fun的声明如下
template<typename T>
void fun(T a)//void fun<int*>(T):T=int*
{
}

template<typename T>
void fun(T& a)//void fun<int[7]>(T (&)):T=int [7]
{
}
//T& 保留了数组的大小信息,因而优于T

//巧用模板
template<typename T, std::size_t N> // return size of
constexpr std::size_t arraySize(T (&)[N]) // an array as a
{ // compile-time
return N; // constant
}

//对于函数指针2者之间其实没有太大的区别。
void someFunc(int, double); // someFunc is a function;
// type is void(int, double)
template<typename T>
void f1(T param); // in f1, param passed by value
template<typename T>
void f2(T& param); // in f2, param passed by ref
f1(someFunc); // param deduced as ptr-to-func;
// type is void (*)(int, double)
f2(someFunc); // param deduced as ref-to-func;
// type is void (&)(int, double)

##auto
其实和template没有太大区别。最大的不同在于:

1
auto x5 = {...}; // std::initializer_list<T>

而在函数中{}无法被正确的解释,因而template也随之失效。

##decltype
根据表达式和对象来推导类型。这个推导最接近于对象的真实类型。

1
2
3
4
5
6
7
8
9
10
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto type deduction:
// myWidget1's type is Widget
decltype(auto) myWidget2 = cw; // decltype type deduction:
// myWidget2's type is
// const Widget&
int x=0;
decltype((x)); //int&
decltype(x); //int

decltype(auto)使用的是decltype的类型推导而不是auto。

因为(x)是一个表达式,因而将其作为一个左值来解释。

类型转换

  1. static_cast <new_type> (expression)

    基本上用于静态类型转换,也能转换指针但不进行运行时安全检查,所以是非安全的。

  2. const_cast <new_type> (expression)

    可去除对象的常量性(const),它还可以去除对象的易变性(volatile)。

  3. dynamic_cast <new_type> (expression)

    它能安全地将指向基类的指针转型为指向子类的指针或引用。

  4. reinterpret_cast <new_type> (expression)
    常用的一个用途是转换函数指针类型,即可以将一种类型的函数指针转换为另一种类型的函数指针,但这种转换可能会导致不正确的结果。无任何条件的转换,一般只用于底层代码。