404频道

学习笔记

参加完姐姐的婚礼回到家后,静下心来之后内心莫名的感伤。也许是早晨三点醒四点起床导致身体在下午已经出现了疲惫。也许是生活本来就应该是平淡的,兴奋的多了,也自然疲惫的多,总之要保持一个符合每个人性格的平衡。也许是因为如女朋友同事的例子中一样,当丈夫娶妻过门的时候,丈夫的妹妹是哭的,因为妹妹有恋哥情节,难不成我也有恋姐情节的存在,还好我仅是有点伤感而已,不严重。也许是因为对时间流逝的无奈,总以为我们还没有长大,转眼间姐姐已经步入了婚姻的殿堂,成立了自己的新家庭,逝者如斯。趁着自己还有点文艺范的精神状态,写写这两天的一些感想,要不然明天一接触需要理性思考的计算机语言,这种感性点的状态又会归于平淡,又会在我鲜有的心路历程文章里少了一篇。

小的时候总是羡慕姐姐在城里的生活,每年在仅有的几次见面中总能感受到一个不一样的姐姐。那时的我对于城市的大小、城市的好坏还没有概念,仅仅知道城市要比农村好不少倍,仅仅知道姐姐是城里的,是城市小朋友的代言人,是引以为荣的小榜样。也许下面这张小时候的珍贵照片最能反应出城市孩子和农村孩子之间的区别。
Image Title
记得大约上八年级寒假的时候见过姐姐写的字,字迹铿锵有力且独特,不是常见的楷体字,由特定的字体加上自己的独到之处柔和而成。分明是经过训练方能造就,我从未在我认识的同学中见过能够写出如此华丽的字体,即使有书法天赋的同学由于没有经过特定的练习,字体往往都是课本上常见的楷体字。因为从上高中起,我已经开始渐渐地融入曾经向往的城市生活,直到如今我也算半个城市人。我和姐姐之间的差距在不断的缩短。当然会发现原来城市生活也有城市生活的弊端,如果让我选择一次我还会选择在农村度过我的童年,那种各种玩各种贴近大自然的生活是在高楼间无法体会到的。

姐姐终于找到了可以寄托的归宿,姐夫是很优秀的。姐姐属于离不开爱情的类型,但身体却不是很好,也许是姐夫喊姐姐玉妹的缘由。期望在外来姐夫能够好好照顾姐姐,同时姐姐应该多注意身体,多吃粗粮。

一个我想象不到的地方是随着现在人民文化水平的提高,婚礼的举办方式正在朝着复杂化的方向发展。现在的婚礼已经将旧有的当地风俗和现代的风俗结合在了一起,而且这两者是叠加的关系,意味着风俗越来越多,需要处理的事情越来越复杂。其实婚礼的作用无非就是热闹、喜庆, 我实在是搞不太明白复杂和热闹之间是一个什么样的关系。我期望我婚礼是简单的,简单的不能再简单,我甚至期望不需要亲朋好友的过多参与,我甚至不期望举办复杂的婚礼仪式,热闹的背后是家人的操劳,是长达数月的准备。我只希望能有一次难忘的旅行,两个人的旅行就足够幸福。

祝福的含义我是不理解的,祝福原本是一个人向另外一个人的未来的美好祝愿,就是说些客套话,说些不切实际的话。其实一个人很难对另外一个人的外来向好的方面发展做出贡献,就如同大臣们天天喊着万岁万岁万万岁,却未见过哪个皇帝超过百岁一般。很多人都会向姐姐的婚礼说出祝福,但有多少是过后还记得自己曾经说过的。我在这里同样祝福姐姐和姐夫新婚快乐,生活幸福,和睦相处,恩恩爱爱,当然我是发自内心的,而且我的话是有文字可考究的。同样在给姐姐的红包里我写下了“原寻寻觅觅,现卿卿我我,年年岁岁情爱深,岁岁年年无不同”的祝福,这里同样做一个备忘。

