2009-04-26

MySQL Query cache笔记


有点麻烦,有点难懂.
尽管如此,还是记下目前所理解的大致情况.

首先是两个数据结构.


// sql/sql_cache.h
struct Query_cache_memory_bin
{
Query_cache_memory_bin() {} /* Remove gcc warning */
#ifndef DBUG_OFF
ulong size; //基本的size大小,实际上,free_blocks存放的接近size大小的block.大于前一个bin而小于后一个bin
#endif
uint number; // free_blocks 里的数量
Query_cache_block *free_blocks; //存放相似size大小的block

inline void init(ulong size_arg)
{
#ifndef DBUG_OFF
size = size_arg;
#endif
number = 0;
free_blocks = 0;
}
};

struct Query_cache_memory_bin_step
{
Query_cache_memory_bin_step() {} /* Remove gcc warning */
ulong size; //bin的大小
ulong increment; //bin之间的便宜量
uint idx; //bin群中,第一个关联此step的下标
inline void init(ulong size_arg, uint idx_arg, ulong increment_arg)
{
size = size_arg;
idx = idx_arg;
increment = increment_arg;
}
};



query cache的大概结构式这样的.
+----------------------------------------------------+
| step array 当作cache的一级索引 |
| 存放的主要信息是某一个size大小的bin群起始的位置偏移 |
+----------------------------------------------------+
| bin array 当作cache的二级索引 |
| 存放的主要是size大小的bin群组 |
+----------------------------------------------------+
| 实际的cache 区域 |
+----------------------------------------------------+

对于cache的step和bin的一些换算关系如下
current_bin_count = ( pre_bin_count + QUERY_CACHE_MEM_BIN_PARTS_INC ) * QUERY_CACHE_MEM_BIN_PARTS_MUL;

简单地说,在一级索引step里,下级cache单位块的数量按上级数量的基础,加上一定的增量,以某种倍数增长.

至于大小方面,有
current_bin_size = pre_bin_size >> QUERY_CACHE_MEM_BIN_STEP_PWR2
也就是上级cache的单位块按照一定数量递减.

查看源代码计算一下,可以知道,
current_bin_count * current_bin_size <= pre_bin_size.
意思是,在进行进一步分块的时候,尽管块的数量增加了,但是总量还是小于等于上一级的块的大小.

接着,要提提实际cache的分布了.

源代码里有这么一个图.
sql/sql_cache.cc
For example:
query_cache_size>>QUERY_CACHE_MEM_BIN_FIRST_STEP_PWR2 = 100,
min_allocation_unit = 17,
QUERY_CACHE_MEM_BIN_STEP_PWR2 = 1,
QUERY_CACHE_MEM_BIN_PARTS_INC = 1,
QUERY_CACHE_MEM_BIN_PARTS_MUL = 1
(in followed picture showed right (low) bound of bin):
| 100>>1 50>>1 |25>>1|
| | | | | |
| 100 75 50 41 33 25 21 18 15| 12 | - bins right (low) bounds
|\---/\-----/\--------/\--------|---/ |
| 0 1 2 3 | | - steps
\-----------------------------/ \---/

这图一开始也没明白.
后来似乎懂了.
假设cache块的大小事100.
按照公式计算,每次分块,大小减去一半,数量按照公式计算取整.
50>>1是一次分块,得到块的大小是25.根据公式计算分为( 2 + 1) * 1 = 3块.
于是从25开始,以8为增量划分块.

增量的计算也是有公式的.
inc = (prev_size - mem_bin_size) / mem_bin_count
以50>>1这个例子来说.
pre_size = 50 , men_bin_size = 25 , men_bin_count = 3
所以增量是8.

如何解释这个增量公式呢.
首先,每次mem_bin_size是折半递减的.
那么,从内存分布上来说,prev_mem_bin_size的大小恰好是当前bin群起始的偏移量.
只要明白这就那个增量公式就好理解了.
先得到bin群的偏移量起点,然后计算当前bin大小的偏移量,就能正确分布了.

但,既然在bin的结构里已经有了size这个成员用来表示bin的大小了,那何必用inc来计算偏移呢.

挣扎了几天给自己的解释是,这是它查找bin的算法决定的.

注意到代码里的注释部分,有这么句话:
When a new block is allocated we find most suitable memory block
(minimal of >= required size). If such a block can not be found, we try
to find max block < required size

也就是在寻找合适的bin的时候,他会后弦尝试寻找合适大小的bin块,当找不到的时候,会在不满足大小的最大的bin群里寻找.
那么,既然大小不满足,为什么要向小的群里找而不是大的群里找呢.

看它寻找bin的算法.


// sql/sql_cache.cc
uint Query_cache::find_bin(ulong size)
{
DBUG_ENTER("Query_cache::find_bin");
// Binary search
int left = 0, right = mem_bin_steps;
do
{
int middle = (left + right) / 2;
if (steps[middle].size > size)
left = middle+1;
else
right = middle;
} while (left < right); //采用折半查找的方式,在step数组里寻找合适的位置.按照降序排列
if (left == 0)
{
// first bin not subordinate of common rules
DBUG_PRINT("qcache", ("first bin (# 0), size %lu",size));
DBUG_RETURN(0);
}//left是不满足size的最大块组,即,应该是满足点的右方
uint bin = steps[left].idx -
(uint)((size - steps[left].size)/steps[left].increment);
//idx可能是step结束的bin的下标.因此后半部计算的是size多出bin_size部分占的大小.也就是说,其实是从小于的step部分取出来的.
DBUG_PRINT("qcache", ("bin %u step %u, size %lu step size %lu",
bin, left, size, steps[left].size));
DBUG_RETURN(bin);
}


过程是寻找不满足大小的第一个bin群(bin群按照降序排列).
此时,size必然是大于left的最小大小,但是小于right的最小大小.

因此,只能在left的群组里以增量方式寻找合适的大小.

这样,这个有些古怪的cache结构及大致记录完毕了.

总结一下,其实或许应该称这个cache结构拥有二层索引.
第一层是一个step范围索引,索引了某个区间范围的bin组.
第二层是一个bin,增量范围索引,索引了bin组内,各个增量的cache快.
+-----+
| | <---step数组 +----------------------+
+-----+ | size = bin | <--- bin 数组
|size | =============> +----------------------+
+-----+ | size = bin + incre |
| | +----------------------+
| | | .... +
| | | |
+-----+ +----------------------+

bin里各自维护了各自大小的block链表.

2009-04-24

闲话


一堆GoogleReader上的一堆RSS终于让我开始想关于如何理财的问题了.

