一个普通技术宅的点点滴滴

0%

现实中很多东西都可以看成一场博弈,但很多时候并没有那么理想化罢了,

最近咸鱼了一把, 把PSV翻出来,稍微玩了一下《弹丸论破》系列的最新作《新弹丸论破V3:大家的自相残杀新学期》
弹丸论破

弹丸论破V3的宣传图

阅读全文 »

最近花了一个多月的零碎时间了读这部著作,读毕,有醍醐灌顶之势。遂记拙文一篇,以表观感。

当我看到这本书的时候,一种很怪异的心情了浮上来。巴赫、埃舍尔、哥德尔,这三个看似风牛马不相及的人物怎么能写到一本书里?并且还是一本讲人工智能的书籍里面。这本书从一个简单的形式系统逐渐深入,并且提出了许多这三个人之间的联系,虽然我觉得很多也许是作者某种意义上故意设置的同构,但是这样一种解释方式也让我对这三个人、甚至是这三门学科——音乐、美术、数学,有了新的看法。

鉴于这本书的精妙之处,我难以一一解读,就谈谈我认为的这本书最令人深刻的问题吧。那就是——什么是智能?

在写这本书的年代,人工智能对于现实生活的影响远远没有现在那么高,那个时代,国际象棋和围棋还是机器不能战胜人类的领域。但是作者已经有了如此詹前的想法,可谓一奇也。有人说机器永远不可能拥有智能,因为他只能计算。但是人的神经元也是符合各种各样的物理规则的,神经元也只能按照固定的模式运行。书中提到的一点,就是由一个无智能的复杂系统可能表现出智能。我认为不但是一个无智能的系统,即使是一个智能的系统,也会表示出一定的智能性,因为具有智能的团体,在一定程度上是可以通过将个体的智能退化而获得上层智能的。《乌合之众》中曾经这样描述过 :“群体的行为总是表现为排斥异议、极端化、情绪化和低智商。”这也许是当一个团体,表现出一定的意识所需要的,将个体的意识行为退化的一种表现。

举个例子,著名过气老婆歌姬“初音未来”,其本身是一个由创作者创作的一个虚拟形象,但是在其知名度提升之后,却能一呼百应,让众多的爱好者加入进来,创作大量的同人作品。在大家创作同人作品的时候,其实就是对这个本不存在的人物的一种形象补全,这个形象由于众多同人的加入逐渐变得丰满起来。说好的初音平胸呢??“初音未来”这个形象,从宏观来看,仿佛变得拥有了自己的意识一般,“初音未来”的演唱会,无论在哪里举办,都会有那么多人去捧场,这种现象有时候真的会让我们忘记了这个“初音未来”只是一个不具意识的创作形象罢了,是众多的粉丝和同人作者,赋予了这个形象以生命。肥宅的老婆是由众肥宅的意识组成的???

有趣的是,这种赋予生命的现象并不一定需要这些意识的联系,只要一群有着共同目标的人就会产生这种现象。其在一部21世纪初的动画《攻壳机动队:Stand Alone Complex》中也有提到,荣格所描述的集体无意识似乎在这种时候展现了出来。

在不久之前,Google的Duplex在开发者大会上向我们展示了AI与人通话的场景,其对话自然的程度,让与其对话的人都没能识别出这是一个AI,在特定场景之下,人类已经区别不出AI个真实人类的区别。就我个人认为,AI的发展目前又进入了一个新的风口,其已经具备一定的“智能”,但是距离人类的智能水平还有不少差距。我们应当抛弃那种“人类沙文主义”的包袱,正确看待其发展,并使AI的进一步智能而努力!�还有不少差距。我们应当抛弃那种“人类沙文主义”的包袱,正确看待其发展,并使AI的进一步智能而努力!

最近博客一直断更,一时想不到要写什么比较好。这次趁着刚刚学完红黑树的机会,就开篇文章谈谈红黑树吧。

相信稍微对于数据结构都些了解的人都了解过二叉搜索树,二叉搜索树的优点很明显,就是在树基本平衡的情况下(每个分支的深度都差不多)的情况下,对于树节点的增删改查在对数时间(O(lgN))内就能够完成。但是其缺点也很明显,就是必须要在 平衡 的情况下,才能有这么高的效率,如果树只向一个方向增长(最坏情况)的话,其效率就变得和线性表一样了,甚至还要低下。