目前的婚庆已经完全市场化,只要是服务项没有不收费的可能性,而且往死里要。没有信仰的民族是个可怕的民族,如果马克思主义不能所谓一种信仰,那么中国人中的绝大多数是没有信仰的。即使大家上初中、高中、大学、研究生阶段都在马克思的理论,在当前的国情下估计也没有多少人能视马克思如珍宝。如果在国外我估计会出现在教堂举办婚礼非常廉价,甚至免费的可能性,因为能够为新郎新娘主持婚礼本身就是一件非常荣幸的事情,金钱不是最重要的,当然我不了解海外的真实情况。总感觉一件本来可以免费的事情只要跟金钱沾边总会变味。

中国父母的典型形象是碎碎念,对于子女总会絮絮叨叨那么一箩筐。在不经意间,子女已经长大成人,可在父母的眼中子女永远是未长大的孩子。中国的父母在子女身上花费了太多的心血,以至于子女比自己更重要。

一个活动的缺点是非常容易被人察觉的,而活动的优点往往不容易被人发觉。因此,要想顺利完成一个活动是不容易的。举办婚礼的时候我发现有很多事先未准备妥当的地方,而这些恰恰是最容易被发现的,而做得特别好的地方却是较难被人们发现。

年龄之间的代沟还是比较明显的,这一点在酒桌上特别能够体现,在酒桌上话语最多的永远是上一辈人,因为年轻人跟上一辈人在思想上还是有较大的差距,这是好事。如果两代人之间差距过小,说明这个社会变化太慢。年轻人应该吸取上一代人的长处,去其槽粕,取其精华。我想未来的酒桌文化会大变样。

我目前的理想生活状态是平淡充实的,我喜欢静下心来学习,我喜欢那种学习的充实感,我喜欢跟同龄人人心贴心的交谈,而不是在噪杂的环境中大家泛泛而谈。我的性格中腼腆的一面表现在我在与人沟通时内心的想法总会羞于表达,比如很难从我的嘴里向父母说出生日快乐之类的话语,向姐姐当面说出新婚快乐,白头偕老。一方面想着跟人沟通,另一方面却羞与吐露自己的内心想法,貌似有点矛盾的样子。

人类本该是感性的,接触的外部环境多了之后会渐渐趋于理性,当然人类的科技进步需要理性。理性多了,自然感性就会少一些。这几年跟计算机打交道的时间成了生活、工作的最重要部分,我大部分生活状态处于理性状态,心情以平淡居多。包括我现在写这篇文章,也是在用理性的思维方式来写感性的文章,换成在高中时期写的文章却是用感性的思维方式来写感性的文章。

我的性格未曾被工作改变,甚至是相貌在工作三年的时间里也未曾有大的变动。我现在仍然看上去像个学生,路人也经常会将我当成一名学生。女朋友说这是我不成熟的表现,我不知道成熟如何去界定,我认为我每天学到的东西都在助我向成熟迈进,直到我老去的那一刻我也不可能成熟,因为只要我还有进步我就不会成熟。我对于三年来的未改变我引以为豪,至少我没有被这个残缺的社会给同化掉,至少我还拥有自己的思想,同时也证明了一点我在大学期间思想就已经在保持了一种相对稳定的状态。我不认为工作后在官场上混的如鱼得水,在酒桌上的机智多变是一种成熟的表现,我对此持悲观态度。

行行出状元。今天看到我姑在玩QQ农场、QQ牧场的游戏,熟练程度相当高。由于我姑空闲时间较多,常常一个人在家,已经玩了多年的游戏。我姑对电脑的使用还处在初级阶段,平常电脑的作用还定位在玩农场的阶段。只要做的次数多了,行行都可以出状元。

本来想着把自己参加完姐姐婚礼之后的感想记录下来,结果写着写着开始有些怀旧,写自己的居多,困的不知所云,到此为止。不知等我老去的那天当我读到此文章时会是一种什么样的心情,难不成还会跟现在一样有点压抑的感觉?

system函数

函数包含在C语言的标准库中,在头文件stdlib.h中声明如下:

1
int system(const char *command);

该函数会创建一个独立的进程,该进程拥有独立的进程空间,为阻塞函数,只有当新进程执行完毕该函数才返回。

返回值:可以通过返回值来获取新进程的main函数的返回值,返回值保存在int类型的第二个字节即8-15比特,可以通过向右移位或者利用宏WEXITSTATUS(status)来获取新进程的返回值。其中WEXITSTATUS(status)宏包含在头文件<sys/wait.h>中。