确实,过去,对于钱财,一向是没有注意.
不知道如何去花,也不知道花在了哪里.
甚至有时候不知道花了多少.

想想,这其实是有些可怕的.
你做的事情,你自己不能掌控.
就好比开飞机的不清楚开飞机的状况,随时都有坠机的危险.

起了分析自己财务状况的念头,便开始向关于理财的东西.
在网上找的时候发现了这个网站.

网上账本这个东西,其实也有想过.不过迟了点.
在理财观念觉醒的时候,就萌生了这个想法,当时还觉得应该是一个创意.
不曾想,早有人在06年就弄出来了.

翻了下关于网大账本的东西,发觉不仅早已有之,还曾经跟滔滔有过一段难言的过去.
顺手回滔滔看了下,确实有记账服务的影子,不过已经改为了'我的秘密'.
想来腾讯是放弃了这方面的业务拓展.

毕竟,就腾讯目前的客户群定位来说,理财还不能作为吸引人的项目.
或者是因为这东西确实不太好找盈利模式.

稍微浏览了一下网大账本打内容,似乎没有太多打广告或者增值服务.
但是想到06年便开始了这东西,有些好奇,其是如何经营下来的.

不管怎样,从这里开始,开始注意分析自己的消费行为了.
在有限的资金情况下,知道如何流动才是合理安排收支的开始.
如果连全局都不能掌握,细节肯定是失败了.

做了这么多年失败的人,或许是该警觉一下了.

还有新的观念跟理财有关的,那便是股票.

虽然接触股票很早地就在"日程计划"里,只是一直都都没有付诸行动.
一直在所谓的经济学前提下徘徊.

或许这里应该感谢一下两个月前的生活.
多少改变了自己的一些东西.

实用或许渐渐成为自己的一个倾向.
或者说,实践.

也许有些迟,但总比不改要好得多吧.

不知道什么时候能真正的掌握自己的资金流动.
从财务上成熟起来.

也许应该快点,感觉时间有些不等人了.

或许是真的老了.
开始担心时间了.

2009-04-20

SUN卖出去了


收购SUN的不是IBM,而是对头Oracle.

备受财政状况困扰的SUN,在IBM频频发出收购的信号的时候,扭扭捏捏.
惹得IBM重新审视SUN的价值.

重于,IBM发觉,虽然java很诱人,但是,IBM和SUN有太多地方时冲突的.即是收购了SUN,估计自己也会消化不良.况且,收购传闻之初,就操收到反垄断调查.
所以,IBM还是放弃了收购SUN的计划.

这时的SUN处于一个尴尬的地步.
就像当初微软打算收购雅虎一样,起初是雅虎羞答答地欲拒还迎,后来微软不要了,自己送上门去却碰了一鼻子灰.

就在人们猜测SUN是否也会如同雅虎一样的下场的时候,oracle出手了.

在IBM打算收购SUN的时候,oracle就打着小鼓了.
估摸着手里那么多的java东西,一旦咨询对头IBM拿下了java,再用java的话,就等与将自己置于IBM之下,处处受制.
倘若要换技术的话,一来成本太高,也不太现实.
二来,这个oracle的风格不同.

在现在各大巨头都在云里来雾里去的时候,oracle依然固执着它的数据库.
云这类概念新产物向来是oracle不感冒的.

静待着IBM渐渐失去兴趣的时候,快速出手将sun拿了下来.
估计这回oracle现在正躲在墙角偷笑.

本来,没有sun的话,顶多oracle能在咨询跟IBM抗衡,到了硬件方面,oracle只能算是个小辈.
虽然有基于一些操作系统级别的东西,但是,与蓝色巨人来对比的话,实在是太渺小了.
而sun,伟大的sun,服务器,软件,操作系统,芯片,无一不是在各自领域独领风骚的.
在sun传收购风闻的时候,还很多人感叹这个小巨人落得个今天这个下场.

前面已经说过,就产品线来说,SUN跟IBM是互有重叠的,只是SUN的一些坏脾气,以及对开源的过度乐观,导致了SUN即使握着java这个利器,也难以走出经济困境.

现在,oracle拉了sun一把,从此,oracle有在其他领域跟IBM抗衡的资本了.
这不能不谓IT界的一个大事件.

以后的格局将不是现在所能想象的.
oracle和sun结合,使得oracle在硬件方面肯定会有一定程度的表现.
这不太重要.
因为本来SUN和IBM虽然技术交叉,但毕竟客户群不太一样.
在硬件方面,大概就是维持目前这个状态.

关心的应该是Java.

Java也曾经是IBM力推的东西.
想想eclipse,价值几十万美元的东西慷慨地捐赠出来,为的什么.
就是为了促进Java的发展.
试想,没有一款好用的IDE,java能像现在这么普及么?
或许可以,但是可能花的时间更久.

而看看oracle.
手下一大批跟java相关的唱片.
可以看出,oracle也是对java很热心的.

当两个宿敌都用同一个东西,而这个东西是中立的时候,天下太平.
但是,当其中一方拿到java之后,情况会如何呢?

就像当初IBM欲收购SUN时,人们对oracle的猜测那样,现在,或许应该轮到对IBM的猜测的.

不同的是,IBM可以不靠java生活,但是,IBM对java的影响是不容小看的.

IBM会如何对待oracle收购后的java呢?
或许会看oracle先怎么做.

如果oracle很绅士地保持现在java的状态,一切太平.
IBM也不会小气到从此不理java这孩子.

问题是,oracle会如何影响java的发展.
在新的标准的制定上,会不会有私心.
或者说,会不会借java做一些对自己有利的标准建议.
这是个问题.而且是个大问题.

java流行很大程度是因为当初它一次编译到处运行的美梦.
某种程度上说,java是依靠着开放性在存活着.
之前的跨平台愿景,后来的开放愿景,都是开放在促进着java的成长.
甚至后来的C#.net也算是某种程度上的开放促使的.
想想如果不是开放微软也不会想淌这趟浑水,也不回想用IE的老路将java灭亡,也不回后来自立门户跟java竞争,使得java不断逼着进步.

如果java变得不那么开放了,或许java就真的快死了.
何况,现在语言越来越多.
混合语言已经将软件开发进化到扩展性为王的时代.

一个语言如果不够开放,那么别人即是想与你交互,也是很难的.
何况,别人可以选择无视你的存在.
那么,在互操作上,一个自闭的语言就没有理由成为一个成功的语言.

到底java命运如何,到底SUN能不能帮oracle跟IBM正面抗衡.
时间会说明一切.

2009-04-19