最优情况和最坏情况

平衡二叉树和不平衡二叉树比较

于是,就有人想要发明一种能够在最坏情况下仍然拥有对数时间的效率的数据结构,“自平衡二叉查找树”便诞生了。英文名叫做"Balanced tree",简称B树hhh。说这么多,估计各位心里仍然没有一点B树Orz。其实所谓平衡树,就是在插入,删除的时候,使用一些树的变换手段(一般是旋转),来使树达到平衡的数据结构,其本质仍然是一棵二叉搜索树。

最早提出的平衡树是AVL树,但是由于种种原因,其并未得到工业界的广泛使用,具体介绍由于篇幅限制这里就不展开了,有兴趣的读者可以自行搜索。目前工业界最为广泛使用的平衡树,便是本次要介绍的红黑树,但为了让我们更容易实现红黑树,我在此并不直接介绍红黑树,而是效率和红黑树差不多的一种变体——左倾红黑树。

在介绍红黑树之前,请让我先介绍一种和红黑树等价的数据结构:”2-3树”。这种树是有点像二叉搜索树,但是又有些不同,重点在于其有两种节点,一种节点和二叉树一样,有一个键和两条指向子节点的链接。另外一种就比较厉害了,他有两个键和三条链接,这三条链接分别指向:同时小于两个键的节点,大小在两个键之间的节点,以及同时大于两个键的节点。

当插入一个键的时候,首先对这个键进行搜索,按照二叉树的方法找到插入的位置,如果要插入的位置在一个2-节点,那就好办了,只要插入这个节点中合适的位置,让这个节点变成3-节点,这样做的话树的平衡性并没有改变。但如果插入的位置已经是一个3-节点的话,事情就变得有些棘手,我们必须在不改变树的平衡性的前提下完成插入操作。
我们可以首先将这个键插入3-节点,变成一个临时的4-节点,然后,沿着根的方向,依次分解遇到的4-节点。将4-节点中的处于中间大小的键放到他的父节点中。这样依次进行变换,一直到根节点,如果遇到根节点也变成了4-节点的情况,就可以直接将这个4-节点拆成三个2-节点,这样做的话,树的每条分支增长的速度就是一样的了,因为树由从上向下生长改为了 从下向上 生长。

2-节点
3-节点

2-节点和3-节点

了解了2- 3-树的插入操作后,便可以正式开始介绍左倾红黑树了。2-3 树好是好,但是由于其是用两种不同类型的节点构成的,实现起来不方便。我们可以用一个方法巧妙的替代2-3树中的3节点,就是用两个普通的2-节点,通过一种特殊的链接——红链接来连接起来,来表示一个3-节点。

当然,要表示出这个红链接,需要给每个节点加入一个额外的属性——颜色,红黑树中每个节点都是两种颜色中的一种——黑色和红色。节点的颜色表示指向该节点的链接颜色。根节点与其他节点不同,其始终为黑色。大家应当注意到,左倾红黑树的“左倾”意味着只能够存在向左的红连接,而不能存在向右的链接。

在红黑树中,为了在保证树的平衡性的同时,还能够保证红链接的合法性,我们采用一个balance函数,其调用rotate_left(左旋)和rotate_right(右旋)以及flip_color_black方法来实现这些变换。flip_color_black和flip_color_red的作用在于将一个子节点全为红色的节点染红,并将其子节点染黑,以及上述操作的反向。其实现已经在文后的代码中给出,这里不再细说。关于树的旋转变换,参见下图。

树旋转

树的插入操作和2-3树类似,首先找到插入位置,插入,然后沿查找路径向上依次执行balance方法(参见实现中的put方法)

再来说说删除最小键,可以在向左遍历的过程中,让红黑树暂时变得“不严谨”,也就是说,暂时允许等价的4-节点出现。并且需要做一个变换,保证当前查找的位置一定是3-节点或者是4-节点,这样当查找到底部的时候,就可以保证当前节点不是2-节点,实现中使用flip_color_red和树的旋转操作来实现,此时只需要直接从树中删去最小节点,然后沿着路径向上调用balance方法恢复树的性质。这里要注意的是,如有需要,我们可以临时改变根节点的颜色为红色,这样就能保证在变换中,父节点的颜色一定是红色。
删除最大键与最小键同理,但因为子节点在右边,需要做一些额外的变换,这里留给读者去思考,实现见代码。