这里以调用ls命令为例来展示用法:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main()
{
int r=system("ls");
// 用右移位的方式来获取新进程的返回值
//printf("%d\n",r>>8&255);
// 用WEXITSTATUS(status)宏的方式来获取新进程的返回值
printf("%d\n",WEXITSTATUS(r));
}

popen函数

函数包含在头文件stdio.h中,相关函数如下:

1
2
FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

popen函数在父子进程之间建立一个管道,其中type指定管道的类型,可以为”r”或”w”即只读或可写。在shell中的管道符”|”即采用此函数来实现。popen函数为阻塞函数,函数的具体用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
main()
{
char buf[1024];
FILE *f=popen("ls","r");
// 根据管道获取文件描述符
int fd=fileno(f);
int r;
while((r=read(fd,buf,1024))>0)
{
buf[r]=0;
printf("%s\n",buf);
}
close(fd);
// 关闭管道
pclose(f);
}

exec系列函数

该系列函数并不创建新的进程,而是将程序加载到当前进程的代码空间来执行并替换当前进程的代码空间,在exec*函数后面的代码将无法执行。

1
2
3
4
5
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);

fork函数

该函数非常常用。函数原型如下:

1
pid_t fork(void);

调用该函数会产生一个子进程,该子进程不仅复制了父进程的代码空间、堆、栈,而且还复制了父进程的执行位置。之后父子进程同时执行,通常由于操作系统任务调度的原因,子进程会先执行。父进程和子进程之间的并不会共享堆、栈上的数据,可以通过文件或共享内存的方式来通讯。

返回值:该函数父进程返回子进程的id,子进程返回0。通常在代码中通过返回值来判断是子进程还是父进程,用来执行不同的代码。

如果父进程先结束,则子进程会成为孤儿进程,子进程仍然可以继续执行。进程数中的根进程init会成为该子进程的父进程。

如果子进程先结束,则子进程会成为僵尸进程。僵尸进程并不再占用内存和CPU资源,但是会在进程数中看到僵尸进程。因此代码中必须对僵尸进程的情况做处理,通常的处理办法为采用wait函数和信号机制。子进程在结束的时候会向父进程发送一个信号SIGCHLD,整数值为17。父进程扑捉到该信号后通过调用wait函数来回收子进程的资源。其中wait函数为阻塞函数,会一直等待子进程结束。wait函数返回子进程退出时的状态码。下面通过实例演示一下当子进程先结束时父进程怎样回收子进程。

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
#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void processChildProcess()
{
printf("receive the child process end\n");
wait();
}

int main()
{
int pid;
pid = fork();
if (pid > 0)
{
// 父进程
signal(SIGCHLD, processChildProcess);
while(1)
{
sleep(1);
}
}
else
{
// 子进程
sleep(1);
printf("child process end\n");
}
}

关于fork的详细理解可以看这里:一个fork的面试题

前段时间公司领导安排我完成一个android的小项目,功能为完成手机短信的分类功能。由于考虑到可能会和某运营商合作,项目需求多变,项目名到项目死掉的那一刻也没有想好,暂定叫短信管家。前段时间参加了今年软考中的高级项目管理师考试,对项目管理有一定的了解,由于在实际的工作中没有参与项目经理的角色,对项目管理的知识理论和实际结合还有一定差距。我一向是喜欢挑人毛病的,本文也不例外,将结合项目管理的知识领域来分析项目中存在的问题,并给出纠正的措施。本文并不想按照项目管理的44个过程组来写,不想受项目管理理论知识条条框框的约束。

项目简介