东邪西毒终极版


难怪很多人会说,在看了终极版之后,才终于明白东邪西毒再讲述些什么.

确实,相对东邪西毒,终极版的人物事件的脉络清晰了很多.

在古龙式的背景和叙述手段下,其实人物的性格在本质上不会相差很大.
至少,从影片的叙述角度来说,古龙人物说话语气不会差太多,而且表达方式也不是很直接.

在略带灰黄的荧幕上,有时候,真得很难分清那时张国荣还是梁朝伟.

其实现在看来,这应该是部很简单的片子.
一群爱情关系复杂的人,在叙述着彼此交叉而独立的感情过程.

无非是表达着一个古老的命题,所谓的感情是没有原因的.

太过纠缠情感,就像慕容嫣一样,难以自拔.
在情感的似是而非中无从抉择.
明知道结果,却依然不愿意相信,编造着各种理由以给自己期望.

人有时候就是喜欢麻醉自己.
如同影片反复提到的一句话:记性太好,烦恼就会多.
太过执着纠缠,后果通常只是伤神收场.
于事实不会有太大的影响.

单纯才是所谓快乐的源头.
孤女单纯的坚持,洪七为饭而生活,桃花的无忧无虑.
这都是片子里没有纠结的人物.

西毒大嫂的一句话,或许可以为片子人物的情感做一些注脚:
"明明心里想要,但是嘴巴又不肯讲,一定要你送到面前才行."

太过聪明的人,或者太过自信的人,通常有些自负,不愿意轻易求人.
于是,有时候,机会就在眼前,但是他不会去争取.
因为他会去估量,回去推测,他能找到大概自己去争取的话会得到什么结果.
所以,通常结果是他们不会去主动靠近什么.
如同西毒的一句话,如果你不想被人拒绝,那就先拒绝别人.

他用这句话解释自己离开白驼山庄的缘由.
而其实,这也是他最终没对大嫂说出口的理由.
西毒是个聪明人,但是越是聪明的人越怕被拒绝,况且,他知道,她是喜欢他的.

中国有句话叫聪明反被聪明误,说的大概就是这种情况.
偏偏她也是个聪明的女子.
"最初都由着他,渐渐也就不想理他"
在本应该是非理性博弈的感情中,双方都在用猎人的眼光审视对方.
在等待,等待一个合适的时机,而偏偏两个人等的都是对方的先行动.
于是,最后西毒离开白驼山,她成了大嫂.

所以,有时候,许多事情不光是合适就行,重要的还是争取.

另一个占了较大篇幅的角色应该是剑客,也就是梁朝伟那个角色.
从剧情上可以知道,他是桃花的丈夫,而桃花是桃花岛黄药师喜欢的人.
暂且放下黄药师这个人物.

剑客角色想要表达的是等待.
他说自己三十岁前就会失明,在失明的时候,他想回家,回去看看桃花.
但是,他此生在没有回到桃花岛.

在漫长不定的等待里,他终于失去了阳光,失去了再看桃花的机会.
在从他失明的那天起,他角色的使命就已经结束了.
死,只不过强调了等待的意义.

有时候能等不是一件坏事,坏就坏在你不知道在等什么.
或者,你不知道什么时候能等到.
而与此同时,时间对你来说又有着特殊的意义.
时间的流逝,在消耗着你的其他东西.
为了一个不明确的东西,在不断地付出另外一些不可挽回的东西.
这是应该考虑的问题.
考虑值不值得.

终于,他的死为这个考虑做出了最好的解释.
他只能将桃花留在心里.
他再也看不到桃花了.
他不愿放弃,但又不能不放弃.
所以他即使看不见了,依然点着盏灯.
当他去杀马贼的时候,强吻了孤女.
因为他要记住桃花.
因为他要想桃花道歉.

孤女与他来说只是一个替代物而已.

某种程度上说,死在马贼手上是他自己选的归宿.
不能等到自己想要的,至少也要做自己能做的.

再看看东邪.
有些奇怪为何叫东邪西毒.
西毒是故事的主脉络,而东邪呢?
故事是由西毒叙述的,将他的名字挂载影片名字里无可厚非.

但是,东邪本身的出场次数不多,为何要将其放在片名?
仔细想想,如果说西毒是串起了故事,那么东邪就是串起了人物.
东邪喜欢的女人叫桃花,桃花是剑客的妻子.同时,也因为桃花的原因,东邪和西毒的大嫂有接触.
而也因此,东邪将桃花岛和沙漠联系了起来.
在沙漠这边,东邪会定期找西毒,而西毒又将洪七等人物串了起来.

所以,可以说,东邪其实是故事人物线的中心.
同时注意到他往返的两个地方:
沙漠和桃花岛.

这也应该算是个有意思的地方.
故事里,女人主要在桃花岛,而男人主要在沙漠.
就场景意向来说,桃花是一个比较鲜艳柔性的印象,而沙漠则是刚性粗狂的印象.
或许是有意安排吧.

东邪穿插与这两个地方,充当着交流的信使.
注意到东邪在故事中的主要作用.
一是慕容嫣的故事.
另一个是醉生梦死.

东邪调戏了慕容嫣.
最后却又躲开了慕容嫣.
理由是他喜欢的是桃花.

在拿着醉生梦死再次离开桃花岛去沙漠的时候,东邪明白了女人.
跟慕容嫣的对话让他明白,其实自己的下场会跟西毒一样.
虽然,西毒是因为不愿,而他是不能.
如他自己所说,从一开始他就是输的那个.

也因此,他将醉生梦死带到了沙漠,自己喝了一半,将另一半留给了西毒.
希望西毒能明白.
然后回到桃花岛,解开束缚成为东邪.

东邪希望西毒明白的,西毒没有明白.
直到很多年后,西毒接到大嫂死讯的时候,西毒才开始寻找醉生梦死.
也此发觉西毒根本不是因为喝了醉生梦死才忘记的.
而是以为东邪知道,纠缠明知不可挽回的东西,于人于己都没有好处,不如放弃.
西毒也才明白,为什么东邪要告诉他喝了醉生梦死能够忘记很多东西.

以为醉生梦死并不能帮西毒忘掉过去,不能帮西毒逃避.
所以,西毒明白了.
这个世界,根本没有逃避就能解决的问题.

于是,他回到白驼山庄,回到了他曾经逃避的地方,成为西毒.

总的来说,这是一部看似爱情又非爱情的片子.

或者说,其实很多道理都是一样的.
不管放在哪里,道理还是道理.

2009-04-18

Blogger标签云


发现标签云有些意思.
把单调的标签变得不那么死板了.