现在遇到了最难的操作——删除指定键,但有了前面删除最大最小键的铺垫,问题也就迎刃而解了。与删除最大最小键时类似,沿着查找路径进行4-节点变换,当找到需要删除的键时,可以分为三种情况:

  • 没有子节点

这种情况非常容易处理,由于可保证当前不是2-节点,直接删除该节点就行

  • 只有一边的子节点

这种也比较简单,只需要将子节点接上原来的链接就行

  • 同时拥有左右节点

此时需要使用类似二叉搜索树中的方法,将该节点与他的右分支中的最小节点交换,此时又将问题转换成了一个删除右分支中的最小键的问题。

最后附上我的实现代码:
[gist https://gist.github.com/Koswu/3383ec7242a997dae163c9af922b1a56]

看完《白箱》中的3d制作的部分以后,突然对于3d建模感兴趣,抱着兴趣指引的方针(笑,针对一款开源3d图形处理软件——blender,进行了一番学习。经过一周的学习后,取得了不错的成效。下面是拙作:
效果图

在学习的过程中,也学习到了一些图形学相关的知识。比如:我们平时用眼睛观察世界,用相机拍照,这样获取的图片属于物体的透视投影,也就是让物体投影到一点上。这种透视表现出来的是一种近大远小的视觉,这符合常识,但是不能够很好的表现物体各部分的比例。这个时候我们就需要正交视图,这是一种投影到面的视图,我们几何中学到的三视图就属于正交视图。

从零开始构建一张效果图,大致需要以下步骤:

  1. 建模
    这一步需要很有耐性,基本上就是在一些规则图形上修修补补,构建物体的形状
  2. 材质选择
    对已经建模的物体进行上色,上贴图等等,改变物体的材质效果
  3. 灯光设置
    对场景进行灯光布置和摄像机的位置摆放位置调整
  4. 渲染
    对于场景计算灯光和材质等等的效果建立出最终的图片

不知不觉一个学期过去了,虽然有些遗憾,但我仍然庆幸收获了不少知识。在此对我的寒假要学习的东西做一个粗略的计划,以此提醒自己还应该做的事情,防止玩过头。


首先寒假中的第一个任务是对算法和数据结构的学习,由于开学就要参加蓝桥杯的比赛,必须在开学之前对于竞赛相关的题目有一个较为全面的认知。除特殊情况以外,应当每天抽出时间,特别是晚上的时间,对算法和数据结构进行一定的练习。

其次,在大一的时候,计划读完《深入理解计算计算机系统》这本书,所以在寒假期间应当至少看到“处理器体系结构”一章。这个可以利用其许多闲置的下午时光完成。

然后是对于计协的要求,对于web后端和数据库进行一些学习,可尝试利用起早晨与的时间来进行。

最后在技术之外,养成良好的生活习惯方面,应当设置闹钟,将学校里养成的生物钟调整过来,无特殊情况,晚上一般应在12点前睡觉,最迟不超过12点半,而早上在8点半起床,最迟不超过9点半。同时在中午也养成短暂午休的习惯,每天可以在午休前背下英语。

拜读过《数学之美》中的余弦定理应用的相关章节,并学习了符号表的实现以后,一直希望自己能够做一个能够判断文章相似度类似的东西。虽然基础有限,但是仍然取得了不错的效果。
分词相关对于我来说数学基础还是有些不够,所以我决定暂时不进行分词,对英文文章这种天生的分词进行处理就好。
首先是实现一个符号表,在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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#ifndef SILIB_H_
#define STLIB_H_

template <typename Key, typename Value> class BinarySearchST
{
private:
Key *keys;
Value *values;
int st_size;
int count;
int resize(int capacity);
public:
BinarySearchST(int capacity);
int size();
bool isempty();
Value get(Key key);
int rank(Key key);
int put(Key key, Value val);
void del(Key key);
Key min();
Key max();
Key select(int k);
Key ceiling(Key key);
Key floor(Key key);
~BinarySearchST();
};
template <typename Key, typename Value>int BinarySearchST<Key,Value>::resize(int capacity)
{
if (capacity < count + 1)
{
return -1;//capacity is too small...
}
Key *keys_new = new Key[capacity];
Value *values_new = new Value[capacity];
if (!keys_new || !values_new)
{
return -2;//allocate memory error...
}
for (int i = 0;i < count;i++)
{
keys_new[i] = keys[i];
values_new[i] = values[i];
}
delete[] keys;
delete[] values;
keys = keys_new;
values = values_new;
st_size = capacity;
return 0;
}
template <typename Key, typename Value>BinarySearchST<Key,Value>::BinarySearchST(int capacity):st_size(capacity),count(0)
{
keys = new Key[capacity];
values = new Value[capacity];
}
template <typename Key, typename Value>int BinarySearchST<Key,Value>::size()
{
return count;
}
template <typename Key, typename Value>bool BinarySearchST<Key,Value>::isempty()
{
return !count;
}
template <typename Key, typename Value>Value BinarySearchST<Key,Value>::get(Key key)
{
if (isempty())
{
return 0;
}
int i = rank(key);
if (i < count && keys[i] == key)
{
return values[i];
}
return 0;
}
template <typename Key, typename Value>int BinarySearchST<Key,Value>::rank(Key key)
{
int lo = 0, hi = count-1;
while (lo <= hi)
{
int mid = lo + (hi-lo)/2;
if (keys[mid] > key)
{
hi = mid - 1;
}
else if (keys[mid] < key)
{
lo = mid + 1;
}
else
{
return mid;
}
}
return lo;//if not found, return the index of lower than search key.
}
template <typename Key, typename Value>int BinarySearchST<Key,Value>::put(Key key, Value val)
{
int i = rank(key);
if (i < count && keys[i] == key)
{
values[i] = val;
return 0;
}
if (count+1 > st_size)
{
int ret_code=resize(st_size*2);
if (ret_code != 0)
{
return ret_code;
}
}
for (int j = count; j > i;j--)
{
keys[j] = keys[j-1];
values[j] = values[j-1];
}
keys[i] = key;
values[i] = val;
count++;
return 0;
}

template <typename Key, typename Value>void BinarySearchST<Key,Value>::del(Key key)
{
int i = rank(key);
if (i < count && keys[i] == key)
{
for (int j = i;j < --count;j++)
{
keys[j] = keys[j+1];
keys[j] = keys[j+1];
}
}
}
template <typename Key, typename Value>Key BinarySearchST<Key,Value>::min()
{
return keys[0];
}
template <typename Key, typename Value>Key BinarySearchST<Key,Value>::max()
{
return keys[count-1];
}
template <typename Key, typename Value>Key BinarySearchST<Key,Value>::select(int k)
{
return keys[k];
}
template <typename Key, typename Value>Key BinarySearchST<Key,Value>::ceiling(Key key)
{
int i = rank(key);
return keys[i];
}
template <typename Key, typename Value>Key BinarySearchST<Key,Value>::floor(Key key)
{
int i = rank(key);
if (keys[i] > 0 && keys[i] == key)
{
return keys[i];
}
return keys[i+1];
}

template <typename Key, typename Value> BinarySearchST<Key,Value>::~BinarySearchST()
{
delete[] keys;
delete[] values;
}

#endif

这里统计词频率时,Key为string,Value为int,我们可以设计一个函数,能够将文章读入符号表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BinarySearchST<string, int> *getCountedObj(std::ifstream& ifst)
{
BinarySearchST<string, int> *st = new BinarySearchST<string, int>(1);
while (!ifst.eof())
{
string word;
ifst >> word;
if (word != "")
{
st->put(word, st->get(word)+1);
}
}
return st;
}

这时可以得到两个文章向量,各维的坐标值对应了相应的词频。使用以下函数可以计算出这两个文章向量夹角的余弦值,即文章相似度

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
double calculateSimialarity(BinarySearchST<string, int> *st1, BinarySearchST<string, int> *st2)
{
int i=0, j=0;
long long product = 0;
double abs1 = 0,abs2 = 0;
while (i<st1->size()&&j<st2->size())
{
string key1 = st1->select(i);
string key2 = st2->select(j);
if (key1 > key2)
{
abs2 += pow(st2->get(key2), 2);
j++;
}
else if (key1 < key2)
{
abs1 += pow(st1->get(key1), 2);
i++;
}
else
{
product += st1->get(key1) * st2->get(key2);
abs1 += pow(st1->get(key1), 2);
abs2 += pow(st2->get(key2), 2);
j++;
i++;
}
}
abs1 = sqrt(abs1);
abs2 = sqrt(abs2);
double cosed_degree = product/(abs1*abs2);
return cosed_degree;
}

使用这个网站这个中的小说作为实验,发现同为福尔摩斯的两篇小说基本上相似度在0.9以上,不同题材小说仅有0.6左右的相似度,较好地符合了预期,实验大成功!!

想入坑Arch好久了,但是有很多次都因为在解决引导的时候就卡了,最后一直没能装上。经过上次制作启动盘的经验,对于grub有了更多的了解。这次再次尝试安装,虽然经历了一些挫折,但还是成功了。下面是具体的安装过程。

首先用u盘引导系统,接下来就进入了arch的Live CD模式(雾)。这也是arch linux最劝退的一步,很多人看到装个系统都要命令行,就退了.。其实用命令行装系统才能真正在装系统的同时学习到知识。

同步时间

由于arch采用网络安装的方式,所以先让计算机连接上网络,并执行

timedatectl set-ntp true

硬盘分区

对硬盘进行分区

fdisk /dev/sdY

需要注意的是,如果电脑为UEFI引导的,需要将硬盘设置为GPT分区,否则为传统MBR模式。由于我装双系统,所以就只分了一个分区作为根目录,一个分区作为交换分区。
交换分区记得将分区的类型设置为82,这样可以让systemd自动发现并挂载。

格式化交换分区

mkswap /sdYM

格式化主分区

mkfs.ext4 /sdYN

将主分区挂载至/mnt,将EFI分区挂载至/mnt/boot

mount /dev/sdYN /mnt #挂载主分区
mkdir /mnt/boot
mount /dev/sda1 /mnt/boot #挂载EFI分区

安装系统文件

pacstrap /mnt base base-devel

生成fstab文件并检查

genfstab -U /mnt >> /mnt/etc/fstab
cat /mnt/etc/fstab

添加swap分区的UUID到fstab

echo "#/dev/sdYM swap \n UUID=`lsblk -no UUID /dev/sdYM` none swap defaults 0 0" >> /mnt/etc/fstab

chroot到新的系统目录,由于普通的chroot容器有一些问题,所以我们采用专门开发的arch-chroot

arch-chroot /mnt

设置时区

ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
hwclock --systohc --utc

设置本地化信息,打开/etc/locale.gen,取消以下几行的注释

en_US.UTF-8 UTF-8
zh_CN.UTF-8 UTF-8
zh_TW.UTF-8 UTF-8

运行

locale-gen

以生成本地化配置文件

在hosts中设置localhost,如果不设置也行,但会造成firefox打开慢等问题

#在/etc/hosts中加入
127.0.0.1     localhost

设置root密码

passwd

如果使用intel处理器,安装intel-ucode以启用微码更新

pacman -S intel-ucode

下面进入第二阶段,安装开机引导

安装grub

pacman -S grub
grub-insatll --efi-directory=/boot

配置grub
pacman os-prober
mkdir /mnt/windows
mount /dev/sda2 /mnt/windows#挂载其他系统的磁盘
grub-mkconfig -o /boot/grub/gurb.cfg

至此就可以正常进入系统了,重启

exit
umount -R /mnt
reboot

如果仍然进入windows的话,在windows借助一些工具,如bootice来修改UEFI启动顺序,将arch放到第一个。


第三阶段,安装GNOME桌面

首先解决显卡驱动的问题,以英特尔和英伟达双显卡驱动为例,安装相关驱动和humblebee——实现显卡交火的工具

sudo pacman -S humblebee mesa xf86-video-intel nvidia primus

安装后系统命令将默认用集成显卡运行,如果需要使用独显运行,使用

primusrun 命令

曾经linux下主流的显示协议是X11,现在GNOME已经切换到了效率更高的Wayland,直接安装gnome软件组来获取所有套件

sudo pacman -S gnome

如果需要其他的gnome软件包的话运行

sudo pacman -S gnome-extra

开启gdm

systemctl enable gdm

重启系统,就这样,基本的arch linux就安装好了

贴一张安装好的截图:
!安装后

由于最近突然有换系统的想法,于是打算直接做一个启动盘以应付将来的各种装机需求,说起来简单做起来难,在几天摸爬滚打后终于完成了,在这里记录一下过程供参考。


初拟需求

当然是越大越全越好啊,手头有一个32G的U盘,理论上应该能够装挺多系统的,会用到的都塞进去最好,下面是可能会用到的系统

  • Win系
    • Windows XP (给各种可能遇到的老的电脑装机)
    • Win 7 x64(给可能遇到兼容性问题的电脑装机)
    • Win 10 x64(新电脑装机)
  • Linux系
    • Arch Linux (即将给我的电脑换上的系统)
    • CentOS (学习服务器运维可能会装到)
    • Ubuntu (Linux新手必备)
    • Kali Linux (安全工具可能用到,最好直接是Live CD)

思路

首先排除目前市面上常见的各种XX装机盘制作工具,这种工具制作出来的质量不说,里面有没有挂恶意程序也是未知的,所以一切最好采用开源工具和官方原版镜像制作。初拟采用grub2装入u盘做引导,然后提供选项进行选择进入。利用grub2的iso载入能力,将不同的镜像放到iso/目录里面。

操作与重改需求

操作过程中,发现grub对于win系没法直接载入iso,因为实质上grub是将kernel内核读入内存以后,再对内核传递参数告诉后面应该从iso去读。但是win毕竟不是linux,目前网上的解决方案里面要么只能支持老版本的winNT,要么需要将整个iso先读入内存,这对小内存机器极不友好。同时,Debian对于读入iso引导的方式支持性也很差。并且Cent OS完整版占用太大,对于兼容性好的FAT32放不下。

解决方案:因为不常用去掉CentOS和Debian系的Kali,win可以采用分区+合盘的方案,将U盘借助工具分区为主分区(FAT32)和win分区(NTFS),将win镜像解压到win分区里,并且将几个win的wim文件参照网上的教程合并为一个esd文件。xp的安装方式也和win7以后不一样,所以排除。
这时还剩下的系统有:

  • Win系
    • Windows XP
    • Win 7 x64
    • Win 10 x64
  • Linux系
    • Arch Linux
    • CentOS
    • Ubuntu
    • Kali Linux

实际制作:
首先将U盘分区,然后将grub安装到u盘上

sudo mkdir /mnt/usb
sudo mount /dev/sdY /mnt/usb #挂载U盘
sudo grub-install --target=i386-pc --boot-directory=/mnt/usb/boot --recheck /dev/sdY #安装BIOS支持
sudo grub-insatall --target=x86_64-efi --efi-directory=/mnt/usb --boot-directory=/mnt/usb/boot #安装UEFI支持

然后是grub.cfg配置文件

#加载模块
insmod cat
insmod gzio
insmod ext2
insmod ntfs
insmod font
insmod fat
insmod part_msdos
insmod loopback
insmod iso9660
if [ ${grub_platform} == 'efi' ]
then
insmod efi_gop
insmod efi_uga
else
insmod vbe
fi
insmod font
insmod search_fs_uuid

set default=0   #默认选项
set timeout=30   #默认等待时间

#加载字体

if loadfont ${prefix}/fonts/unicode.pf2
then
insmod gfxterm
set gfxmode=auto
set gfxpayload=keep
terminal_output gfxterm
fi

#设置语言
set locale_dir=${prefix}/locale
set lang=zh_CN

#设置背景图片
set root='hd0,1'
insmod jpeg
background_image /boot/grub/images/anime1.jpg
#设置未选中项颜色
set menu_color_normal=white/black
#设置选中项颜色
set menu_color_highlight=cyan/black
# ------------------------------------------------------------------------

#搜索根目录下有iso的磁盘设定为root分区
search --set -f /iso

#set root=(hd0,2)

menuentry "安装WINDOWS" {
search --set -f /bootmgr#搜索根目录下有bootmgr的磁盘设定为root分区
if [ ${grub_platform} == 'efi' ]#efi采用链式引导
then
insmod chain
#echo "it's efi"
chainloader /efi/boot/bootx64.efi
else#传统BIOS采用ntldr引导
insmod ntldr
#echo "it's not efi"
ntldr /bootmgr
fi
}

menuentry "安装Arch linux x86_64 2017.10.01" {
set iso_path=/iso/archlinux-2017.10.01-x86_64.iso
loopback loop ${iso_path}
linux (loop)/arch/boot/x86_64/vmlinuz archisolabel=ARCH_201710 img_dev=/dev/disk/by-label/ISOTOOLS img_loop=$iso_path iso_path=$iso_path earlymodules=loop
initrd (loop)/arch/boot/x86_64/archiso.img
}


menuentry "安装ubuntu-16.04.2-desktop-amd64" {
set iso_path=/iso/ubuntu-16.04.2-desktop-amd64.iso
loopback loop ${iso_path}
linux (loop)/casper/vmlinuz.efi boot=casper initrd=/casper/initrd.lz iso-scan/filename=${iso_path} quiet noeject noprompt splash --
initrd (loop)/casper/initrd.lz
}

menuentry "重启"{
echo "Rebooting...."
reboot
}

menuentry "关机"{
  echo "Shuting down...."
  halt
}

再将相关的iso放在主分区的/iso文件夹中,并将windows合盘后的镜像写入次分区。
制作大成功!

我们都知道,在计算机中都是使用二进制来表示整数,但是很少有人深入理解计算机具体是怎么去表示的,认为其不重要,然而事实恰恰相反,许多知名的漏洞利用的都是人们不熟悉或者疏忽了计算机里具体执行的运算。所以对于了解二进制数是怎么存储在计算机里是很有必要的。

一个n位的十进制无符号数,其每个位可以看作一个n维向量中的带有权重的维,而其权重也与位置相关,以1234为例,4的权重为100,3的权重为101,2的权重为102,而1的权重为103,其数值即为各维的长度乘以该维的权重之和,即1×103+2*102+3*101+4*100=1234。

二进制无符号数同理,其每一位的权重即为2n,以1010(2)为例,其可看作一个四维向量,各维的权重从左到右依次是23,22,21,2^0。在计算机中表示二进制数时,是以字节为单位存储的。其有两种存储方式,大端法和小端法,区别在于每个数存储方式是从高地址位还是低地址位存储。如一个数为0x1234,有两个字节,如果在一个16位的机器上,分配两个内存地址分别为0x00和0x01的字节存储,在采用大端法时,内存地址0x00中存储的是0x12,0x11中存储的是0x34;而小端法相反,在0x00中存储的是0x34,0x11中存储的是0x12。这就造成了在不同的机器中,同一段代码可能会有不同的结果。下面这段代码就利用这一点,检测了运行的机器采用的是大端法还是小端法。

#include <stdio.h>

typedef unsigned char* byte_pointer;

int islittle_indian()
{
  int i=0xFF;
  byte_pointer ptr = (byte_pointer)&i;
  if ((*ptr) == 0xFF)
  {
    return 1;
  }
  if ((*ptr) == 0x00)
  {
    return 0;
  }
  else
  {
    return -1;
  }
}

int main(int argc, char const *argv[])
{
  printf("%d\n",islittle_indian());
  return 0;
}

这段代码在采用大端法的机器上运行的时候,输出0,在小端法的机器上运行的时候,便会输出1。尽管现在我们身边采用大端法的机器不多了,但是仍然需要了解。

说了这么多,表示范围仍然在无符号数内,而要扩展到有符号数,就不得不提目前大部分计算机都在采用的有符号数表示方法——补码,许多国内教科书介绍补码时,总是提到将最高位表示符号即原码,原码取反加1就是补码,但是完全没说补码的定义。其实如果了解到无符号数可以看作n维向量这一点,就很好理解补码了。

补码就是将最高位的权重变为负值的表示方式

如1010(2),如果我们按照无符号数的解读方式去乘相关的权重的话,会得到123+1*21=10。而如果我们当成补码去解读的话,就是1(-23)+2*21 = -6,这也得以说明为什么补码中负数的表示范围要比正数大1。补码有很多优点,如进行减法的运算和加法一样,在正数范围内与无符号数表示方法一样等等。虽然补码很方便,但是也要注意与无符号数混用时,经常会产生难以发现且严重的bug。

写了这么多,全面了解计算机系统对于写出健壮程序的重要性可见一斑,今后也应当多留心这方面的东西,才能更好地理解自己写出的程序。