在2013年元旦后上班第一天,经过领导简单介绍项目后,项目正式开始启动。项目参与人员包括我和一名美工。由于公司没有做android项目的经验,我是公司唯一懂点android项目的技术人员,我之前也仅仅是自学过一点。经过两个星期的学习之后开始了android项目的开发,春节前领导希望看到一个版本出来。离春节放假还有三天,美工搞出一个首界面来,我仿照首界面基本搞定。后来领导一看不符合需求,其实领导之前就看过首界面效果图,只是没有仔细看。当然领导也不知道要做出个什么样的东西,估计心想着技术人员弄出个什么样的东西来再修改。
年后,对需求开会进行了重新整理,通过思维导图的方式跟领导确定需求,就需求中存在的疑问进行确认。不过后来事实证明经过领导确认的需求也是在不断变更。这次的需求要远比上次复杂,将原来的短信分类从一级短信分类更改为了二级短信分类。公司没有互联网行业产品的经验,美工给出的效果图也仅是从桌面端来考虑移动产品的设计。我最终决定自己开始重新设计软件的界面,参考了众多同类软件,花费的时间比较多。大约到2013年4月底产品已经基本可用,经过我自己的测试和领导的使用效果还算可以。后来发现不支持彩信,经过我的一番研究后发现现有的产品功能要对彩信支持需要耗费非常大的工作量,因为软件本身的功能打破了android系统短信的设计,也难怪了android的应用中没有功能相似的产品出现。这也成为了该项目失败的直接原因。

可行性研究

技术可行性

项目的起源来自领导的短信泛滥,领导想着能够搞一个拦截垃圾短信并对短信进行管理的软件将是酷的一件事情。领导接触过一些android,片面的认为这种方案在技术上可行。我也做了一个调研,发现现有的android软件项目中没有此功能的产品。

经济可行性

支出分析:由于项目主要参与人员仅有我一人,支出的成本较小。收益分析:现在的互联网产品都是先圈用户再考虑盈利,我们这个软件产品也不例外。

运行环境可行性

对于产品的运营存在两种方式:1、跟某地方运营商合作;2、自己单独发布。

总体而言,由于项目较小,对可行性研究基本忽略,没有科学、可观、公证的对项目进行可行性分析。

范围管理

在项目立项的时候仅有一个产品的大致方向,做一款android系统中的短信分类管理类应用。从始至终领导对于产品的理解在不断变化,每次变化都需要修改很多代码,跟领导对于导致产品研发过程做了很多无用功。
在项目进行到一个月后,我认识到了项目这样下去反复修改不可能有好的结果,因此跟领导召开了需求定义的会议,就产品的需求用思维导图的方式就项目的范围进行了定义,会后就需求中没有考虑到的问题跟领导进行了沟通确认。此次会议之后项目的需求基本敲定,虽然之后领导有需求变更的情况,但是相对较少。

进度管理

由于公司初次开展android类项目,对项目的进度很难掌控。项目开始时负责市场部的领导给出了一个月之内完成的计划,我心想我自己加把劲应该能搞定吧,因为最初的项目需求还比较简单。我用了两个星期的时间来学习android知识,用两个星期的时间来开发,结果到最后我才刚根据美工提供的效果图完成了首页,我总是不能很好的估算自己的工作进度。这篇《为什么程序员总是不能准确预估工作量》的文章或许能替我解释些什么。
在项目范围确定之后我对自己工作的进度管理也不够好,第一次尝试做android项目,未知因素太多,编码中总能遇到这样那样的问题。

成本管理

项目的整个成本应该我的人力成本占了绝大部分,本来软件项目中的人力成本就是占的比重较多的,何况主要是我一个人参与的项目,自然我那微薄的人力占了项目成本的大部分。至于后期项目的盈利并没有过多考虑,现在的互联网行业本来就是先圈用户后盈利。

质量管理

我在项目的编码过程中,一般一个小功能完成之后都会进行详尽的功能测试。由于功能不负责,产品的质量只要多测试一般问题就不大了。

人力资源管理

项目的初期我就感觉项目不可能成功,但是领导坚持要做,而且我一个做C++的程序员来写android的代码,我当时居然仅有听从领导的意识,没有适当的表达自己的想法。当时怀着一己之心认为可能一个月项目就完成了,完成之后工作依旧,还可以熟练熟练android的开发,谁知一练手就是做了五个月,而到最后项目仍然没有成型。
由于一直对项目持悲观态度,始终认为我自己开发出来的产品我都不想去用,用户怎么可能去用。长期自己独自做,兼任了产品经理、美工、码农的工作,做的过程中还要担心着需求的变更,在项目进行到中后期的时候, 缺少激励因素,对工作激情不够,这也影响了项目的进度。

沟通管理