于是到处找可以用于blogger的插件.
找到一些,但不是很满意.

于是自己动手写一个简单的先用一样.
当然,思路来源于这个Blog的介绍,在此声明一下.

下面是自己写的,或者说改的标签云.


function getColor( _count , _count_upper_limit )
{
var begin_color = 192.0;
var end_color = 26.0;
var count = _count/1.0;
var count_upper_limit = _count_upper_limit/1.0;

var color = begin_color - (count/count_upper_limit) * (begin_color-end_color);
return [ Math.floor(color) , Math.floor(color*20.0/26.0) , Math.floor(color*5.0/26.0) ];
}


function getFontSize( _count , _count_upper_limit )
{
var begin_font_size = 0.4;
var end_font_size = 2.2;
var count = _count/1.0;
var count_upper_limit = _count_upper_limit/1.0;

return ( begin_font_size + (count/count_upper_limit) *( end_font_size - begin_font_size) );
}

// Max label count;
var maxLabelCount = 0;

var LableUrls = new Object;


var LabelObject = new Object;
var name = "";
var count = "";
var url = "";
LabelObject['name'] = name;
LabelObject['count'] = count;
LableUrls[ url ] = LabelObject;


for( url in LableUrls )
{
if( LableUrls[url]['count'] > maxLabelCount )
{
maxLabelCount = LableUrls[url]['count'];
}
}

for( url in LableUrls )
{
font_size = getFontSize( LableUrls[url]['count'] , maxLabelCount );
colors = getColor(LableUrls[url]['count'] , maxLabelCount );
outputString = "<a href='" + url + "'" + "style='padding-right:0.5em;font-size:" + font_size + "em;color:" + colors[0] + "," + colors[1] + "," + colors[2] + ";'>" + LableUrls[url]['name'] + "</a>";
document.write( outputString );
}


使用的时候将blogger的模板展开,并将上面的内容插入到下面的块中.
getColor和getFont是用来产生颜色和字体大小的,可以感觉自己需要改.
两个参数的意义上,_count为传入标签的个数,_count_upper_limit为最大的标签个数.

举个例子,比如有个名为 杂谈 的标签,这个标签下有4个文章,那么_count = 4.
_count_upper_limit就是所有标签中,_count最大的那个值.

上面例子outputString里将诸如"'<>等符号转移了,免得更新模板的时候出问题.

当时写的时候就是因为这个搞了不少时间.
不过算是学了点JavaScript和CSS : ).















WOW代理权之网易


网易从九城手里拿走了WOW.

然后广大的WOWer为网易此举欢呼.
一来,长久以来对九城的怨气终于得到痛快淋漓的发泄.
二来,是以网易的实力,大家都觉得应该会比九城运营地好.

对于网易的实力,或者说财力,应该是不容质疑的.
在正式宣布取得代理权的同时,网易也发话了.
将不购买九城的服务器,而以现在的标准配备投入新的服务器组.
这是一笔大的投入.

一方面显示出了网易的阔绰,另一方面,也算是对暴雪展示实力的一手.

但是,这与能不能争取到WLK无关.
尽管,WLK审批受阻也许是九城知道不久将失去WOW所以没有卖命公关的结果.
但是,能否已经WLK也算是广大玩家对网易的一点小考验.

除此之外,在WOWer对九城的恨慢慢淡去的时候,能否对网易产生爱是一个问题.
网易能否比九城运营得好,这个或许是个认真考虑的问题.

网域与九城不同,网易自己拥有研发实力,而且,其技术力量在国内算是数一数二的.
在网易扎根的WOW,能否服网易的水土,是个需要时间才能回答的问题.

人们之所以对九城有怨言,是因为九城的服务质量不行.
这跟九城作为吸金工具的角色有关.

当WOW到了网易,如果也只是赚钱工具的话,估计也不回比九城好到哪里去.

想想网易手里的其他几款游戏.
以及网易倾入大量心血的天下贰.

很明显,天下贰有着WOW的影子,在收入WOW之后,天下贰将如何?
或者说,WOW将如何.

这是个和微妙的问题.

相同题材,相似体验.
不同的是一个是点卡游戏,一个是道具游戏.

相信,有相当一部分天下贰玩家是从WOW转过去的.
在网易拿到WOW之后,这些玩家会不会回到WOW,这应该是个值得考虑的问题.

回答WOW就意味着离开天下贰.
也就意味着天下贰玩家的流失.

没有玩家玩的游戏,只能寿终正寝.

问题是,网易会让天下贰慢慢死去,死在自己的手上么?
网易会为了别人的孩子,而杀了自己的孩子么?

网易不会.
以丁磊的境界来说,WOW只是他的棋子,不像九城,当成救命稻草.

但是,玩家不是丁磊,不会说,为了支持国产而拒绝网易的WOW.
那么,网易将如何留住天下贰的玩家?

从现在网易对WOW的大手笔来看,网易似乎是在做一种姿态.
就是,我不是九城,别拿我的WOW跟九城的WOW相比.
这是在告诉暴雪,也是在告诉玩家,网易的WOW跟九城的WOW不是一个档次的.

这样的做法一来稳住了现有玩家的心,就是将来数据交接出现问题,玩家也愿意因为网易这个名字而不去太多计较.
二来,想原先因为九城原因离开WOW的玩家发出回归的信号.
三是向之前对九城没信心而徘徊在WOW之外的玩家招手.

网易的如意算盘就是如此打的.
在留住原有资源的基础上,回收丢失的资源,同时不忘开辟新的玩家群.
这样做的直接目的就是尽可能地做大WOW.

做大WOW对网易有什么好处?
明显的好处就是收入的增加.
单靠一个WOW都能将九城扶起来,那么在网易的运营下,WOW带来的收入,更是不可估量.

网易不缺钱,网易也不求钱.
那为何网易要花大力气去将WOW拿过来呢?

难道网易是因为看到九城玩家水生火热的生活想要解救其出来.
自然不是,那么,网易接手WOW的目的并不是要多拿一个游戏代理而已.

回到之前谈到的天下贰.
天下贰和WOW式同一类型的游戏,这在前面提到了.
而WOW的壮大,也必然多少会对天下贰产生影响.

那么,网易愿意用天下贰换WOW的用意就有些明显了.
用天下贰换WOW.
这无论从哪方面来说,都是一个不错的买卖.

首先,盈利能力不是一个档次的.
但,前面也说过,钱对于网易来说不是问题.
网易也不是那种见钱眼开的货色.

网易求的是技术.