在项目的开发过程中基本能保持没完成一个关键功能跟领导汇报一次项目进展。在项目中沟通方面存在不少的问题,尤其以前期严重。前期由于跟领导的需求不一致,导致做了很多的重复性劳动。领导对项目的需求本来就不够明确,一旦有好的想法就加到项目中,这也导致了跟领导的沟通困难,因为这样的沟通效率太低。

风险管理

项目可研阶段对技术可行性分析研究不够,导致了后期在对彩信进行分类处理的时候遇到了技术上的难题。当然前期的可研阶段做充分的技术可行性研究也是不太现实了,因为本来技术对公司而言就是未知的。最好的办法就是对自己不熟悉而且短期内不能实现盈利的项目不参与。

文档管理

由于项目管理中要求每个阶段都有输出的文档,这么繁琐的文档我相信中国找不出几家软件公司能够办到,能办到的估计都已经死掉了。此项目中仅有范围定义的思维导图、我在编写某个具体的功能代码前绘制的流程图,应该说文档及其少。但我目前不认为这有什么不对,我还是坚信某些情况下看代码比看文档要来的更快,特别是看我这种android菜鸟写的代码。更何况此项目的需求在不断随着领导的想法而变化,文档都没法写,写文档的成本太高。

我的一些反思:

在项目中我也存在不少问题。

  1. 项目开始阶段应该向领导表达清楚自己的想法,阐明自己对项目的看法。
  2. 对项目的进度没有很好的把握。
  3. 对技术较为熟悉之后可以适当的学习后期开发功能需要技术,比如一直没有研究彩信的技术实现,导致后期在开发彩信功能时遇到技术问题。如果能够提前学习相关技术就能够提前预支项目的风险。

shell中的环境变量

查看环境变量

  1. 通过env命令可以查看所有的环境变量
  2. 通过echo $环境变量名方式来查看单个环境变量

设置环境变量

export命令来设置环境变量

在程序中该如何获取和设置环境变量呢?

通过main函数的第三个参数

通常大家接触比较多的是两个参数的main函数,实际上还有一个包含三个参数的main函数,第三个参数为包含了系统的环境变量的二级指针。
用法如下:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main ( int argc, char *argv[], char *arge[])
{
while (*arge)
{
printf("%s\n", *arge);
arge++;
}
return 0;
}

实例将会输出该用户的所有环境变量。

通过外部环境变量environ

该变量定义在“unistd.h”头文件中,定义为:extern char **environ;
用法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>

extern char **environ;

int main ( int argc, char *argv[] )
{
char **env = environ;
while (*env)
{
printf("%s\n", *env);
env++;
}
return 0;
}

实例将会输出该用户的所有环境变量。

通过系统函数

在C语言的头文件“stdlib.h”中定义了三个和环境变量相关的函数:

1
2
3
char *getenv(const char *name);
int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);

比较简单,不再举例。

在Linux的头文件sys/mman.h中提供了两个用来分配内存的函数:mmap和munmap,函数定义原型如下:

1
2
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
int munmap(void *start, size_t length);

mmap说明

返回值:内存映射后返回虚拟内存的首地址。
参数:
start为指定的映射的首地址,该地址应该没有映射过,如果为0则有系统指定位置。
length为映射的空间大小,真正分配空间大小为(length/pagesize+1)。
prot为映射的权限,分为四种未指定(PROT_NONE)、读(PROT_READ)、写(PROT_WRITE)、执行(PROT_EXEC)。如果为PROT_WRITE,则直接可以PROT_READ。
flags:映射方式,分为内存映射和文件映射。内存映射:匿名映射。当值为文件映射是后面两个参数才有效。常用的值有:MAP_ANONYMOUS、MAP_SHARED、MAP_PRIVATE。
fd:映射的文件描述符。
offset为从文件的偏移位置开始映射。

munmap说明

从start位置开始释放length个字节的内存。

应用举例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
#include <sys/mman.h>
#include <stdlib.h>

main()
{
int *p = mmap(NULL,
getpagesize(),
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_SHARED,
0,
0);
munmap(p, getpagesize());
}

其中getpagesize()函数的作用为获取一个页的大小,系统默认为4K。

当linux中的函数内部出错时通常函数会返回-1,并且将错误码保存到全局变量errno中,用来表示错误代码。errno全局变量包含在头文件errno.h文件中。下面给出三种打印错误信息的方法。