黏上暴雪的网易,在运营WOW的时候,必然会和暴雪在一些技术方面有所接触.
至少,在运营经验和方式上能得到不少帮助.
这个是可以消化到自己的其他类型的游戏甚至其他领域去的.

再谈天下贰.
虽然,表面上,天下贰以为WOW牺牲了.
但是,一个天下贰倒下了,会有千千万万个天下贰站起来.

网易的民族网游梦,网易的技术梦,在暴雪和WOW的金钱号召力下,绝对会加速.

所以,对于天下贰和WOW的关系.
对于代理和自主研发的关系.
恰好跟表面上看起来是相反的.

网易是在用WOW养"天下贰".
用代理养自主研发.

所以,网易的WOW绝不是九城的WOW.
这是毫无疑问的.

WOW代理权之九城


网易,九城,暴雪,WOW.
毫无疑问的,这是这几天的热门关键字.

九城失去了WOW,作为广大的WOW玩家,同时也是九城的用户,却鲜少有为九城惋惜的.
一致的论调是九城自作自受.

在网友的打趣中,九城不幸言中了自己的身世,接近九成的收入来源于WOW.
而失去WOW的九城,能否挺过来,尤其是在这个金融冬天里.
失去了WOW这个收入源,依靠其他游戏的九城,是否会逐渐淡出人们的视野,沦为二流代理,甚至消失?

想来,九城的下场或许应了中国的一句古话,得道多助,失道寡助.

但是,话说回来,现在WOWer们的焦点还刚刚从九城身上移开.
对九城的幸灾乐祸,或许只是对过去九城不满的幸灾乐祸而已.

在九城将逝,网易未来的时候,WOW依然还是九城下面的WOW.

期待久远的WLK能否在网易手上通过审批是个未知数,网易能否比九城运营得好也是个未知数.
也许,在不久,就会有人开始怀念九城.

毕竟,网易不同于九城.

九城只是个代理商,尽管有自己的研发团队.
但是,看看朱骏和上海申花.
可以想到,九城或许只是支撑了大部分或者整个申花.
在没有了WOW九成的收入之后,申花将何去何从也是个问题.

纵观九城手里的牌:
FIFA Online,奇迹世界,卓越之剑以及其他几款游戏.
能替代WOW地位的,或许只有依靠EA了.

也许,朱骏很早就已经想摆脱暴雪的束缚了,也或许,很早开始,暴雪就开始考虑寻找新的代理商.
于是九城和EA的合作变得顺理成章.

不管怎样,九城失去WOW的一个结果是,有可能将暴雪和EA这对宿敌的战场带到网游.
毕竟,在自己还没有拿的出手的网游之前,EA是九城的靠山.

值得注意的是,就在网易宣布获得代理权的时候,朱骏放了几句很有意思的话.
说无论研发部门有什么要求都无条件答应.
这可以看为是朱骏的一句气话,也可能是实话.

九城从WOW也赚了不少钱.
可惜错在投在了申花.
也许代理权转移这件事,能让九城明白,不是自己的孩子,总有一天会走的.

想当年,九城也是意气风发地从久游手里拿走了劲舞团.
如今,轮到自己.
不知道九城心里有什么准备.

或者说,九城有什么打算.
现在开始自主研发是来不及了.
很快,6月九会到来,很快,九城的收入就会锐减9成.
这是个很现实,也是很严峻的问题.

从之前九城的动态来看,九城很早就做好了失去WOW的准备,在积极寻找WOW的替代品.
只是,一直没有成功.

或许,我们不久将看到九城末日的回响.

不管怎样,从九城失去WOW这件事里应该看到,在网游大量吸进,运营商在忙着数钱的时候,也改考虑下退路和发展前景.
不然,养大的孩子,最后跑了的时候,那才是欲哭无泪.

2009-04-14

load_defualt


感觉条件,或从默认配置文件里读取参数配置,或从指定配置文件里读取参数.

如果指定了配置文件,则从配置文件里读取参数.
如果指定了使用默认配置文件配置,则从默认配置文件读取参数.
如果以上都没有指定,则从默认的配置目录读取配置文件.

参数读取的方式都是递归的.
也就是说,如果配置文件包含类似 !include 之类的语句的话,会递归调用search_default_file 这个函数(位于 mysys/default.c line 574 )


// mysys/default.c
/*
Read options from configurations files

SYNOPSIS
load_defaults()
conf_file Basename for configuration file to search for.
If this is a path, then only this file is read.
groups Which [group] entrys to read.
Points to an null terminated array of pointers
argc Pointer to argc of original program
argv Pointer to argv of original program

IMPLEMENTATION

Read options from configuration files and put them BEFORE the arguments
that are already in argc and argv. This way the calling program can
easily command line options override options in configuration files

NOTES
In case of fatal error, the function will print a warning and do
exit(1)

To free used memory one should call free_defaults() with the argument
that was put in *argv

RETURN
0 ok
1 The given conf_file didn't exists
*/


int load_defaults(const char *conf_file, const char **groups,
int *argc, char ***argv)
{
DYNAMIC_ARRAY args;
TYPELIB group;
my_bool found_print_defaults= 0;
uint args_used= 0;
int error= 0;
MEM_ROOT alloc;
char *ptr,**res;
struct handle_option_ctx ctx;
DBUG_ENTER("load_defaults");

init_default_directories();
init_alloc_root(&alloc,512,0);
/*
Check if the user doesn't want any default option processing
--no-defaults is always the first option
*/
if (*argc >= 2 && !strcmp(argv[0][1],"--no-defaults"))
{
/* remove the --no-defaults argument and return only the other arguments */
uint i;
if (!(ptr=(char*) alloc_root(&alloc,sizeof(alloc)+
(*argc + 1)*sizeof(char*)))) //申请内存,并将其添加到alloc的free链表里.返回实际可用区间的指针.
goto err;
res= (char**) (ptr+sizeof(alloc)); //上面char* 指针存放的开始位置.位MEM_ROOT结构预留位置
res[0]= **argv; /* Copy program name */
for (i=2 ; i < (uint) *argc ; i++) //存放参数,忽略已有的 --no-defaults 参数
res[i-1]=argv[0][i];
res[i-1]=0; /* End pointer */
(*argc)--; //忽略 --no-defaults 参数
*argv=res; //重置参数列表
*(MEM_ROOT*) ptr= alloc; /* Save alloc root for free */
DBUG_RETURN(0);
} //以上动作是把main的命令行参数存到自己的内存区域里.MEM_ROOT链表.

group.count=0;
group.name= "defaults";
group.type_names= groups; //groups = { "mysql","client",0 }.

for (; *groups ; groups++)
group.count++;
//至此.group结构初始化完毕.
if (my_init_dynamic_array(&args, sizeof(char*),*argc, 32)) //将args指向指定大小的内存区域.
goto err;

ctx.alloc= &alloc;
ctx.args= &args;
ctx.group= &group;
//conf_file = "my".递归将 'my' 的配置项目写入ctx指定的上下文环境中
error= my_search_option_files(conf_file, argc, argv, &args_used,
handle_default_option, (void *) &ctx); //conf_file = "my" 以上动作,handle_default_option 将option插入到指定的上下文中
/*
Here error contains <> 0 only if we have a fully specified conf_file
or a forced default file
*/
if (!(ptr=(char*) alloc_root(&alloc,sizeof(alloc)+
(args.elements + *argc +1) *sizeof(char*)))) // 在alloc指定的上下文环境中,为参数开辟内存区域
goto err;
res= (char**) (ptr+sizeof(alloc)); //偏移内存链表头数据结构位置

/* copy name + found arguments + command line arguments to new array */
res[0]= argv[0][0]; /* Name MUST be set, even by embedded library */
memcpy((uchar*) (res+1), args.buffer, args.elements*sizeof(char*)); //将命令行参数复制到上下文环境
/* Skip --defaults-xxx options */
(*argc)-= args_used;
(*argv)+= args_used;

/*
Check if we wan't to see the new argument list
This options must always be the last of the default options
*/
if (*argc >= 2 && !strcmp(argv[0][1],"--print-defaults"))
{
found_print_defaults=1;
--*argc; ++*argv; /* skip argument */
}
// 经过场面的参数过滤,重新复制参数到上下文环境
if (*argc)
memcpy((uchar*) (res+1+args.elements), (char*) ((*argv)+1),
(*argc-1)*sizeof(char*));
res[args.elements+ *argc]=0; /* last null */

(*argc)+=args.elements; // 更新参数个数,加上从配置文件读取的参数个数
*argv= (char**) res; // 将agrv重形象到上下文环境的参数区域.即现在存放原有参数和配置文件参数的那块区域
*(MEM_ROOT*) ptr= alloc; /* Save alloc root for free */
delete_dynamic(&args); //释放已经不需要的aggrs.其内容已经放置到上下文环境中了.
if (found_print_defaults)
{
int i;
printf("%s would have been started with the following arguments:\n",
**argv);
for (i=1 ; i < *argc ; i++)
printf("%s ", (*argv)[i]);
puts("");
exit(0);
}
DBUG_RETURN(error);

err:
fprintf(stderr,"Fatal error in defaults handling. Program aborted\n");
exit(1);
return 0; /* Keep compiler happy */
}

alloc_root


此函数经由某些宏开关,被分成了两个部分.
而实际,主要是从170行开始.之前的是另一个部分.
这个函数主要做的是,从free链表里面获取lengt大小的内存块.
如果没有合适大小的块,则用my_malloc申请新的块.

得到块之后,并没有讲块从free里移除.依然在free链表里.
内存块的大小也是不固定的.

alloc_root的代码

// mysys/my_alloc.c
void *alloc_root(MEM_ROOT *mem_root, size_t length)
{
#if defined(HAVE_purify) && defined(EXTRA_DEBUG)
reg1 USED_MEM *next;
DBUG_ENTER("alloc_root");
DBUG_PRINT("enter",("root: 0x%lx", (long) mem_root));

DBUG_ASSERT(alloc_root_inited(mem_root));

length+=ALIGN_SIZE(sizeof(USED_MEM));
if (!(next = (USED_MEM*) my_malloc(length,MYF(MY_WME))))
{
if (mem_root->error_handler)
(*mem_root->error_handler)();
DBUG_RETURN((uchar*) 0); /* purecov: inspected */
}
next->next= mem_root->used;
next->size= length;
mem_root->used= next; // 将next链接到root链表
DBUG_PRINT("exit",("ptr: 0x%lx", (long) (((char*) next)+
ALIGN_SIZE(sizeof(USED_MEM)))));
DBUG_RETURN((uchar*) (((char*) next)+ALIGN_SIZE(sizeof(USED_MEM))));
#else
size_t get_size, block_size;
uchar* point;
reg1 USED_MEM *next= 0;
reg2 USED_MEM **prev;
DBUG_ENTER("alloc_root");
DBUG_PRINT("enter",("root: 0x%lx", (long) mem_root));
DBUG_ASSERT(alloc_root_inited(mem_root));

length= ALIGN_SIZE(length);
if ((*(prev= &mem_root->free)) != NULL) //有可用内存区域,此时prev与free链表指向相同.
{
if ((*prev)->left < length &&
mem_root->first_block_usage++ >= ALLOC_MAX_BLOCK_USAGE_BEFORE_DROP &&
(*prev)->left < ALLOC_MAX_BLOCK_TO_DROP)
{
next= *prev;
*prev= next->next; /* Remove block from list */
next->next= mem_root->used;
mem_root->used= next;
mem_root->first_block_usage= 0;
}
for (next= *prev ; next && next->left < length ; next= next->next)
prev= &next->next;
}
if (! next) //上面的例程没找到合适大小的内存块,则申请新的内存区域
{ /* Time to alloc new block */
block_size= mem_root->block_size * (mem_root->block_num >> 2);
get_size= length+ALIGN_SIZE(sizeof(USED_MEM));
get_size= max(get_size, block_size);

if (!(next = (USED_MEM*) my_malloc(get_size,MYF(MY_WME))))
{
if (mem_root->error_handler)
(*mem_root->error_handler)();
return((void*) 0); /* purecov: inspected */
}
mem_root->block_num++;
next->next= *prev; //将新块添加到free链表头部
next->size= get_size;
next->left= get_size-ALIGN_SIZE(sizeof(USED_MEM));
*prev=next; //重新让pre指向free链表头部
}
//获得内存块之后,减去链表数据结构的大小,得到实际可用内存块大小的起点.
point= (uchar*) ((char*) next+ (next->size-next->left));
/*TODO: next part may be unneded due to mem_root->first_block_usage counter*/
if ((next->left-= length) < mem_root->min_malloc)
{ /* Full block */
*prev= next->next; /* Remove block from list */
next->next= mem_root->used;
mem_root->used= next;
mem_root->first_block_usage= 0;
}
DBUG_PRINT("exit",("ptr: 0x%lx", (ulong) point));
DBUG_RETURN((void*) point);// 返回实际可用内存块的起始位置指针.此时内存块已经列入free链表里
#endif
}