perror函数

应用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void)
{
FILE *fp ;
fp = fopen( "/root/noexitfile", "r+" );
if ( NULL == fp )
{
perror("error : ");
}
return 0;
}

输出如下:
Permission denied

strerror函数

strerror函数原型为:char *strerror(int errnum);将参数errnum转换为对应的错误码。
应用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void)
{
FILE *fp ;
fp = fopen( "/root/noexitfile", "r+" );
if ( NULL == fp )
{
printf("%s\n", strerror(errno));
}
return 0;
}

输出如下:
Permission denied

printf中的%m打印

应用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main(void)
{
FILE *fp ;
fp = fopen( "/root/noexitfile", "r+" );
if ( NULL == fp )
{
printf("%m\n");
}
return 0;
}

输出如下:
Permission denied

Linux系统中提供了两个在堆中分配空间的底层函数,函数原型如下:

void *sbrk(intptr_t increment);
int brk(void *end_data_segment);

两个函数的作用均为从堆中分配空间,并且在内部维护一个指针,指针的值默认为NULL。如果内部指针为NULL,则得到一页的空闲地址,系统默认为4K字节。指针向后移动即为分配空间,指针向前移动为释放空间。当内部指针的位置移动到一个页的开始位置时,整个页会被操作系统回收。brk为绝对改变位置,sbrk为相对改变位置。

sbrk函数

在sbrk函数中,参数increment为要增加的字节数,increment可以为负数。当increment为负数时表示释放空间。当increment==0时,内部指针位置不动。函数调用成功返回内部指针改变前的值,失败返回(void *)-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
#include <stdio.h>
#include <unistd.h>

int main()
{
int *p0 = sbrk(0);
// 打印堆地址
printf("this site of p0 is : %d\n", p0);

int *p1 = sbrk(1000);
// 这里仍然打印的是第一次的堆地址
printf("this site of p1 is : %d\n", p1);

int *p2 = sbrk(1);
// 打印第一次堆地址+1000后的地址
printf("this site of p2 is : %d\n", p2);

// 回到初始堆地址,释放空间
sbrk(-1001);

int *p3 = sbrk(0);
// 检查是否回到初始地址
printf("this site of p3 is : %d\n", p3);
}

输出如下内容:
this site of p0 is : 264622080
this site of p1 is : 264622080
this site of p2 is : 264623080
this site of p3 is : 264622080

brk函数

在brk函数中,函数作用为将当前的内部指针移动到end_data_segment位置。成功返回0,失败返回-1。
函数使用举例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <unistd.h>

int main()
{
int *p0 = sbrk(0);
printf("this site of p0 is : %d\n", p0);

brk(p0 + 1000);
printf("this site is : %d\n", sbrk(0));

brk(p0 + 1001);
printf("this site is : %d\n", sbrk(0));

brk(p0);
printf("this site is : %d\n", sbrk(0));
}

函数输出如下:
this site of p0 is : 206979072
this site is : 206983072
this site is : 206983076
this site is : 206979072

适用场景:

内存的管理方式比malloc和free更加灵活,适合申请不确定的内存空间的情况,特别适合同类型的大块数据。如果用malloc则可能存在申请内存空间过多浪费的情况,过少时需要重新调用realloc来重新申请内存的情况。速度比malloc快。

前端时间折腾了一段时间的Github博客,终于搞明白了jekyll,开始写了一篇博客发现问题比较多。比如中文编码问题,Github对makedown的支持问题,Github的文章同步问题,网速问题。总体而言,感觉用Github来写博客还不是非常满意。