init_alloc_root


代码如下
主要是预处理一下相关数据结构的内容.

init_alloc_root的代码

// my_sys/my_alloc.c
/*
Initialize memory root

SYNOPSIS
init_alloc_root()
mem_root - memory root to initialize
block_size - size of chunks (blocks) used for memory allocation
(It is external size of chunk i.e. it should include
memory required for internal structures, thus it
should be no less than ALLOC_ROOT_MIN_BLOCK_SIZE)
pre_alloc_size - if non-0, then size of block that should be
pre-allocated during memory root initialization.

DESCRIPTION
This function prepares memory root for further use, sets initial size of
chunk for memory allocation and pre-allocates first block if specified.
Altough error can happen during execution of this function if
pre_alloc_size is non-0 it won't be reported. Instead it will be
reported as error in first alloc_root() on this memory root.
*/

void init_alloc_root(MEM_ROOT *mem_root, size_t block_size,
size_t pre_alloc_size __attribute__((unused)))
{
DBUG_ENTER("init_alloc_root");
DBUG_PRINT("enter",("root: 0x%lx", (long) mem_root));
//为MEM_ROOT结构各部分置初始值.保持各链表为空.
mem_root->free= mem_root->used= mem_root->pre_alloc= 0;
mem_root->min_malloc= 32;
mem_root->block_size= block_size - ALLOC_ROOT_MIN_BLOCK_SIZE;
mem_root->error_handler= 0;
mem_root->block_num= 4; /* We shift this with >>2 */
mem_root->first_block_usage= 0;

#if !(defined(HAVE_purify) && defined(EXTRA_DEBUG))
if (pre_alloc_size)
{
if ((mem_root->free= mem_root->pre_alloc=
(USED_MEM*) my_malloc(pre_alloc_size+ ALIGN_SIZE(sizeof(USED_MEM)),
MYF(0))))
{
mem_root->free->size= pre_alloc_size+ALIGN_SIZE(sizeof(USED_MEM));
mem_root->free->left= pre_alloc_size;
mem_root->free->next= 0;
}
}
#endif
DBUG_VOID_RETURN;
}






相关的数据结构如下.
USED_MEM代表的是一个内存块.
而MEM_ROOT是一个内存链表.意义参见实现.
这两个数据结构会被反复用到.
参见相应的标签

USED_MEM和MEM_ROOT的代码

// include/my_alloc.h
typedef struct st_used_mem
{ /* struct for once_alloc (block) */
struct st_used_mem *next; /* Next block in use */
unsigned int left; /* memory left in block */
unsigned int size; /* size of block */
} USED_MEM;


typedef struct st_mem_root
{
USED_MEM *free; /* blocks with free memory in it */
USED_MEM *used; /* blocks almost without free memory */
USED_MEM *pre_alloc; /* preallocated block */
/* if block have less memory it will be put in 'used' list */
size_t min_malloc;
size_t block_size; /* initial block size */
unsigned int block_num; /* allocated blocks counter */
/*
first free block in queue test counter (if it exceed
MAX_BLOCK_USAGE_BEFORE_DROP block will be dropped in 'used' list)
*/
unsigned int first_block_usage;

void (*error_handler)(void);
} MEM_ROOT;

2009-04-13

line_391--2009-4-14 16:38:09


由 391 至 404 , 通过duplicate 一个stdout的descriptor来判读stdout是否有效.
代码如下:

main的代码

//clien/mysql.cc
int main(int argc,char *argv[])
{
char buff[80];

MY_INIT(argv[0]); // 初始话化THR_KEY_mysys指向的线程环境,并初始化一些互斥量和锁.
DBUG_ENTER("main");
DBUG_PROCESS(argv[0]);
#pragma region SET_TERMINAL //设置交互终端的状态.
delimiter_str= delimiter;
default_prompt = my_strdup(getenv("MYSQL_PS1") ?
getenv("MYSQL_PS1") :
"mysql> ",MYF(MY_WME));
current_prompt = my_strdup(default_prompt,MYF(MY_WME));
prompt_counter=0;

outfile[0]=0; // no (default) outfile
strmov(pager, "stdout"); // the default, if --pager wasn't given
{
char *tmp=getenv("PAGER");
if (tmp && strlen(tmp))
{
default_pager_set= 1;
strmov(default_pager, tmp);
}
}
if (!isatty(0) || !isatty(1))
{
status.batch=1; opt_silent=1;
ignore_errors=0;
}
else
status.add_to_history=1;
status.exit_status=1;
#pragma endregion SET_TERMINAL
{ #pragma region PROMOTE_STDOUT //保证stdout有效,且可以访问.
/*
The file descriptor-layer may be out-of-sync with the file-number layer,
so we make sure that "stdout" is really open. If its file is closed then
explicitly close the FD layer.
*/
int stdout_fileno_copy;
stdout_fileno_copy= dup(fileno(stdout)); /* Okay if fileno fails. */
if (stdout_fileno_copy == -1)
fclose(stdout);
else
close(stdout_fileno_copy); /* Clean up dup(). */
}
#pragma endregion PROMOTE_STDOUT
...
}

line_364--2009-4-14 16:37:25

  main
  由 364 至 389 , 设置交互终端的一些信息,如提示符类型和这段设备状态等.
代码如下:
main的代码
// clien/mysql.cc
int main(int argc,char *argv[])
{
    char buff[80];

    MY_INIT(argv[0]); // 初始话化THR_KEY_mysys指向的线程环境,并初始化一些互斥量和锁.
    DBUG_ENTER("main");
    DBUG_PROCESS(argv[0]);
#pragma  region SET_TERMINAL //设置交互终端的状态.
    delimiter_str= delimiter; 
    default_prompt = my_strdup(getenv("MYSQL_PS1") ? 
    getenv("MYSQL_PS1") : 
    "mysql> ",MYF(MY_WME)); 
    current_prompt = my_strdup(default_prompt,MYF(MY_WME)); 
    prompt_counter=0;

    outfile[0]=0;   // no (default) outfile
    strmov(pager, "stdout"); // the default, if --pager wasn't given
    {
        char *tmp=getenv("PAGER");
        if (tmp && strlen(tmp))
        {
            default_pager_set= 1;
            strmov(default_pager, tmp);
        }
    }
    if (!isatty(0) || !isatty(1))
    {
        status.batch=1; opt_silent=1;
        ignore_errors=0;
    }
    else
    status.add_to_history=1;
    status.exit_status=1;
#pragma endregion SET_TERMINAL
    ...
}

MY_INIT


main里的入口
代码如下:

main的代码

// clien/mysql.cc
int main(int argc,char *argv[])
{
char buff[80];

MY_INIT(argv[0]);

...
}






include/my_sys.h line 40
MY_INIT
设置程序名字,并调用my_init()进行另外一些初始化
代码如下:

INIT的代码

#define MY_INIT(name); { my_progname= name; my_init(); }






my_init
代码见末尾.
1.检测init的状态,如果已经初始化完毕,或之前已经进行过初始化了,那么直接跳出.
2.设置初始化标记,表示初始化例程被调用过,即 1 中的 my_init_done标记.
3.递增初始化计数器.
4.设置默认的文件以及文件夹访问权限.
root用户以及所有者具有文件的读写权限,文件夹的所有权限
owner只具有文件的读写权限.
其余人没有任何权限
5.依据线程方面的一些开关
1)#if defined(THREAD) && defined(SAFE_MUTEX) /mysys/my_init.c line 80
进行名为safe_mutex_global_init 的操作
safe_mutex_global_init /mysys/thr_mutex.c line 50
|
|-- pthread_mutex_init /include/my_pthread line 236
|
|-- my_pthread_mutex_init /mysys/my_pthread.c 432
根据初始化互斥标记,进行互斥量的初始化
底层依靠的是 pthread 库的 pthread_mutex_init 函数
这段动作将初始化一个名为 THR_LOCK_mutex 的线程锁.
2)#if defined(THREAD) && defined(MY_PTHREAD_FASTMUTEX) && !defined (SAFE_MUTEX) /mysys/my_init.c line 83
进行名为fastmutex_global_init 的操作
设置了一个名为 cpu_count int类型静态量.
应该是CPU数量的计数器.
无其他任何动作.
根据线程方面的开关.在两个线程互斥模型中最多选择一个使用于系统.
注意到两个互斥的开关条件 defined(SAFE_MUTEX)
6.调用pthread的pthread_init初始化线程环境.
7.调用my_thread_global_init,并对此初始化例程的结果进行跟踪检测
函数动作包括
my_thread_global_init /mysys/my_thr_init.c line 81
1)调用get_thread_lib获得当前环境所使用的线程库类型.
线程库ID位于文件 /include/my_pthread.h line 691
2)利用pthread_keycreate建立一个新的线程上下文环境,并以THR_KEY_mysys作为此环境的索引
THR_KEY_mysys 类型未明. /mysys/my_thr_init.c line 86
3)判断是否在目标为linux环境
如果是,则在此版本的代码编写期间,pthread_exit函数存在race condition的bug.
使用一些代码避免这个bug
4)根据一些条件,做一些线程方面的设置工作.
主要牵涉到 pthread_mutexattr_init 和 pthread_mutexattr_settype 两个函数
5)初始化一些线程锁
名字列表:
THR_LOCK_malloc
THR_LOCK_open
THR_LOCK_lock
THR_LOCK_isam
THR_LOCK_myisam
THR_LOCK_heap
THR_LOCK_net
THR_LOCK_charset
THR_LOCK_threads
THR_LOCK_time
THR_COND_threads
注意到这些锁的行为由之前设定的一些attr决定.
6)调用pthread_cond_init函数
底层是pthread_cond_init,初始化名为THR_COND_threads的条件量
7)对于win平台,初始化一个THR_LOCK_thread
8)根据一些条件,有选择的初始化LOCK_localtime_r , LOCK_gethostbyname_r
9)调用my_thread_init,并检测状态.
获得之前建立的线程上下文环境,用THR_KEY_mysys索引,尝试得到一个上下文环境的指针.
然后将此数据结构放入用THR_KEY_mysys索引的上下文环境中.
数据结构为一个线程描述结构.
struct st_my_thread_var /include/my_pthread.h 662
10)如果没有异常,则进行扫尾工作.调用my_thread_global_end.
内部例程里利用线程锁,逐步等待线程结束.
清理之前申请的线程上下文环境 THR_KEY_mysys;
释放之前申请的所有的锁.参见 5)
8.对win环境读写一些注册表,并初始化winsock环境

代码如下:

my_init的代码

// mysys/my_init.c
my_bool my_init(void)
{
char * str;
if (my_init_done)
return 0;
my_init_done=1;
mysys_usage_id++;
my_umask= 0660; /* Default umask for new files */
my_umask_dir= 0700; /* Default umask for new directories */
#if defined(THREAD) && defined(SAFE_MUTEX)
safe_mutex_global_init(); /* Must be called early */
#endif
#if defined(THREAD) && defined(MY_PTHREAD_FASTMUTEX) && !defined(SAFE_MUTEX)
fastmutex_global_init(); /* Must be called early */
#endif
netware_init();
#ifdef THREAD
#if defined(HAVE_PTHREAD_INIT)
pthread_init(); /* Must be called before DBUG_ENTER */
#endif
if (my_thread_global_init())
return 1;
#if !defined( __WIN__) && !defined(__NETWARE__)
sigfillset(&my_signals); /* signals blocked by mf_brkhant */
#endif
#endif /* THREAD */
{
DBUG_ENTER("my_init");
DBUG_PROCESS((char*) (my_progname ? my_progname : "unknown"));
if (!home_dir)
{ /* Don't initialize twice */
my_win_init();
if ((home_dir=getenv("HOME")) != 0)
home_dir=intern_filename(home_dir_buff,home_dir);
#ifndef VMS
/* Default creation of new files */
if ((str=getenv("UMASK")) != 0)
my_umask=(int) (atoi_octal(str) 0600);
/* Default creation of new dir's */
if ((str=getenv("UMASK_DIR")) != 0)
my_umask_dir=(int) (atoi_octal(str) 0700);
#endif
#ifdef VMS
init_ctype(); /* Stupid linker don't link _ctype.c */
#endif
DBUG_PRINT("exit",("home: '%s'",home_dir));
}
#ifdef __WIN__
win32_init_tcp_ip();
#endif
DBUG_RETURN(0);
}
} /* my_init */


小结:
MY_INIT 宏所做的工作主要是:
1.初始化THR_KEY_mysys索引的线程环境.
2.初始化一些锁变量和线程条件变量.

爽文

去看了好东西. 坦白说,多少是带着点挑刺的味道去的. 毕竟打着爱情神话和女性题材的气质,多多少少是热度为先了. 看完之后倒是有些新的想法. 某种程度上来说,现在的年轻人或者说声音就像小叶. 只要说点贴心的话就能哄好. 也是那种可以不用很努力了. 留在自己的舒适区避难所小圈子抱团就...