今天下午偶然想起了前段时间看过faxbox的一篇文章,今天下载下来尝试,果然非常酷。优点如下:

  • 博客放在了Dropbox不会丢失。当然没有Github酷,Github可以通过版本管理来查看历史版本。貌似Dropbox本身有版本管理的功能。
  • 写博客的方式简捷。Github的jekyll还需要利用rake命令来创建文章,这样才能保证文章的头部含有YAML标签。而faxbox直接编辑文本文件即可。
  • 文本编辑器给力。可以通过实时预览功能查看makedown的解析情况,当然也可以通过在线的makedown编辑器stackedit来编写makedown。
  • 网速比Github快。测试一下感觉网速还比较快。
  • 可以修改模板,fork模板的方式简捷更酷。
  • 对中文的支持好。因为本来就是本土化的软件当然对中文全力支持。
  • 代码语法高亮。测试了一下默认的模板支持语法高亮。
  • 自带的模板更漂亮。自带的模版虽然不多,但是有说明是适合博客还是相册。
  • 网站自带网站分析工具。可以通过简单的浏览网页就可以知道网站的访问情况,虽然farbox在访问量达到1W之后要收费,按照目前的价格,但是我个人觉得比较值,希望farbox能够一直坚持做下去。做的好我愿意付费。

缺点:

  • 经过一下午的了解,暂时没有发现博客可以分类的功能,不过这个功能我暂时可以不需要。以后准备先写些博客在Github和Faxbox上同步更新,以便多比较比较这两种Geek的写博客方式。后来发现是有文章分类功能的,具体操作为将文章放入不同的文件夹中,文件夹的名字即为分类的名字,这种方式比jekyll的YAML语法方式要好用。
  • 感觉评论功能有点鸡肋。博客还没写完有事工作,准备续写时发现有人留言指正错误,想回复谢谢,发现无法直接回复。用多说代替之,方法为直接将多说的js脚本存入新建的comment_js.md文件中即可,非常简洁。
  • Linux下的用户不太方便。由于farbox软件仅有windows版和mac版,dropbox在linux有相应版本,在linux下的用户不能享受到farbox编辑工具带来的方便。相反在linux下结合git和vim来编写博客却比较方便。

PS:
Github博客地址:kuring.github.com
Fixbox博客地址:kuring.faxbox.com

本文是在安装完成Hadoop的基础之上进行的,Hadoop的安装戳这里
本文采用的Hadoop版本为0.20.2,HBase版本为0.90.6,ZooKeeper的版本为3.3.2(stable版)。
本文仍然采用了Hadoop安装的环境,机器如下:

机器名 IP地址 用途 Hadoop模块 HBase模块 ZooKeeper模块
server206 192.168.20.6 Master NameNode、JobTracker、SecondaryNameNode HMaster QuorumPeerMain
ap1 192.168.20.36 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain
ap2 192.168.20.38 Slave DataNode、TaskTracker HRegionServer QuorumPeerMain

安装ZooKeeper

由于HBase默认集成了ZooKeeper,可以不用单独安装ZooKeeper。本文采用独立安装ZooKeeper的方式。
1. 将zookeeper解压到/home/hadoop目录下。
2. 将/home/hadoop/zookeeper-3.3.2/conf目录下的zoo_sample.cfg文件拷贝一份,命名为为“zoo.cfg”。
3. 修改zoo.cfg文件,修改后内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
dataDir=/home/hadoop/zookeeper-3.3.2/zookeeper_data
# the port at which the clients will connect
clientPort=2181
dataLogDir=/home/hadoop/zookeeper-3.3.2/logs
server.1=192.168.20.6:2888:3888
server.2=192.168.20.36:2888:3888
server.3=192.168.20.38:2888:3888

其中,2888端口号是zookeeper服务之间通信的端口,而3888是zookeeper与其他应用程序通信的端口。
这里修改了dataDir和dataLogDir的值。
需要特别注意的是:如果要修改dataDir的值不能将原来的行在前面加个“#”注释掉后在后面再增加一行,这样是不起作用的。可以参考bin目录下的zkServer.sh文件中的72行,内容如下:

1
ZOOPIDFILE=$(grep dataDir "$ZOOCFG" | sed -e 's/.*=//')/zookeeper_server.pid

这里通过grep和sed命令来获取dataDir的值,对于行前面添加“#”注释是不起作用的。
4. 创建zoo.cfg文件中的dataDir和dataLogDir所指定的目录。
5. 在dataDir目录下创建文件myid
6. 通过scp命令将zookeeper-3.3.2目录拷贝到其他节点机上。
7. 修改myid文件,在对应的IP的机器上输入对应的编号,该编号要和zoo.cfg中的一致。本例中在192.168.20.6上文件内容为1;在192.168.20.36上文件内容为2;在192.168.20.38上文件内容为3。至此ZooKeeper的安装已经完成。

运行ZooKeeper

1. 在节点机上依次执行/home/hadoop/zookeeper-3.3.2/bin/zkServer.sh start脚本。运行第一个ZooKeeper的时候会因等待其他节点而出现刷屏现象,等启动起第二个节点上的ZooKeeper后就正常了。运行完成之后该脚本会出现刷屏现象,我这里没有理会。
2. 通过jps命令来查看各节点机上是否含有QuorumPeerMain进程。
3. 通过/home/hadoop/zookeeper-3.3.2/bin/zkServer.sh status命令来查看状态。本例中有三个节点机,其中必有一个leader,两个follower存在。
4. 在各节点机上依次执行/home/hadoop/zookeeper-3.3.2/bin/zkServer.sh stop来停止ZooKeeper服务。

配置时间同步ntp服务

HBase在运行的时候各个节点之间时间不同步会存在莫名其妙的问题,这里选择以192.168.20.36机器作为时间同步服务器,其他机器从该机器同步时间。
在192.168.20.36上通过service ntpd start命令来启动ntp服务。
在其他机器上配置crontab命令,增加下面一行:

1
0 */1 * * * /usr/sbin/ntpdate 192.168.20.36 && /sbin/hwclock -w

这里采用一个小时同步一次时间的方式。

安装HBase

1. 在HMaster机器上将HBase解压到/home/hadoop目录下。
2. 修改配置文件hbase-env.sh,使export HBASE_MANAGES_ZK=false。如果想要HBase使用自带的ZooKeeper则使用设置为true。使export JAVA_HOME=/usr/java/jdk1.6.0_10来指定java的安装路径。
3. 修改配置文件hbase-site.xml如下:

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
<configuration>
<property>
<name>hbase.rootdir</name>
<value>hdfs://server206:9000/hbase</value>
</property>
<property>
<name>hbase.cluster.distributed</name>
<value>true</value>
</property>
<property>
<name>hbase.master.port</name>
<value>60000</value>
</property>
<property>
<name>hbase.zookeeper.property.clientPort</name>
<value>2181</value>
</property>
<property>
<name>hbase.zookeeper.quorum</name>
<value>server206,ap1,ap2</value>
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/home/hadoop/zookeeper-3.3.2/zookeeper_data</value>
</property>
<property>
<name>dfs.support.append</name>
<value>true</value>
</property>
<property>
<name>hbase.regionserver.handler.count</name>
<value>100</value>
</property>
</configuration>

4. 修改regionservers,添加HRegionServer模块所运行机器的主机名。在本例中内容如下:

1
2
ap1
ap2

5. 为了确保HBase和Hadoop的兼容性,这里将/home/hadoop/hadoop-0.20.2/hadoop-0.20.2-core.jar文件复制到/home/hadoop/hbase-0.90.6/lib目录下,并将原先的Hadoop的jar文件删掉或重命名为其他后缀的文件。
6. 将hbase-0.90.6文件夹通过scp命令复制到其他节点机上。

HBase的启动

  1. 在Hadoop的NameNode所在的机器上使用start-all.sh脚本来启动Hadoop集群。
  2. 在各个节点机上调用zkServer.sh脚本来启动ZooKeeper。
  3. 在HMaster所在的机器上使用start-hbase.sh脚本来启动HBase集群。

HBase启动后会在HDFS自动创建/hbase的文件夹,可以通过hadoop fs -ls /hbase命令来查看,该目录不需要自动创建。如果在安装HBase的过程中失败需要重新启动,最好将此目录从集群中删除,通过命令hadoop fs -rmr /hbase来删除。

需要特别注意的是在hadoop集群中hadoop fs -ls /hbase目录和hadoop fs -ls hbase目录并非一个目录,通过hadoop fs -ls hbase查看到的目录实际上为/user/hadoop/hbase目录。

HBase管理界面

Master的界面:http://192.168.20.6:60010/master.jsp
RegionServer的界面:http://192.168.20.36:60030/regionserver.jsp和http://192.168.20.38:60030/regionserver.jsp

参考资料

下载链接

http://pan.baidu.com/share/link?shareid=1235031445&uk=3506813023 提取码:v8ok

0%