陆游和唐婉

公元1144年,陆游与舅父唐仲俊之女唐婉结婚,陆母怕陆与唐沉醉于两个人的天地中,而影响陆的登科进官,以婚后三年未有子为由,逼其与唐婉离婚。那是一个母命如圣旨的年代!

十年之后,陆回到老家,偶到绍兴有名的沈园去游玩,谁曾想却再这里遇见了昔日恋人唐婉。当唐婉走到陆游身边的那一刹间,时光与目光都凝固了,不知是梦是真,眼帘中饱含的不知是情、是怨、是思、是怜。

好在一阵恍惚之后,已为他人之妻的唐婉终于提起沉重的脚步,留下深深的一瞥之后走远了。只留下了陆游在花园中怔怔发呆。
和风袭来,吹醒了沉醉在旧梦中的陆游,他不由地循着唐婉的身影追寻而去。来到池塘边柳树下,遥见唐婉与赵士程正在池中水榭上进食。隐隐看见唐婉低首蹙眉,有心无心地伸出玉手红袖,与赵士程浅斟慢饮。这一似曾相识的场景,看得陆游的心都碎了。昨日情梦,今日痴怨,尽绕心头感慨万千。于是提笔在壁上题了千古绝唱的

钗头凤:

红酥手,黄縢酒,满城春色宫墙柳。
东风恶,欢情薄。
一怀愁绪,几年离索。
错! 错! 错!

春如旧,人空瘦,泪痕红浥[yì]鲛绡[jiāo xiāo]透。
桃花落,闲池阁。
山盟虽在,锦书难托。
莫!莫!莫!

第二年春天,抱着一种莫名的憧憬,唐婉再一次来到沈园,徘徊在曲径回廊之间,忽然瞥见陆游的题词。反复吟诵,想起往日二人诗词唱和的情景不由得泪流满面、心潮起伏,和了一首词,题在陆游的词后:

钗头凤 (唐婉)

世情薄,人情恶,雨送黄昏花易落。
晓风干,泪痕残。
欲笺[jiān]]心事,独语斜阑。
难!难!难!

人成各,今非昨,病魂常似秋千索。
角声寒,夜阑珊。
怕人寻问,咽泪妆欢。
瞒!瞒!瞒!

表达了旧情难忘而又难言的忧伤情愫,她不久之后便郁郁而死。

陆游浪迹天涯数十年,企图就此忘却他与唐婉的凄婉往事。然而离家越远,唐婉的影子就越萦绕在他的心头。此番倦游归来,唐婉早已香消玉殒,自己也已至垂暮之年(75岁)。然而他对旧事、对沈园依然怀着深切的眷恋。常常在沈园幽径上踽踽[jǔ]独行,追忆着深印在脑海中那惊鸿一瞥的一幕。这时他写下了

沈园怀旧诗二首:

 一、

   城上斜阳画角哀,

   沈园非复旧池台。

   伤心桥下春波绿,

   曾是惊鸿照影来。
 二、

   梦断香消四十年,

   沈园柳老不吹绵。

   此身行在稽山土,

   犹吊遗踪一泫然。


沈园是陆游怀旧场所,也是他伤心的地方。他想着沈园,但又怕到沈园。春天再来,撩人的桃红柳绿,恼人的鸟语花香,风烛残年的陆游(81岁)虽然不能再亲至沈园寻觅往日的踪影,然而那次与唐婉的际遇,伊人那哀怨的眼神、差怯的情态、无可奈何的步履、欲言又止的模样,使陆游牢记不忘,于是又赋

“梦游沈园”诗:

其一:

   路近城南已怕行,

   沈家园里更伤情;

   香穿客袖梅花在,

   绿蘸寺桥春水生。

其二:

   城南小陌又逢春,

   只见梅花不见人;

   玉骨久沉泉下土,

   墨痕独锁壁间尘。


陆游八十五岁那年春日的一天,忽然感觉到身心爽适、轻快无比。原准备上山采药,因为体力不允许就折往沈园。此时沈园又经过了一番整理,景物大致恢复旧观,陆游满怀深情地写下了最后一首沈园情诗:

      沈家园里花如锦,
      半是当年识放翁; 
      也信美人终作土,
      不堪幽梦太匆匆。

作者:莲娃
来源:CSDN
原文:https://blog.csdn.net/mlyjqx/article/details/69351079
版权声明:本文为博主原创文章,转载请附上博文链接!

墨菲定律

墨菲定律是一种心理学效应,是由爱德华·墨菲(Edward A. Murphy)提出的。
主要内容:
一、任何事都没有表面看起来那么简单;
二、所有的事都会比你预计的时间长;
三、会出错的事总会出错;
四、如果你担心某种情况发生,那么它就更有可能发生。
墨菲定律的原句是这样的:如果有两种或两种以上的方式去做某件事情,而其中一种选择方式将导致灾难,则必定有人会做出这种选择。
墨菲定律是其作出的著名论断,亦称墨菲定律、墨菲定理,是西方世界常用的俚语。
墨菲定律根本内容是:如果事情有变坏的可能,不管这种可能性有多小,它总会发生。

成立条件:
1、事件有大于零的概率;
2、样本足够大(比如时间足够长,人数足够多等)
在科学和算法方面,它与英文所谓的“worst-case scenario(最劣情形)”同义,数学上用大O符号来表示。例如,对插入排序来说,最劣情形即是要排序的阵列完全倒置,必须进行 n*(n-1) 次的置换才能完成排序。在实验上,证明了最劣情形不会发生,并不代表比它轻微的情形就不可能,除非能够很有信心的推论事件的概率分布是线型的。

墨菲定律的原句已经派生出以下的版本:
1.别试图教猫唱歌,这样不但不会有结果,还会惹猫不高兴。
2.别跟傻瓜吵架,不然旁人会搞不清楚,到底谁是傻瓜。
3.不要以为自己很重要,因为没有你,太阳明天还是一样从东方升上来。
4.笑一笑,明天未必比今天好。
5.好的开始,未必就有好结果;坏的开始,结果往往会更糟。
6.你若帮助了一个急需用钱的朋友,他一定会记得你——在他下次急需用钱的时候。
7.有能力的——让他做;没能力的──教他做;做不来的──管理他。
8.你早到了,会议却取消;你准时到,却还要等;迟到,就是迟了。
9.你携伴出游,越不想让人看见,越会遇见熟人。
10.你爱上的人,总以为你爱上他是因为:他使你想起你的老情人。
11.你最后硬着头皮寄出的情书;寄达对方的时间有多长,你反悔的时间就有多长。
12.东西越好,越不中用。
13.一种产品保证60天不会出故障,等于保证第61天一定就会坏掉。
14.东西久久都派不上用场,就可以丢掉;东西一丢掉,往往就必须要用它。
15.你丢掉了东西时,最先去找的地方,往往也是可能找到的最后一个地方。
16.你往往会找到不是你正想找的东西。
17.你出去买爆米花的时候,银幕上偏偏就出现了精彩镜头。
18.另一排总是动的比较快;你换到另一排,你原来站的那一排,就开始动的比较快了;你站的越久,越有可能是站错了排。
19.一分钟有多长? 这要看你是蹲在厕所里面,还是等在厕所外面。
20、计划没有变化快。
21、欠账总是要还的。
22、做恶总是要遭报应的,不是不报,只是时间未到。
23、该来的总是要来的。
24、明天又是一个新的开始。
25、你越是害怕的事物,就越会出现在你的生活中。
26、往往等公车太久没来,就走了的人,刚走公车就来了。
27、关键时刻掉链子。
28、越想要什么就越不能得到什么。
29、人出来混,总是要还的。
30、怕什么,来什么。
31、若想人不知除非己莫为。
32、你上班经常带的一样东西(U盘、银行卡、会员卡等等),当有一天你觉得反正天天带都用不上,不带了。而实际可能就在你没带它的那一天,你真的就需要它了。

悼念金庸 从“金句”看金庸生死观

1。慧极必伤,情深不寿,强极则辱,谦谦君子,温润如玉。——《书剑恩仇录》

2。人生在世,充分圆满的自由根本是不能的。——《笑傲江湖》

3。幽冥之事,究属渺茫。死虽未必可怕,但凡人莫不有死,到头这一身,难逃那一日。能够多活一天,便多一天罢!——《倚天屠龙记》

4。生死修短,岂能强求?予恶乎知悦生之非惑邪?予恶乎知恶死之非弱丧而不知归者邪?予恶乎知夫死者不悔其始之蕲生乎?——《倚天屠龙记》

5。兄弟,每个人都要死,我说那谁也躲不了的瘟疫,便是大限到来,人人难逃。——《射雕英雄传》

6。人生在世,去若朝霞。魂归来兮,哀我何悲。——《天龙八部》

中国围棋八大美女棋手,容貌与智力的完美结合

陈盈 天津棋手,20岁入段,虽然入段时间较晚,但陈盈很长一段时间在棋迷中被评为最有人气的围棋美女,作为围棋主持人也很受欢迎。

高星 1996年出生的湖北棋手,16岁定段。高星五官非常清秀,年龄不大,下棋时很有大师风范。

黑嘉嘉 1994年出生的台北棋手,14岁在中国定段。黑嘉嘉被很多人成为当今围棋界第一美女,而且还有向文艺界发展的想法,不过她可不是花瓶,她的围棋水平非常强大,如今已是职业七段的高水准棋手。

贾罡璐,1995年出生的福建棋手,16岁定段。贾罡璐也是一位“跨界棋手”,经常会起各大平台客串围棋节目的主持人。

王香如,围棋初段,自7岁学棋开始,2000年获得全国少儿围棋比赛儿童(女子)组冠军,在入段前王香如就已经很有名,因为她的美貌,因为她的才情,更因为她的坚持。当然,王香如的名气更多地还是来自于2008年她与黑嘉嘉”争夺”入段名额的”风波”。

於之莹 1997年出生的江苏棋手,12岁定段。棋迷都昵称她为“小鱼儿”,生活中非常可爱的女孩子,但请注意:她就是当今女子围棋的第一人!在世界围棋排行榜上已经连续4年是女子围棋世界排名第一了。曾在性别大战中战胜过李钦诚(2016年亚洲杯电视快棋赛冠军)夺得新人王冠军。

俞俐均 1999年出生的台湾棋手,14岁入段。在台湾有“小黑嘉嘉”之称,外貌非常甜美,下棋时候就像芭比娃娃一样安静漂亮。

浙江棋手 18岁定段。江浙出美女,张越然气质非常不错,如今在女子围甲她也为浙江队效力

来源:https://baijiahao.baidu.com/s?id=1586857450902400762

象棋界十大美女 | 不只是棋下得好颜值还很高!

象棋TOP美女,以下排名不分先手,仅代表作者观点。。。

1、时凤兰:象棋大师,1993年5月16日出生,广东中山人,现为广东省象棋队运动员,被誉为“象棋女神”。

2、唐思楠:象棋大师,1997年出生,浙江湖州人,被誉为“象棋小仙女”。

3、李越川:象棋大师,1995年出生,贵州贵阳人。雅号小川,与许银川(大川)、蒋川(中川)并称象棋界三川。

4、武文慧:象棋大师,1995年4月24日出生,内蒙古象棋队运动员。

5、梁妍婷:象棋大师,1994年5月3日出生,浙江瑞安人,现为四川象棋队运动员。

6、唐丹:象棋特级大师,1990年1月出生,安徽枞阳人,被誉为“超级丹”,现为中象女子等级分第一人,是为数不多能与男子象棋大师相对抗的女子棋手。

7、吴可欣:象棋大师,1998年出生,浙江省义乌市人,现为浙江象棋队运动员。

8、董毓男:象棋大师,2000年06月05日出生,现为江苏象棋队运动员。

9、张婷婷:象棋大师,1991年6月10日出生,河北沧州人。

10、郎琪琪:象棋大师,四川成都人,现服役于四川象棋队。

​来源:https://baijiahao.baidu.com/s?id=1593508667109402916

爱情和婚姻需要烟火气

有越来越多的人想要离婚了。婚姻似乎成了一座围城,外面的人不想进去,里面的人都想冲出来。

烟火气才是婚姻的真谛,没有烟火气的婚姻就是一场孤独的旅行。所以,如果你还不会做饭的话,是时候去好好学一学了,生活需要不断调整我们的情绪和心态,婚姻更需要你每天制作充满爱的美味佳肴。

爱情,如果不落实到穿衣、吃饭、数钱、睡觉这些实实在在的生活里去,是不容易天长地久的。

婚姻研究专家约翰·戈特曼在《幸福的婚姻》一书中说:“在各种生活小事上靠近配偶,是浪漫持久存在的关键。”

有人说,婚姻中要有不会磨灭的爱情;要有志趣相投的三观;要有势均力敌的经济;要有互相包容的内心;还要有一定程度的距离,给彼此一些独立的空间…..

但仅有这些还是不够的,最好的婚姻,没有那么多的“有情饮水饱”,而是实实在在的柴米油盐,是抚慰你身心的那顿饭,守候你回家的那盏灯,放在你床头的那杯水,以及你们携手走过的那些路。

做饭是婚姻的修行,爱情最好的模样应该就是一屋两人三餐四季,褪去一天的疲劳,两个人坐在暖黄色的灯光下,桌子上是热气腾腾的饭菜,里面都是家的味道,爱的人在身旁,热的菜在手边,一切都如同岁月静好一般的温暖。

我看了很多幸福美满的夫妻,发现他们都有一个共同的特点:不管多忙,都会一起做饭。

最令人羡慕的爱情就是你负责下厨做饭,我负责吃完不剩;你负责洗手作羹汤,我负责择菜洗碗瓢;你负责柴米油盐酱醋茶,我负责琴棋书画诗酒花…..

日剧《最完美离婚》中有句台词:“男女凑在一起不代表是夫妻,夫妻也不等同于家人,只要去政府递交了申请就成了夫妻,但不代表能成为家人,家人是可以坐下来好好喝上一杯茶,吃上一顿饭。”

夫妻俩能吃到一起,才能过到一起,如若连吃饭都是随意解决,那婚姻生活也只会越过越糟糕。

幸福只是一件小事,一件需要两个人用心保鲜的小事。

不需要你陪我去多么高档奢华的餐厅大快朵颐,也不需要你陪我去异国他乡享受别样的风土人情。要的只是你能陪我做一顿饭,然后好好的坐下来,一起品尝。

有人说:“婚姻就像是一大碗洋葱卤肉饭,洋葱爆锅会让你流眼泪,小火炖卤肉会让你等待,要考虑酱油放多少,冰糖放几块,味道拿捏全凭平日里的默契。

一勺子米饭铺在碗底,是平平淡淡的生活,惊喜是大勺卤肉浇在米饭上,再加半勺汤,切开的卤蛋,你一半,我一半,有福同享有难同当。”

愿你以后的日子,厨房有烟火,客厅有笑容,卧室有拥抱,爱人跟你一蔬一饭,你跟爱人一颦一笑。

愿往后余生,有人陪你吃人间烟火,吃到眼睛老了,胃口小了,牙齿掉光光了,然后在餐桌边一点一点地老去,一起走到世界的尽头。

摘抄原文:http://www.sohu.com/a/258721034_196571

SQL执行中占CPU资源最多的前10条查询

select top 20 total_worker_time/execution_count as avg_cpu_cost,plan_handle,execution_count,(select substring(text,statement_start_offset/2+1,(case when statement_end_offset=-1then len(convert(nvarchar(max),text))*2else statement_end_offsetend - statement_start_offset)/2)from sys.dm_exec_sql_text(sql_handle)) as query_textfrom sys.dm_exec_query_statsorder by [avg_cpu_cost] desc
找出工作负荷中运行最频繁的查询select top 10 total_worker_time,plan_handle,execution_count,   (select substring(text,statement_start_offset /2 +1,       (case when statement_end_offset = -1          then len(convert(nvarchar(max),text))*2          else statement_end_offset        end - statement_start_offset)/2)      from sys.dm_exec_sql_text(sql_handle)) as query_text   from sys.dm_exec_query_stats   order by execution_count desc
找到被编译得最多的前10位查询计划select top 10 plan_generation_num,execution_count,  (select substring(text,statement_start_offset /2 +1,     (case when statement_end_offset = -1       then len(convert(nvarchar(max),text))*2       else statement_end_offset      end - statement_start_offset)/2)    from sys.dm_exec_sql_text(sql_handle)) as query_textfrom sys.dm_exec_query_statswhere plan_generation_num>1order by plan_generation_num desc
来源:https://blog.csdn.net/easyboot/article/details/7623746

信息通信发展司召开IPv6规模部署及专项督查工作 全国电视电话会议

2018年8月3日,工业和信息化部信息通信发展司召开IPv6规模部署及专项督查工作全国电视电话会议。部信息通信发展司司长闻库,部网络安全管理局副局长张新,部信息通信管理局互联网处处长裴玮,中国信息通信研究副院长王志勤出席会议并讲话,中央网信办信息化发展局处长方新平受邀出席会议。会议由部信息通信发展司巡视员陈家春主持。

会议指出,发展基于IPv6的下一代互联网,不仅是互联网演进升级的必然趋势,更是助力互联网与实体经济深度融合、支撑经济高质量发展的迫切需要,对于提升国家网络空间综合竞争力、加快网络强国建设具有重要意义。

会议要求,以“通盘布局、移动先行、流量突破”为主要工作思路,推进各项改造工作。其中,基础电信企业30个自营APP是LTE IPv6“高速公路上的测试车”,各企业要力争提前完成改造,真正实现端到端贯通,为提升用户规模与网络流量打下良好基础;内容分发网络(CDN)改造要适度超前,为互联网应用改造提供足够的内容加速资源;云服务企业,特别是大型云服务企业不仅要及时完成云产品IPv6改造目标,还要充分发挥平台优势,面向中小型企业提供IPv6技术咨询和网站改造等服务;终端制造企业要进一步加快移动和固定终端的软硬件升级,消除IPv6盲点。各企业间加强沟通协调对接,合力破解具体困难问题;各通信管理局、各企业、中国信息通信研究院要按照相关要求,扎实做好专项督查各项工作,确保相关工作取得实效。

工业和信息化部机关司局、北京市通信管理局、基础电信企业、北京邮电大学、部属单位及部分互联网企业有关部门负责人在现场参会。各省(区、市)通信管理局、基础电信企业及互联网企业负责人在视频分会场参会。

来源:http://www.miit.gov.cn/n1146290/n1146402/n1146440/c6291799/content.html

Whistleblower reveals Google’s plans for censored search in China

传谷歌搜索将接受审查重新进入中国

Illustration by Alex Castro / The Verge

Google is reportedly planning to relaunch its search engine in China, complete with censored results to meet the demands of the Chinese government. The company originally shut down its Chinese search engine in 2010, citing government attempts to “limit free speech on the web.” But according to a report from The Interceptthe US tech giant now wants to return to the world’s biggest single market for internet users.

According to internal documents provided to The Intercept by a whistleblower, Google has been developing a censored version of its search engine under the codename “Dragonfly” since the beginning of 2017. The search engine is being built as an Android mobile app and will reportedly “blacklist sensitive queries” and filter out all websites blocked by China’s web censors (including Wikipedia and BBC News). The censorship will extend to Google’s image search, spell check, and suggested search features.

The web is heavily censored in China, with the country’s so-called Great Firewall stopping citizens from accessing many sites. Information on topics like religion, police brutality, freedom of speech, and democracy are heavily filtered, while specific search topics (like the 1989 Tiananmen Square protests and Taiwanese independence) are censored completely. Advocacy groups report that censorship in the country has increased under President Xi Jinping, extending beyond the web to social media and chat apps.

The whistleblower who spoke to The Intercept said they did so because they were “against large companies and governments collaborating in the oppression of their people.” They also suggested that “what is done in China will become a template for many other nations.”

Patrick Poon, a researcher with Amnesty International, agreed with this assessment. Poon told The Intercept that if Google launches a censored version of its search engine in China it will “set a terrible precedent” for other companies. “The biggest search engine in the world obeying the censorship in China is a victory for the Chinese government — it sends a signal that nobody will bother to challenge the censorship any more,” said Poon.

In a statement given to The Verge, a spokesperson said: “We provide a number of mobile apps in China, such as Google Translate and Files Go, help Chinese developers, and have made significant investments in Chinese companies like JD.com. But we don’t comment on speculation about future plans.”

According to The Intercept, Google faces a number of substantial barriers before it can launch its new search app in China, including approval from officials in Beijing and “confidence within Google” that the app will be better than its main rival in China, Baidu.

Google previously offered a censored version of its search engine in China between 2006 and 2010, before pulling out of the country after facing criticism in the US. (Politicians said the company was acting as a “functionary of the Chinese government.”) In recent months, though, the company has been attempting to reintegrate itself into the Chinese commercial market. It launched an AI research lab in Beijing last December, a mobile file management app in January, and an AI-powered doodle game just last month.

Although this suggests Google is eager to get a slice of China’s huge market of some 750 million web users, ambitions to relaunch its search engine may yet go nowhere. Reports in past years of plans to bring the Google Play mobile store to China, for example, have so far come to nothing, and Google regularly plans out projects it ultimately rejects.

Notably, relations between China and the US have worsened in recent weeks due to trade tariffs imposed by President Trump. The Intercept reports that despite this Google staff have been told to be ready to launch the app at short notice. The company’s search engine chief, Ben Gomes, reportedly told employees last month that they must be prepared in case “suddenly the world changes or [President Trump] decides his new best friend is Xi Jinping.”

来源:https://www.theverge.com/2018/8/1/17638480/google-china-search-engine-censored-report

[转]国内知名站长网站 ChinaZ 论坛宣布关闭

先说明一下,关闭的是站长论坛BBS这个网站 http://bbs.chinaz.com/,不是站长之家。

Chinaz站长论坛成立于2002年,是国内知名站长社区,而昨日一条《站长论坛关闭公告》刷爆了站长的朋友圈,满屏的情怀,叹息一个时代的结束。十六年间,互联网发生了翻天覆地的变化,站长们从满怀梦想的少年变成了油腻大叔。

2005年,Chinaz创始人姚剑军与其他三位创始人成立厦门享联科技有限公司,以公司化的方式运营站长站。公司成立后的第一个动作便是推出第三方数据统计服务商——CNZZ,其一个月后,成为中国站长必用的网站统计服务之一。CNZZ在2008年获得IDG投资,2011年被阿里巴巴集团收购。2017年1月享联科技宣布通过新三板上市!股票代码:870486。

其中,2010年ChinaZ迎来了自己的吉祥物,取名“WoWo”。关于为什么选择蜗牛作为吉祥物,站长之家创始人阿飞哥(姚剑军)称:“蜗牛慢吞吞的,但是却很努力的在爬,而且背上背着一个重重的壳,这和吉祥物wowo吉祥物wowo站长好相像。我认为蜗牛其实是一种精神,纵使自己很慢,但也会不断的去努力,永不放弃。”

2017年公司上市后,阿飞也迎来的人生的高峰,而Chinaz站长论坛关闭的理由是“因业务发展需要”,不禁让人想到论坛已经成了重重的壳,但愿站长们能做到永不放弃!

来源:站长圈

源地址:https://www.oschina.net/news/98001/say-goodbye-to-bbs-chinaz

人有欺之,却不自欺

小九:“真跳下去,我也没有把握不会受伤,况且我不能用这种方式证明自己有种,那不过是证明了我是个傻子。他们显然是在欺负我,难道我还要欺负自己吗?若如其所愿,便是其帮凶,没有道理帮着欺负我的人去欺负自己。”

人有欺之,却不自欺,这倒是不错。

世人往往不懂强弱之道,自在之强,看似柔弱。所谓勇者,有勇于敢,亦有勇于不敢,而勇于不敢往往更难。明白这个道理并不容易,你能喊出那一句实不简单。

莫说你有功夫可以跳下去,也可以打得过他们,若是你没有功夫在身,也打不过他们,那是更不能跳了。因为你根本不该这么做、也不能这么做,哪怕受辱骂嘲笑,亦能不跳,则是大勇。

当然有,万事万物都在大道之中。若行止自然,就不必刻意去讲。

来源:徐公子<<太上章>>

编程语言简史:给C语言做个演示程序,结果他们弄出了一个操作系统UNIX

编程语言有上千种,但是流行的不过10来种,那些我们经常使用的编程语言都是谁在什么时候创造出来的呢?Casper Beyer 为我们进行了整理。

1800年

Joseph Marie Jacquard 教会了一台织布机读穿孔卡片,制造出了第一个高度多线程的处理单元。他的发明受到了预见天网(Skynet)诞生的纺织工人的强烈反对。

1842年

Ada Lovelace(英国诗人拜伦之女)为计算程序拟定“算法”,写作的第一份“程序设计流程图”,被珍视为“第一位给计算机写程序的人”。稍微有点不便的是当时还没有计算机呢。

1936年

阿兰·图灵被称为计算机科学之父,人工智能之父。但英国法庭却并不认可,还判处对他进行化学阉割。

女皇后来宽恕了他,但不幸的是当时他已经过世很久了。

1936年

Alonzo Church(算法理论重要奠基人)发明了lambda算子,跟图灵生活在同样的时代,但是他在时代的另一边,也并没有被女王阉割。

1957年

John Backus创建了FORTRAN语言,这真正是程序员使用的第一种语言。

1959年

Grace Hopper发明了第一门针对企业面向商业的编程语言,并且把这门语言叫做“面向商业的通用语言(common business-oriented language)”,简称COBOL。

1964年

John Kemeny 和 Thomas Kurtz 认为编程太难了,需要回归本源,他们把自己的编程语言叫做BASIC。

1970年

Niklaus Wirth开发了多种语言,最后流行起来的是PASCAL。他喜欢开发语言。

他还发明了让摩尔定律变得过时的Wirth定律(软件变慢的速度比硬件变快的速度更快),因为软件开发者会编写出连大型主机也没法跟上的臃肿软件。

这在后来被证明是正确的——在Electron.js被发明出来后

1972年

Dennis Ritchie在贝尔实验室上班上到无聊了,于是他决定写出带有花括号的C语言,这门语言取得了巨大成功。随后他又增加了分段错误等对开发者友好的功能来辅助提高生产率。

折腾完这门语言之后他还有时间,于是他跟在贝尔实验室的伙计决定给C语言做个演示程序,结果他们弄出了一个操作系统,UNIX。

1980年

Alan Kay发明了一门面向对象语言,他把这门语言叫做Smalltalk,在Smalltalk中一切都是对象,甚至一个对象也是对象。没人真正搞得清楚small talk是什么意思。

1983年

Jean Ichbiah注意到Ada Lovelace的程序从来都没有实际运行过,决定以她的名字开发一门语言,但是这门语言还是没有跑起来。

1983年

Bjarne Stroustrup 注意到C在编译方面花的时间还不够多,于是他把自己能想到的每一项功能都增加了进去,然后称之为C++。

每一个地方的程序员都接受了它,因为这样他们在工作的时候找借口看阿猫阿狗视频和xkcd漫画就显得比较有诚意了。

1986年

Brac Box 和 Tol Move决定在Smalltalk的基础上制作一个C语言的不可读版本,他们把这门语言叫做Objective-C,但是没人弄得清楚它的语法。

1987年

Larry Wall有宗教经验,他成为了一名牧师,并且把Perl变成了一种教义。

1991年

Guido van Rossum不喜欢花括号,于是他发明了Python,语法选择的灵感来源自Monty Python(巨蟒剧团)和Flying Circus(飞行马戏团)。

1993年

Roberto Ierusalimschy和他的朋友认为自己需要一个巴西本地化的脚本语言,在本地化期间发生了一个错误,这个错误会把指针从1而不是0开始计算,他们把这门语言叫做Lua。

1994年

Rasmus Lerdorf给他个人主页的CGI脚本做了一个模板引擎,后来他把自己的资料都放到了网上。

世界决定将这些东西用到一切,Rasmus于是匆忙地将一些数据库绑定做了进去,并把这门语言叫做PHP。

1995年

Yukihiro Matsumoto不是很高兴,因为他注意到其他程序员不是很高兴。他创建了Ruby来让程序员高兴。在他创建了Ruby后“Matz”高兴了,Ruby社区高兴了,每个人都高兴了。

1995年

Brendan Eich利用周末时间设计了一门语言,打算用这门语言来为全世界的每一个主流浏览器乃至于最终的Skynet都提供动力。

他先是找到了Netscape然后说这门语言叫做LiveScript,但在代码评审期间Java变得流行起来,所以他们决定最好还是用花括号,然后就把它更名为JavaScript。

结果表明,Java却是一个会让他们惹上麻烦的商标,JavaScript随后更名为ECMAScript,但大家还是把它叫做JavaScript。

1996年

James Gosling发明了Java,第一们真正过于繁琐的面向对象语言,在这里设计模式完全压倒了实用主义。

于是就诞生了超级有效的管理器提供商、容器提供商、服务提供商、单一管理器提供商模式。

2001年

Anders Hejlsberg重新发明了Java然后把它叫做C#,因为用C来编程感觉要比Java酷。每个人都喜欢这个新版本的Java,因为它完全不像Java。

2005年

David Hanselmeyer Hansen创建了一个web框架叫做Ruby on Rails,从此大家不再记得Ruby和Rails是两个独立的东西了。

2006年

John Resig为JavaScript写了一个帮助库,每个人都以为那是一门语言,从此从互联网上拷贝粘贴jQuery代码就成为了一门职业。

2009年

Ken Thompson 和 Rob Pike 决定做一门类似C那样的语言,但要有更安全的装置,还要有更好的卖相,并且把Gopher(囊鼠)作为吉祥物。

他们把这门语言成为Go,并把它做成开源然后另外卖Gopher商标的护膝和头盔作为收入来源。

2010年

Graydon Hoare也想把语言做成C那样,他称之为Rust。每个人都要求马上用Rust把软件的每一块都重写一遍。Graydon希望做点更有亮点的事情,于是开始为苹果开发Swift。

2012年

Anders Hjelsberg希望在web浏览器里面写C#,于是他设计出TypeScript,这东西其实是JavaScript,但里面有了更多的Java的东西。

2013年

Jeremy Ashkenas想要像Ruby开发者一样快乐,于是他创建了CoffeeScript,这东西编译后像JavaScript但是样子又更像Ruby。Jerry从来都没有变得像Matz和Ruby开发者那样真正快乐。

2014年

Chris Lattner做Swift的时候,其主要的设计目标就是不要成为Objective-C,最后它看起来像Java。

原文链接:https://medium.com/@caspervonb/a-brief-totally-accurate-history-of-programming-languages-cd93ec806124

译者:36Kr 编译组   编辑:郝鹏程。

来源:编程语言简史:有人讨厌花括号,于是他发明了Python

https://www.oschina.net/news/92787/a-brief-totally-accurate-history-of-programming-languages

IAP是什么

1.IAP是什么–简介

IAP是In Application Programming的首字母缩写,IAP是用户自己的程序在运行过程中对User Flash的部分区域进行烧写,目的是为了在产品发布后可以方便地通过预留的通信口对产品中的固件程序进行更新升级。

2.IAP是什么–功能

在应用编程IAP(In-Application Programming)是应用在Flash程序存储器的一种编程模式。它可以在应用程序正常运行的情况下,通过调用特定的IAP程序对另外一段程序Flash空间进行读/写操作,甚至可以控制对某段、某页甚至某个字节的读/写操作,这为数据存储和固件的现场升级带来了更大的灵活性。

3.IAP是什么–实现方法

通常在用户需要实现IAP功能时,即用户程序运行中作自身的更新操作,需要在设计固件程序时编写两个项目代码,这两部分项目代码都同时烧录在User Flash中,当芯片上电后,首先是第一个项目代码开始运行,它作如下操作:

1)检查是否需要对第二部分代码进行更新

2)如果不需要更新则转到4)

3)执行更新操作

4)跳转到第二部分代码执行

第一部分代码必须通过其它手段,如JTAG或ISP烧入;第二部分代码可以使用第一部分代码IAP功能烧入,也可以和第一部分代码一道烧入,以后需要程序更新是再通过第一部分IAP代码更新。

在第二部分代码开始执行时,首先需要把CPU的中断向量表映像到自己的向量表,然后再执行其他的操作。

如果IAP程序被破坏,产品必须返厂才能重新烧写程序,这是很麻烦并且非常耗费时间和金钱的。针对这样的需求,STM32在对Flash区域实行读保护的同时,自动地对用户Flash区的开始4页设置为写保护,这样可以有效地保证IAP程序区域不会被意外地破坏。

4.IAP是什么–IAP与ISP的区别

在线编程目前有两种实现方法:在系统编程(ISP)和在应用编程(IAP)。ISP一般是通过单片机专用的串行编程接口对单片机内部的Flash存储器进行编程,而IAP技术是从结构上将Flash存储器映射为两个存储体,当运行一个存储体上的用户程序时,可对另一个存储体重新编程,之后将控制从一个存储体转向另一个。ISP的实现一般需要很少的外部电路辅助实现,而IAP的实现更加灵活,通常可利用单片机的串行口接到计算机的RS232口,通过专门设计的固件程序来编程内部存储器。

来源:电子产品世界 作者:蒋雅娴

vimrc配置(带注释版)

“=========================================================================
” DesCRiption: 适合自己使用的vimrc文件,for Linux/Windows, GUI/Console

” Last Change: 2010年08月02日 15时13分

” Version:     1.80

“=========================================================================
set nocompatible            ” 关闭 vi 兼容模式
syntax on                   ” 自动语法高亮
colorscheme molokai         ” 设定配色方案
set number                  ” 显示行号
set cursorline              ” 突出显示当前行
set ruler                   ” 打开状态栏标尺
set shiftwidth=4            ” 设定 << 和 >> 命令移动时的宽度为 4
set softtabstop=4           ” 使得按退格键时可以一次删掉 4 个空格
set tabstop=4               ” 设定 tab 长度为 4
set nobackup                ” 覆盖文件时不备份
set autochdir               ” 自动切换当前目录为当前文件所在的目录
filetype plugin indent on   ” 开启插件
set backupcopy=yes          ” 设置备份时的行为为覆盖
set ignorecase smartcase    ” 搜索时忽略大小写,但在有一个或以上大写字母时仍保持对大小写敏感
set nowrapscan              ” 禁止在搜索到文件两端时重新搜索
set incsearch               ” 输入搜索内容时就显示搜索结果
set hlsearch                ” 搜索时高亮显示被找到的文本
set noerrorbells            ” 关闭错误信息响铃
set novisualbell            ” 关闭使用可视响铃代替呼叫
set t_vb=                   ” 置空错误铃声的终端代码
” set showmatch               ” 插入括号时,短暂地跳转到匹配的对应括号
” set matchtime=2             ” 短暂跳转到匹配括号的时间
set magic                   ” 设置魔术
set hidden                  ” 允许在有未保存的修改时切换缓冲区,此时的修改由 vim 负责保存
set guioptions-=T           ” 隐藏工具栏
set guioptions-=m           ” 隐藏菜单栏
set smartindent             ” 开启新行时使用智能自动缩进
set backspace=indent,eol,start
” 不设定在插入状态无法用退格键和 Delete 键删除回车符
set cmdheight=1             ” 设定命令行的行数为 1
set laststatus=2            ” 显示状态栏 (默认值为 1, 无法显示状态栏)
set statusline=\ %<%F[%1*%M%*%n%R%H]%=\ %y\ %0(%{&fileformat}\ %{&encoding}\ %c:%l/%L%)\
” 设置在状态行显示的信息
set foldenable              ” 开始折叠
set foldmethod=syntax       ” 设置语法折叠
set foldcolumn=0            ” 设置折叠区域的宽度
setlocal foldlevel=1        ” 设置折叠层数为
” set foldclose=all           ” 设置为自动关闭折叠
” nnoremap <space> @=((foldclosed(line(‘.’)) < 0) ? ‘zc’ : ‘zo’)<CR>
” 用空格键来开关折叠

” return OS type, eg: windows, or linux, mac, et.st..
function! MySys()
if has(“win16”) || has(“win32”) || has(“win64”) || has(“win95”)
return “windows”
elseif has(“unix”)
return “linux”
endif
endfunction
” 用户目录变量$VIMFILES
if MySys() == “windows”
let $VIMFILES = $VIM.’/vimfiles’
elseif MySys() == “linux”
let $VIMFILES = $HOME.’/.vim’
endif
” 设定doc文档目录
let helptags=$VIMFILES.’/doc’
” 设置字体 以及中文支持
if has(“win32″)
set guifont=Inconsolata:h12:cANSI
endif
” 配置多语言环境
if has(“multi_byte”)
” UTF-8 编码
set encoding=utf-8
set termencoding=utf-8
set formatoptions+=mM
set fencs=utf-8,gbk
if v:lang =~? ‘^zh\|ja\|ko
set ambiwidth=double
endif
if has(“win32”)
source $VIMRUNTIME/delmenu.vim
source $VIMRUNTIME/menu.vim
language messages zh_CN.utf-8
endif
else
echoerr “Sorry, this version of (g)vim was not compiled with +multi_byte”
endif
” Buffers操作快捷方式!
nnoremap <C-RETURN> :bnext<CR>
nnoremap <C-S-RETURN> :bprevious<CR>
” Tab操作快捷方式!
nnoremap <C-TAB> :tabnext<CR>
nnoremap <C-S-TAB> :tabprev<CR>
“关于tab的快捷键
” map tn :tabnext<cr>
” map tp :tabprevious<cr>
” map td :tabnew .<cr>
” map te :tabedit
” map tc :tabclose<cr>
“窗口分割时,进行切换的按键热键需要连接两次,比如从下方窗口移动
“光标到上方窗口,需要<c-w><c-w>k,非常麻烦,现在重映射为<c-k>,切换的
“时候会变得非常方便.
nnoremap <C-h> <C-w>h
nnoremap <C-j> <C-w>j
nnoremap <C-k> <C-w>k
nnoremap <C-l> <C-w>l
“一些不错的映射转换语法(如果在一个文件中混合了不同语言时有用)
nnoremap <leader>1 :set filetype=xhtml<CR>
nnoremap <leader>2 :set filetype=css<CR>
nnoremap <leader>3 :set filetype=javascript<CR>
nnoremap <leader>4 :set filetype=php<CR>
” set fileformats=unix,dos,mac
” nmap <leader>fd :se fileformat=dos<CR>
” nmap <leader>fu :se fileformat=unix<CR>
” use Ctrl+[l|n|p|cc] to list|next|previous|jump to count the result
” map <C-x>l <ESC>:cl<CR>
” map <C-x>n <ESC>:cn<CR>
” map <C-x>p <ESC>:cp<CR>
” map <C-x>c <ESC>:cc<CR>

” 让 Tohtml 产生有 CSS 语法的 html
” syntax/2html.vim,可以用:runtime! syntax/2html.vim
let html_use_css=1
” Python 文件的一般设置,比如不要 tab 等
autocmd FileType python set tabstop=4 shiftwidth=4 expandtab
autocmd FileType python map <F12> :!python %<CR>
” 选中状态下 Ctrl+c 复制
vmap <C-c> “+y
” 打开javascript折叠
let b:javascript_fold=1
” 打开javascript对dom、html和css的支持
let javascript_enable_domhtmlcss=1
” 设置字典 ~/.vim/dict/文件的路径
autocmd filetype javascript set dictionary=$VIMFILES/dict/javascript.dict
autocmd filetype css set dictionary=$VIMFILES/dict/css.dict
autocmd filetype php set dictionary=$VIMFILES/dict/php.dict
“—————————————————————–
” plugin – bufexplorer.vim Buffers切换
” \be 全屏方式查看全部打开的文件列表
” \bv 左右方式查看   \bs 上下方式查看
“—————————————————————–
“—————————————————————–
” plugin – taglist.vim  查看函数列表,需要ctags程序
” F4 打开隐藏taglist窗口
“—————————————————————–
if MySys() == “windows”                ” 设定windows系统中ctags程序的位置
let Tlist_Ctags_Cmd = ‘”‘.$VIMRUNTIME.’/ctags.exe”‘
elseif MySys() == “linux”              ” 设定windows系统中ctags程序的位置
let Tlist_Ctags_Cmd = ‘/usr/bin/ctags’
endif
nnoremap <silent><F4> :TlistToggle<CR>
let Tlist_Show_One_File = 1            ” 不同时显示多个文件的tag,只显示当前文件的
let Tlist_Exit_OnlyWindow = 1          ” 如果taglist窗口是最后一个窗口,则退出vim
let Tlist_Use_Right_Window = 1         ” 在右侧窗口中显示taglist窗口
let Tlist_File_Fold_Auto_Close=1       ” 自动折叠当前非编辑文件的方法列表
let Tlist_Auto_Open = 0
let Tlist_Auto_Update = 1
let Tlist_Hightlight_Tag_On_BufEnter = 1
let Tlist_Enable_Fold_Column = 0
let Tlist_Process_File_Always = 1
let Tlist_Display_Prototype = 0
let Tlist_Compact_Format = 1

“—————————————————————–
” plugin – mark.vim 给各种tags标记不同的颜色,便于观看调式的插件。
” \m  mark or unmark the word under (or before) the cursor
” \r  manually input a regular expression. 用于搜索.
” \n  clear this mark (i.e. the mark under the cursor), or clear all highlighted marks .
” \*  当前MarkWord的下一个     \#  当前MarkWord的上一个
” \/  所有MarkWords的下一个    \?  所有MarkWords的上一个
“—————————————————————–

“—————————————————————–
” plugin – NERD_tree.vim 以树状方式浏览系统中的文件和目录
” :ERDtree 打开NERD_tree         :NERDtreeClose    关闭NERD_tree
” o 打开关闭文件或者目录         t 在标签页中打开
” T 在后台标签页中打开           ! 执行此文件
” p 到上层目录                   P 到根目录
” K 到第一个节点                 J 到最后一个节点
” u 打开上层目录                 m 显示文件系统菜单(添加、删除、移动操作)
” r 递归刷新当前目录             R 递归刷新当前根目录
“—————————————————————–
” F3 NERDTree 切换
map <F3> :NERDTreeToggle<CR>
imap <F3> <ESC>:NERDTreeToggle<CR>

“—————————————————————–
” plugin – NERD_commenter.vim   注释代码用的,
” [count],cc 光标以下count行逐行添加注释(7,cc)
” [count],cu 光标以下count行逐行取消注释(7,cu)
” [count],cm 光标以下count行尝试添加块注释(7,cm)
” ,cA 在行尾插入 /* */,并且进入插入模式。 这个命令方便写注释。
” 注:count参数可选,无则默认为选中行或当前行
“—————————————————————–
let NERDSpaceDelims=1       ” 让注释符与语句之间留一个空格
let NERDCompactSexyComs=1   ” 多行注释时样子更好看

“—————————————————————–
” plugin – DoxygenToolkit.vim  由注释生成文档,并且能够快速生成函数标准注释
“—————————————————————–
let g:DoxygenToolkit_authorName=”Asins – asinsimple AT gmail DOT com”
let g:DoxygenToolkit_briefTag_funcName=”yes”
map <leader>da :DoxAuthor<CR>
map <leader>df :Dox<CR>
map <leader>db :DoxBlock<CR>
map <leader>dc a /*  */<LEFT><LEFT><LEFT>

“—————————————————————–
” plugin – ZenCoding.vim 很酷的插件,HTML代码生成
” 插件最新版:http://github.com/mattn/zencoding-vim
” 常用命令可看:http://nootn.com/blog/Tool/23/
“—————————————————————–
“—————————————————————–
” plugin – checksyntax.vim    JavaScript常见语法错误检查
” 默认快捷方式为 F5
“—————————————————————–
let g:checksyntax_auto = 0 ” 不自动检查
“—————————————————————–
” plugin – NeoComplCache.vim    自动补全插件
“—————————————————————–
let g:AutoComplPop_NotEnableAtStartup = 1
let g:NeoComplCache_EnableAtStartup = 1
let g:NeoComplCache_SmartCase = 1
let g:NeoComplCache_TagsAutoUpdate = 1
let g:NeoComplCache_EnableInfo = 1
let g:NeoComplCache_EnableCamelCaseCompletion = 1
let g:NeoComplCache_MinSyntaxLength = 3
let g:NeoComplCache_EnableSkipCompletion = 1
let g:NeoComplCache_SkipInputTime = ‘0.5’
let g:NeoComplCache_SnippetsDir = $VIMFILES.’/snippets’
” <TAB> completion.
inoremap <expr><TAB> pumvisible() ? “\<C-n>” : “\<TAB>”
” snippets expand key
imap <silent> <C-e> <Plug>(neocomplcache_snippets_expand)
smap <silent> <C-e> <Plug>(neocomplcache_snippets_expand)
“—————————————————————–
” plugin – matchit.vim   对%命令进行扩展使得能在嵌套标签和语句之间跳转
” % 正向匹配      g% 反向匹配
” [% 定位块首     ]% 定位块尾
“—————————————————————–
“—————————————————————–
” plugin – vcscommand.vim   对%命令进行扩展使得能在嵌套标签和语句之间跳转
” SVN/git管理工具
“—————————————————————–
“—————————————————————–
” plugin – a.vim

“—————————————————————–

[转载自:http://www.cnblogs.com/zourrou/archive/2011/04/16/2018493.html]

蓝牙5.0模块方案

蓝牙5.0模块方案

蓝牙5.0模块方案

全新蓝牙5.0标准在性能上将远超目前的版本,也就是蓝牙4.2LE版本,包括在有效传输距离上将是4.2LE版本的4倍,也就是说,理论上,蓝牙发射和接收设备之间的有效工作距离可达300米。而传输速度将是4.2LE版本的2倍,速度上限为24Mbps.
同时,蓝牙5.0允许无需配对接受信标的数据,比如广告、Beacon、位置信息等,这一传输率提高了8倍。

另外,蓝牙5.0还支持室内定位导航功能,可以作为室内导航信标或类似定位设备使用,结合wifi可以实现精度小于1米的室内定位。这样,你就可以在那些非常大的商场中通过支持蓝牙5.0的设备找到路线。不过,在本次的发布中,蓝牙技术联盟并未提到这一项特性。另外,蓝牙5.0针对物联网进行了很多底层优化,力求以更低的功耗和更高的性能为智能家居服务.
1、更快的传输速度
蓝牙5.0的开发人员称,新版本的蓝牙传输速度上限为24Mbps,是之前4.2LE版本的两倍。当然,你在实际生活中是不太可能达到这个极限速度的,但是仍然可以体验到显著的速度提升。;
2、更远的有效距离
蓝牙5.0的另外一个重要改进是,它的有效距离是上一版本的4倍,因此在理论上,当你拿着手机站在距离蓝牙音箱300米的地方,它还是会继续放着你爱的歌。
也就是说,理论上,蓝牙发射和接收设备之间的有效工作距离可达300米。当然,实际的有效距离还取决于你使用的电子设备。
3、导航功能
此外,蓝牙5.0将添加更多的导航功能,因此该技术可以作为室内导航信标或类似定位设备使用,结合wifi可以实现精度小于1米的室内定位。
举个例子,如果你和小编一样是路痴的话,你可以使用蓝牙技术,在诺大的商业中心找到路。
4、物联网功能
物联网还在持续火爆,因此,蓝牙5.0针对物联网进行了很多底层优化,力求以更低的功耗和更高的性能为智能家居服务

5、升级硬件 
此前的一些蓝牙版本更新只要求升级软件,但蓝牙5.0很可能要求升级到新的芯片。不过,旧的硬件仍可以兼容蓝牙5.0,你就无法享用其新的性能了。
搭载蓝牙5.0芯片的旗舰级手机将于2017年问世,相信中低端手机也将陆陆续续内置蓝牙5芯片。苹果将为成为第一批使用该项技术的厂商之一。
6、更多的传输功能
全新的蓝牙5.0能够增加更多的数据传输功能,硬件厂商可以通过蓝牙5.0创建更复杂的连接系统,比如Beacon或位置服务。因此通过蓝牙设备发送的广告数据可以发送少量信息到目标设备中,甚至无需配对。
7、更低的功耗
众所周知,蓝牙是智能手机的必备功能,随着智能设备和移动支付等越来越多需要打开蓝牙,才能享受便利功能逐渐融入人们的生活之中,蓝牙的功耗成为了智能手机待机时间的一大杀手。
为此蓝牙5.0将大大降低了蓝牙的功耗,使人们在使用蓝牙的过程中再也不必担心待机时间短的问题。

英语中有几个表示商品的词 goods commodity product merchandise wares 区别

来源:https://www.zybang.com/question/c92b94d22116484221401ba7990b3676.html

英语中有几个表示商品的,goods,commodity,product,merchandise,wares 区别

goods,commodity,product,merchandise,wares这些名词都可表示“商品,货物”之意.

但是products是产品,goods是物品,commodity是日用品,merchandise是货物
详细点就是:goods一般生活或商业用词,指销售或购入的商品.Goods 货物!
commodity作“商品”解时系经济学名词,也可指日用品.
product一般指工业产品,也可泛指各种各样的产品.
merchandise正式用词,指商业上销售或商家拥有货物的总称.
wares 指上市待卖的商品或货物.多用复数形式.
merchandise 商品,泛指商品,不特指某一商品

英媒晒国外名人收入 比尔·盖茨每天入账6000多万元

国际在线专稿:在各类媒体上看到哪个福布斯富豪又赚了多少钱时,可能并不会给人造成太大的冲击。不就是数字嘛,多加几个零就是了,数数谁不会啊?

但是,如果把这个数字换算成日薪甚至是时薪和自己对比的话,可以说就很扎心了…

英国《每日邮报》近日统计了多位富豪的日均收入以及花钱方式,一起来看看吧。

(并没有换算成小时单位,所以还请放心阅读)

比尔·盖茨

日均收入:710万英镑(约6215万人民币)

据彭博社报道,盖茨去年进账共26亿英镑(约227.7亿人民币),平均到每天就是710万英镑。

除此以外,作为盖茨基金会主席,盖茨本人能够调动的金钱量更大于他自身的收入……

花销:盖茨喜欢去冰岛度假,还常和家人一起驾游艇出游。

盖茨曾说过:“到了一定程度以后,金钱对于我个人就没什么用处了。现在我用钱的主要目的就是贡献给基金会,并将资源送到世界最穷苦的人们手中。”

J·K·罗琳

日均收入:19.8万英镑(约173.4万人民币)

作为《哈利波特》系列的作者,罗琳从其版税、电影、主题公园、各种纪念品以及舞台剧表演中获得巨大的收入,而她现在还在继续写作。

花销:罗琳和丈夫以及三个孩子一起住在爱丁堡,据说曾以百万英镑买下邻居的房子……

阿黛尔·阿德金斯

日均收入:14.4万英镑(约126万人民币)

阿黛尔的第三个专辑《25》全球共售出2000万份,仅在英国就帮她捞了1650万英镑(约1.44亿人民币)。

花销:在美国和英国多地拥有房产。

大卫·贝克汉姆

日均收入:13.6万英镑(约119.1万人民币)

小贝的肖像权公司去年给他开出了1270万英镑(约1.1亿人民币)的薪水,使用其形象打广告的商家遍布各种行业。

花销:小贝在科茨沃尔德的房子花了近六百万英镑(约5255万人民币),他的宝贝女儿哈珀(Harper)上芭蕾课也是一笔不小的花销。

英国女王

日均收入:11.7万英镑(约102.5万人民币)

英国政府会将皇室不动产利润中的15%给予女王,去年她因此获得的收入共有4280万英镑(约3.7亿人民币),近期预计还将增长。

花销:女王的各种度假在她总花销中其实所占比例不大,维护各个皇家宫殿的费用才是大头。

詹妮弗·劳伦斯

日均收入:4.9万英镑(约42.9万人民币)

尽管这位奥斯卡影后时常抱怨自己的收入不如一些男演员高,但她去年也有1820万英镑的收入(约1.59亿人民币)。

花销:在比弗利山庄的住宅价值540万英镑(约4728万人民币)。

 

使用反向ssh从外网访问内网主机的方法详解

由于我们自己使用的电脑未必有外网ip,因此我们需要一个有固定外网ip的服务器(随便搞个腾讯云,阿里云的小机子就行),然后用这台服务器与内网的机子进行通信,我们到时候要先登陆自己的服务器,然后再利用这个服务器去访问内网的主机。
1、准备好有固定ip的服务器A,以及待访问的内网机器B。两者都开着sshd服务,端口号默认都是22。顺便做好ssh免密码登陆。
2、内网主机B主动连接服务器A,执行以下命令:
 >$ ssh -NfR 10000:localhost:22 username@servername -p 22
这条命令的意思是在后台执行(-f),不实际连接而是做port forwarding(-N),做反向ssh(-R),将远程服务器的10000端口映射成连接本机(B)与该服务器的反向ssh的端口。
附:这里有必要加强一下记忆,这个端口号一不小心就容易搞混

man文档中的参数命令是这样的:

-R [bind_address:]port:host:hostport
-R [bind_address:]port:local_socket
-R remote_socket:host:hostport
-R remote_socket:local_socket

bind_address以及其后面的port是指远程主机的ip以及端口,host以及其后的hostport是指本机的ip和端口。由于ssh命令本身需要远程主机的ip(上上条命令中的servername),因此这个bind_address原则上是可以省略的。
执行完这条命令,我们可以在服务器A上看到他的10000端口已经开始监听:
~]$ ss -ant | grep 10000
LISTEN 0 128 127.0.0.1:10000 *:*
3、在上面的操作中,这个1111端口就已经映射成了内网主机B的22端口了,现在我们只要ssh到自己的这个端口就行了。在服务器A中执行:
 >$ ssh username@localhost -p 10000
这样就成功的登陆了内网的主机了。
功能优化
上面的做法其实有一个问题,就是反向ssh可能会不稳定,主机B对服务器A的端口映射可能会断掉,那么这时候就需要主机B重新链接,而显然远在外地的我无法登陆B

这其实有一个非常简单的解决方案,就是用autossh替代步骤2中的ssh:
C:\ > autossh -M 2222 -NfR 10000:localhost:22 username@servername -p 22
后面的参数跟ssh都一样,只是多了一个-M参数,这个参数的意思就是用本机的2222端口来监听ssh,每当他断了就重新把他连起来。。。不过man文档中也说了,这个端口又叫echo port,他其实是有一对端口的形式出现,第二个端口就是这个端口号加一。因此我们要保证这个端口号和这个端口号加一的端口号不被占用。

有时,我们会想在局域网外访问局域网内的机器。这时,我们可以使用SSH的反向连接来实现。
设备A:位于局域网内,可以访问代理服务器B。 假设该设备IP:A.A.A.A,用户名userA
设备B:位于局域网外,作为访问设备A的代理服务器,不可访问A。假设该设备IP:B.B.B.B,用户名userB
设备C:想要访问A的设备,可以访问B,无法直接访问A。假设该设备IP:C.C.C.C,用户名userC

目标:设备C可以通过SSH访问局域网内设备C

条件:三台设备都需要包含SSH客户端,A,B设备需要包含SSH服务端。

在A设备上建立A设备到B设备的反向代理:

ssh -fCNR <port_b1>:localhost:22 userB@B.B.B.B

例如:ssh -fCNR 10000:localhost:22 userB@B.B.B.B (此时B设备上已经可以通过ssh -p 10000 userA@localhost连接到设备A)

<port_b1>:建立在B机器上,用来代理设备A机器22端口的端口。

userB@B.B.B.B :B机器的用户名和IP地址。

在B设备上建立B设备到A设备的正向代理:(这样做的目的是为了实现和外网的通信)

ssh -fCNL *:<port_b2>:localhost:<port_b1> userB@localhost

例如:ssh -fCNL *:10001:localhost:10000 userB@localhost

<port_b2>:用作本地转发的端口,用来和外网通信,并将数据转发到<port_b1>,实现从其他机器可以访问。

*代表可以接受来自任意机器的访问。

现在C机器上可以通过B机器SSH到A机器

ssh -p<port_b2> userA@B.B.B.B

参数介绍

-f 后台运行-C 允许压缩数据-N 不执行任何命令-R 将端口绑定到远程服务器,反向代理-L 将端口绑定到本地客户端,正向代理

STUN协议,探测NAT类型

1, STUN客户端(101:10)向STUN服务器(404:40)发送请求,要求得到自身经NAT映射后的地址(202:20):
      a,收不到服务器回复,则认为UDP被防火墙阻断,不能通信,网络类型:Blocked.
      b,收到服务器回复(地址要嘛是映射地址要嘛就是源地址),对比本地地址,如果相同(直接返回的就是源地址101:10),则认为无NAT设备(没经过NAT映射转换),进入第2步,否则认为有NAT设备,进入3步.
   2,(已确认无NAT设备)STUN客户端向STUN服务器发送请求,要求服务器从其他IP和PORT(505:50)向客户端回复包:
      a,收不到服务器从其他IP地址的回复,认为包被前置防火墙阻断,网络类型:Symmetric UDPFirewall.(如果没有NAT的话是无论如何都能收到回复的,只有一点受到防火墙的阻断,有时候杀毒软件也阻断)

b,收到则认为客户端处在一个开放的网络上,网络类型:Opened.

   3,(已确认存在NAT设备)STUN客户端(101:10)向STUN服务器(404:40)发送请求,要求服务器从其他IP和PORT(505:50)向客户端回复包:

a,收不到服务器从其他IP地址(包括IP和Port)的回复,认为包被前置NAT设备阻断,进入第4步.(如果不是前置的就相当于全开Opened或Full ConeNat类型,无论那个IP和端口都能接收到回复)
      b,收到则认为NAT设备类型为Full Cone,即网络类型:Full Cone NAT.(此没有什么限制的基本和没有NAT一样Opened)
  4, STUN客户端(101:10)向STUN服务器(404:40)的另外一个IP地址(505:40,端口不能改变)发送请求,要求得到自身经NAT映射后的地址(202:20,如果不是对称的应该返回这个映射地址),并对比之(与第一步中返回的映射地址比对)
      a,地址不相同,则网络类型:Symmetric NAT.(如果是对称类型,则101:10在向一个不同的IP地址(505,端口不变)发送请求时会映射一个新的端口,此处相当于生成一个202:21,比对不相同)
      b,相同则认为是Restricted NAT(受限的),进入第5步,进一步确认类型.
5, (已确认RestrictedNAT设备)STUN客户端(101:10)向STUN服务器(404:40)发送请求,要求服务器从相同IP(404)的其他PORT(41)向客户端回复包:
      a,收不到服务器从其他PORT地址的回复,认为包被前置NAT设备阻断,网络类型:Port Restricted coneNAT.(端口改变了,端口受限相当于我一个人A(101)向B(404)要右手(40端口)的苹果,而B左手(41端口)有个香蕉,A只要B的右手苹果,而B给了A一个左手的香蕉,A肯定是不要的,相当于收不到回复)
      b,收到则认为网络类型: Restricted cone NAT.(IP受限,对端口没什么要求,只要是404

这个IP就行,无论用那个端口都行)

以上实现过程作为服务器只有双IP的服务器才能实现,针对我们这些电脑只有一个IP的开发者来说,我们就得通过两台电脑来模拟实现双IP了,这地方上面请求的服务器地址就得改变一下了,个人认为这里的Server1:404地址的端口必须和Server2:505地址的端口一样,才能实现,端口相同也不会影响测试效果的.

来源:http://blog.csdn.net/hack8/article/details/6593768

客户端主机所在网络可以分为以下类型:

1, Opened: 即主机拥有公网IP,并且没有防火墙,可自由与外部通信.

2, Full Cone NAT: 主机前有NAT设备,

NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,并且从任意外部地址发送到该NAT设备UDP端口B的包都会被转到主机端口A.

3, Restricted cone NAT: 主机前有NAT设备,

 NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,但只有从之前该主机发出包的目的IP发出到该NAT设备UDP端口B的包才会被转到主机端口A.

4, Port Restricted cone NAT: 主机前有NAT设备,

  NAT规则如下:从主机UDP端口A发出的数据包都会对应到NAT设备出口IP的端口B,但只有从之前该主机发出包的目的IP/

解读Linux启动过程

转自:https://my.oschina.net/macwe/blog/1531024

解读Linux启动过程

 

1. 概述

本文解读一下从CPU加电自检到启动init进程的过程, 先通过下面这张图大致看一下Linux启动的整个过程。

本文的分析环境是GRUB 0.97 + Linux 2.6.18。

2. BIOS

CPU加电后首先工作在实模式并初始化CS:IP=FFFF:FFF0,BIOS的入口代码必须从该地址开始。BIOS完成相应的硬件检查并提供一系列中断服务例程,这些中断服务提供给系统软件访问硬件资源(比如磁盘、显示器等),最后选择一个启动盘加载第一个扇区(即:MBR,共512字节)数据到内存0x7C00处,并从这里开始执行指令(CS:IP=0000:7C00),对于笔者的电脑来说这就是GRUB的Stage1部分。

3. GRUB

GRUB的作用是bootloader,用来引导各种OS的。

3.1. Stage1

Stage1就是MBR,由BIOS把它从磁盘的0扇区加载到0x7c00处,大小固定位512字节,此时的CPU上下文如下:

eax=0011aa55 ebx=00000080 ecx=00000000 edx=00000080 esi=0000f4a0 edi=0000fff0
eip=00007c00 esp=00007800 ebp=00000000 iopl=0 nv up ei pl zr na po nc
cs=0000 ds=0000 es=0000 fs=0000 gs=0000 ss=0000 eflags=00000246
// 注: dl=启动磁盘号, 00H~7FH是软盘, 80H~FFH是硬盘。

因为只能是512字节,大小受限,它就干一件事,把Stage2的第一个512字节读取到0x8000,然后jmp到0x8000继续执行。

3.1.1. 读磁盘

磁盘扇区寻址有两种方式:

  • CHS方式:传统的方式,使用三元组(10位Cylinder, 8位Head, 6位Sector)来寻找扇区,最大只能找到(2^10) * (2^8) * (2^6) * 512 = 8GB的硬盘容量,现在的硬盘明显不够用了。
  • LBA方式:现在的方式,使用48位线性地址来寻找扇区,最大支持(2^48) * 512 = 128PB的硬盘空间。虽然机械上还是CHS的结构,不过磁盘的固件会自动完成LBA到CHS的转换。

因为CHS明显不适合现在的硬盘,所以LBA模式寻址是现在的PC的标配了吧!万一磁盘不支持LBA或者是软盘,需要我们手工转换成CHS模式。转换公式如下(就是三维空间定位一个点的问题):

磁道号C = LBA / 每磁道的扇区数SPT / 盘面总HPC
磁头号H = (LBA / 每磁道的扇区数SPT) mod HPC
扇区号S = (LBA mod SPT) + 1

判断是否支持LBA模式

/* check if LBA is supported */
movb	$0x41, %ah
movw	$0x55aa, %bx
int	$0x13

如果返回成功(CF=1)并且BX值是0xAA55表示支持LBA寻址(用Extensions方法)。

注意:3.5英寸软盘需要使用CHS方式寻址,它的CHS参数是80个柱面、2个磁头、每个磁道18个扇区,每扇区512字节,共1.44MB容量。

LBA模式读的功能号是AH=42h,DL参数是磁盘号,DS:SI参数是Disk Address Packet(DAP)结构体的内存地址,定义如下:

struct DAP {
    uint8_t sz; // 结构体大小
    uint8_t unused;
    uint16_t sector_cnt; // 需要都的扇区总数
    struct dst_addr { // 内存地址,读到这里
        uint16_t offset;
        uint16_t segment;
    };
    uint64_t lba_addr;  // 磁盘的LBA地址
};

参考:

  • https://en.wikipedia.org/wiki/Logical_block_addressing#CHS_conversion
  • https://en.wikipedia.org/wiki/INT_13H

3.2. Stage2

Stage2就是GRUB剩下的全部的代码了,包括BIOS中断服务的封装给C代码使用、键盘驱动、文件系统驱动、串口、网络驱动等等,它提供了一个小型的命令行环境,可以解析用户输入命令并执行对OS的启动。

3.2.1. start.S

首先Stage2的头512字节(start.S)被加载到0x8000,并在这里开始执行,此时的CPU上下文如下:

eax=00000000 ebx=00007000 ecx=00646165 edx=00000080 esi=00007c05 edi=0000fff0
eip=00008000 esp=00001ffe ebp=00000000 iopl=0 nv up ei pl zr na po nc
cs=0000 ds=0000 es=0800 fs=0000 gs=0000 ss=0000 eflags=00000246

start.S的工作是把Stage2的后续部分全部加载到内存中(从0x8200开始),有103KB大小。

3.2.2. asm.S

asm.S是0x8200处的代码,先看一下CPU上下文环境:

eax=00000e00 ebx=00000001 ecx=00646165 edx=00000080 esi=00008116 edi=000081e8
eip=00008200 esp=00001ffe ebp=000062d8 iopl=0 nv up ei pl zr na po nc
cs=0000 ds=0000 es=1ae0 fs=0000 gs=0000 ss=0000 eflags=00000246
3.2.2.1. 最开始的代码应该设置好段寄存器和栈
cli
/* set up %ds, %ss, and %es */
/* cs=0000 ds=0000 es=0000 fs=0000 gs=0000 ss=0000 */
xorw	%ax, %ax
movw	%ax, %ds
movw	%ax, %ss
movw	%ax, %es

/* set up the real mode/BIOS stack */
movl	$STACKOFF, %ebp
movl	%ebp, %esp
sti

此时:

cs=0000 ds=0000 es=0000 ss=0000 esp=00001ff0 ebp=00001ff0。
3.2.2.2. 保护模式和实模式

因为GRUB没有实现自己的中断服务,所以访问硬件资源还是使用BIOS的中断服务例程(实模式)。GRUB的命令行环境是工作在保护模式下的,所以当GRUB需要访问BIOS中断的时候需要切换回实模式,于是在GRUB执行过程中会有频繁的实模式和保护模式的互相切换操作,当切换回实模式后别忘了保存保护模式下的栈指针

(1) 实模式进入保护模式

/* transition to protected mode */
DATA32	call EXT_C(real_to_prot)

/* The ".code32" directive takes GAS out of 16-bit mode. */
.code32

下图是实模式到保护模式的切换步骤:

GRUB没有设置分页机制和新的中断,所以GRUB的保护模式访问的是物理内存且是不能使用INT指令,不过对于bootloader来说够用了。因为需要切换到保护模式栈,原来的返回地址要放到新的栈上,以保证能够正常ret:

ENTRY(real_to_prot)
	...
	/* put the return address in a known safe location */
	movl	(%esp), %eax
	movl	%eax, STACKOFF  ; 把返回地址保存起来备用

	/* get protected mode stack */
	movl	protstack, %eax
	movl	%eax, %esp
	movl	%eax, %ebp      ; 设置保护模式的栈

	/* get return address onto the right stack */
	movl	STACKOFF, %eax
	movl	%eax, (%esp)    ; 把返回地址重新放到栈上
	
	/* zero %eax */
	xorl	%eax, %eax

	/* return on the old (or initialized) stack! */
	ret                     ; 正常返回

(2) 保护模式切换回实模式

	/* enter real mode */
	call	EXT_C(prot_to_real)
	
	.code16

下图说明了保护模式切换回实模式的步骤:

保护模式的栈需要保存起来以便恢复现场,让C代码正确运行,实模式的栈每次都重置为STACKOFF即可,和(1)一样,也要设置好返回地址:

ENTRY(prot_to_real)
	...
	/* save the protected mode stack */
	movl	%esp, %eax
	movl	%eax, protstack  ; 把栈保存起来

	/* get the return address */
	movl	(%esp), %eax
	movl	%eax, STACKOFF   ; 返回地址放到实模式栈里

	/* set up new stack */
	movl	$STACKOFF, %eax  ; 设置实模式的栈
	movl	%eax, %esp
	movl	%eax, %ebp
	... 
3.2.2.3. 创建C运行时环境

C的运行环境主要包括栈、bss数据区、代码区。随着切换到保护模式,栈已经设置好了;随着Stage2从磁盘加载到内存,代码区和bss区都已经在内存了,最后还需要把bss区给初始化一下(清0),接下来即可愉快的执行C代码了。

3.2.2.4. 执行cmain()

先执行一个init_bios_info()获取BIOS的信息,比如被BIOS使用的内存空间(影响我们Linux映像加载的位置)、磁盘信息、ROM信息、APM信息,最后调用cmain()。 cmain()函数在stage2.c文件中,其中最主要的函数run_menu()是启动一个死循环来提供命令行解析执行环境。

3.2.2.5. load_image()

如果grub.cfg或者用户执行kenrel命令,会调用load_image()函数来将内核加载到内存中。至于如何加载linux镜像在Documentation的boot.txt和zero-page.txt有详细说明。

load_image()是一个非常长的函数,它要处理支持的各种内核镜像格式。Linux镜像vmlinuz文件头是struct linux_kernel_header结构体,该结构体里头说明了这个镜像使用的boot协议版本、实模式大小、加载标记位和需要GRUB填写的一些参数(比如:内核启动参数地址)。

  • 实模式部分:始终被加载到0x90000位置,并从0x90200开始执行(linux 0.11就这样做了)。
  • 保护模式部分:我们现在使用的内核比较大(大于512KB),叫做bzImage,加载到0x100000(高位地址,1MB)开始的位置,可以任意大小了。否则小内核zImage放在0x10000到mbi.mem_lower * 1024(一般是0x90000)区域。
3.2.2.6. linux_boot()

我们正常的启动过程调用的是big_linux_boot()函数,把实模式部分copy到0x90000后,设置其他段寄存器值位0x9000, 设置CS:IP=9020:0000开始执行(使用far jmp)。

至此GRUB的工作完成,接下来执行权交给Linux了。

4. setup.S

该文件在arch/i386/boot/setup.S,主要作用是收集硬件信息并进入保护模式head.S。初始的CPU上下文如下:

eax=00000000 ebx=00009000 ecx=00000000 edx=00000003 esi=002d8b54 edi=0009a000
eip=00000000 esp=00009000 ebp=00001ff0 iopl=0 nv up di pl zr na po nc
cs=9020 ds=9000 es=9000 fs=9000 gs=9000 ss=9000  eflags=00000046

4.1. 自身检查

先检查自己setup.S是否合法,主要是检查末尾的两个magic是否一致

# Setup signature -- must be last
setup_sig1:	.word	SIG1
setup_sig2:	.word	SIG2

4.2. 收集硬件信息

主要是通过BIOS中断来收集硬件信息。收集的信息包括内存大小、键盘、鼠标、显卡、硬盘、APM等等。收集的硬件信息保存在0x9000处:

# 设置ds = 0x9000,用来保存硬件信息
movw	%cs, %ax			# aka SETUPSEG
subw	$DELTA_INITSEG, %ax 		# aka INITSEG
movw	%ax, %ds

这里看一下如何获取内存大小,这样OS才能进行内存管理。这里用三种方法获取内存信息:

  1. e820h:请求中断INT 15H,AX=E820H时返回可用的物理内存信息,e820由此得名,参考http://www.uruk.org/orig-grub/mem64mb.html。由于内存的使用是不连续的,通过连续调用INT 15H得到所有可用的内存区域,每次查询得到的结果ES:DI是个struct address_range_descriptor结构体,返回的结果都是64位的,完全能够满足目前PC的需求了。
     struct address_range_descriptor {
     	uint32_t base_addr_low;   // 起始物理地址
     	uint32_t base_addr_high;
     	uint32_t length_low;      // 长度
     	uint32_t length_high;
     	uint8_t type;             // 1=OS可用的, 2=保留的,OS不可用
     };
    
  2. e801h:通过请求中断INT15h,AX=e801H返回结果,最高只能得到4GB内存结果。
  3. 88h:古老的办法,通过请求中断INT15h,AH=88H返回结果。最高只能得到16MB或者64MB的内存,现在的电脑不适用了。

扩展阅读:http://wiki.osdev.org/Detecting_Memory_(x86)#E820h

4.3. 启用A20

让CPU访问1MB以上的扩展内存,否则访问的是X mod 1MB的地址。下面列举三种开启A20的方法:

  1. 使用I/0端口92H,AL的将1-bit置1
     inb	$0x92, %al			# Configuration Port A
     orb	$0x02, %al			# "fast A20" version
     andb	$0xFE, %al			# don't accidentally reset
     outb	%al, $0x92
    
  2. 使用BIOS中断INT 0x15, AX=0x2401
     movw	$0x2401, %ax
     pushfl					# Be paranoid about flags
     int	$0x15
     popfl
    
  3. 使用键盘控制器
     movb	 $0xD1, %al			# command write
     outb	 %al, $0x64
     call	 empty_8042
    
     movb	 $0xDF, %al			# A20 on
     outb	 %al, $0x60
     call	 empty_8042
    

4.4. 进入保护模式

4.4.1. 临时的GDT和IDT

这里的IDT全部是0;Linux目前使用的GDT如下:

gdt:
	.fill GDT_ENTRY_BOOT_CS,8,0

	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9A00				# code read/exec
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)

	.word	0xFFFF				# 4Gb - (0x100000*0x1000 = 4Gb)
	.word	0				# base address = 0
	.word	0x9200				# data read/write
	.word	0x00CF				# granularity = 4096, 386
						#  (+5th nibble of limit)
gdt_end:

这里只定义了两个DPL为0的代码段和数据段,只给内核使用的。

4.4.1. 设置CR0.PE

这里使用lmsw指令,它和mov cr0, X是等价的

movw	$1, %ax				# protected mode (PE) bit
lmsw	%ax				# This is it!
jmp	flush_instr

4.5. 调转到head.S(CS:EIP=0x10:100000)

至此硬件信息就收集完成,这些收集到的硬件信息都保存在0x90000处,后续OS可以使用这些硬件信息来管理了。

5. head.S

该文件位于arch/i386/kernel/head.S,这个是内核保护模式的代码的起点,笔者电脑的位置在0x100000,此时CPU上下文是:

eax=00000001 ebx=00000000 ecx=0000ff03 edx=47530081 esi=00090000 edi=00090000
eip=00100000 esp=00008ffe ebp=00001ff0 iopl=0 nv up di pl nz na pe nc
cs=0010 ds=0018 es=0018 fs=0018 gs=0018 ss=0018               eflags=00000002

注:已经进入保护模式,CS的值是GDT表项的索引。

它的作用就是设置真正的分段机制和分页机制、启动多处理器、设置C运行环境,最后执行start_kernel()函数。

5.1. startup_32

5.1.1. 加载临时的分段机制

boot_gdt_table就是临时的GDT,其实和start.S的一样:

	lgdt boot_gdt_descr - __PAGE_OFFSET
	movl $(__BOOT_DS),%eax
	movl %eax,%ds
	movl %eax,%es
	movl %eax,%fs
	movl %eax,%gs

ENTRY(boot_gdt_table)
	.fill GDT_ENTRY_BOOT_CS,8,0
	.quad 0x00cf9a000000ffff	/* kernel 4GB code at 0x00000000 */
	.quad 0x00cf92000000ffff	/* kernel 4GB data at 0x00000000 */

5.1.2. 初始化内核bss区和内核启动参数

为了让C代码正常运行,bss区全部清0,启动参数需要移动到boot_params位置。

5.1.3. 启动临时分页机制

临时的页表,只要能够满足内核使用就行。页目录表是swapper_pg_dir,它是一个4096大小的内存区域,默认全是0。一般__PAGE_OFFSET=0xC0000000(3GB),这是要把物理地址0x00000000映射到0xc0000000的地址空间(内核地址空间)。下面是页目录表和页表的初始化代码:

page_pde_offset = (__PAGE_OFFSET >> 20); // 3072,页目录的偏移

	// 页目录表存放在pg0位置,arch/i386/kernel/vmlinux.lds中定义
	movl $(pg0 - __PAGE_OFFSET), %edi
	movl $(swapper_pg_dir - __PAGE_OFFSET), %edx  // edx是页目录表的地址
	movl $0x007, %eax			/* 0x007 = PRESENT+RW+USER */
10:
	// 创建一个页目录项
	leal 0x007(%edi),%ecx			/* Create PDE entry */
	movl %ecx,(%edx)			/* Store identity PDE entry */
	movl %ecx,page_pde_offset(%edx)		/* Store kernel PDE entry */
	addl $4,%edx   // 指向swapper_pg_dir的下一个项
	movl $1024, %ecx   // 每个页表1024个项目
11:
	stosl  // eax -> [edi]; edi = edi + 4
	addl $0x1000,%eax // 每次循环,下一个页目录项
	loop 11b
	/* End condition: we must map up to and including INIT_MAP_BEYOND_END */
	/* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
	leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp  // 页表覆盖到这里就终止
	cmpl %ebp,%eax
	jb 10b
	movl %edi,(init_pg_tables_end - __PAGE_OFFSET)

下面是对上面代码的翻译(这样更有利于理解):

extern uint32_t *pg0;  // 初始值全0
extern uint32_t *swapper_pg_dir;  // 初始值全0

void init_page_tables()
{
	uint32_t PAGE_FLAGS = 0x007; // PRESENT+RW+USER
	uint32_t page_pde_offset = (_PAGE_OFFSET >> 20); // 3072
	uint32_t addr = 0 | PAGE_FLAGS;  // 内存地址+页表属性
	uint32_t *pg_dir_ptr = swapper_pg_dir; // 页目录表项指针
	uint32_t *pg0_ptr = pg0;  // 页表项指针
	
	for (;;) {
		// 设置页目录项,同时映射两个地址,让物理地址和虚拟地址都能访问,
		*pg_dir_ptr = pg0 | PAGE_FLAGS;     // 0, 1
		*(uint32_t *)((char *)pg_dir_ptr + page_pde_offset) = pg0 | PAGE_FLAGS;  // 768, 769
		pg_dir_ptr++;
		
		// 设置页表项目
		for (int i = 0; i < 1024; i++) {
			*pg0++ = addr;
			addr += 0x1000;
		}
		// 退出条件,实际上只映射了两个页目录就退出了(0,1,768, 769)
		if (pg0[INIT_MAP_BEYOND_END] | PAGE_FLAGS) >= addr) {
			init_pg_tables_end = pg0_ptr;
			return;
		}
	}	
};

5.1.4. 设置栈

/* Set up the stack pointer */
	lss stack_start,%esp

ENTRY(stack_start)
	.long init_thread_union+THREAD_SIZE
	.long __BOOT_DS

/* arch/i386/kernel/init_task.c
* Initial thread structure.
*
* We need to make sure that this is THREAD_SIZE aligned due to the
* way process stacks are handled. This is done by having a special
* "init_task" linker map entry..
*/
union thread_union init_thread_union 
	__attribute__((__section__(".data.init_task"))) =
		{ INIT_THREAD_INFO(init_task) };

内核最初使用的栈是init_task进程的,也就是0号进程的栈,这个进程是系统唯一一个静态定义而不是通过fork()产生的进程。

5.1.5. 设置真正的IDT和GDT

	lgdt cpu_gdt_descr   // 真正的GDT
	lidt idt_descr    //真正的IDT
	ljmp $(__KERNEL_CS),$1f   // 重置CS
1:	movl $(__KERNEL_DS),%eax	# reload all the segment registers
	movl %eax,%ss			# after changing gdt.  // 重置SS

	movl $(__USER_DS),%eax		# DS/ES contains default USER segment
	movl %eax,%ds
	movl %eax,%es

	xorl %eax,%eax			# Clear FS/GS and LDT
	movl %eax,%fs
	movl %eax,%gs
	lldt %ax
	cld			# gcc2 wants the direction flag cleared at all times
	// push一个假的返回地址以满足 start_kernel()函数return的要求
	pushl %eax		# fake return address  

对于IDT先全部初始化成ignore_int例程:

setup_idt:
	lea ignore_int,%edx
	movl $(__KERNEL_CS << 16),%eax
	movw %dx,%ax		/* selector = 0x0010 = cs */
	movw $0x8E00,%dx	/* interrupt gate - dpl=0, present */

	lea idt_table,%edi
	mov $256,%ecx
rp_sidt:
	movl %eax,(%edi)
	movl %edx,4(%edi)
	addl $8,%edi
	dec %ecx
	jne rp_sidt
	ret

ignore_int例程就干一件事,打印一个错误信息"Unknown interrupt or fault at EIP %p %p %p\n"

对于GDT我们最关心的__KERNEL_CS、__KERNEL_DS、__USER_CS、__USER_DS这4个段描述符:

.quad 0x00cf9a000000ffff	/* 0x60 kernel 4GB code at 0x00000000 */
.quad 0x00cf92000000ffff	/* 0x68 kernel 4GB data at 0x00000000 */
.quad 0x00cffa000000ffff	/* 0x73 user 4GB code at 0x00000000 */
.quad 0x00cff2000000ffff	/* 0x7b user 4GB data at 0x00000000 */

至此分段机制、分页机制、栈都设置好了,接下去可以开心的jmp start_kernel了。

6. start_kernel

该函数在linux/init/main.c文件里。我们可以认为start_kernel是0号进程init_task的入口函数,0号进程代表整个linux内核且每个CPU有一个。 这个函数开始做一系列的内核功能初始化,我们重点看rest_init()函数。

6.1.rest_init

这是start_kernel的最后一行,它启动一个内核线程运行init函数后就什么事情也不做了(死循环,始终交出CPU使用权)。

static void noinline rest_init(void)
{
	kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND); // 启动init
	……
	/* Call into cpu_idle with preempt disabled */
	cpu_idle();  // 0号进程什么事也不做
}

6.2. init()

该函数的末尾fork了”/bin/init”进程。这样1号进程init就启动了,接下去就交给init进程去做应用层该做的事情了!

// 以下进程启动后父进程都是0号进程
if (ramdisk_execute_command) {
	run_init_process(ramdisk_execute_command);
	printk(KERN_WARNING "Failed to execute %s\n",
			ramdisk_execute_command);
}

/*
 * We try each of these until one succeeds.
 *
 * The Bourne shell can be used instead of init if we are 
 * trying to recover a really broken machine.
 */
if (execute_command) {
	run_init_process(execute_command);
	printk(KERN_WARNING "Failed to execute %s.  Attempting "
				"defaults...\n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");

附录1. 启动多核CPU

以上解读的内容只在0号CPU上执行,如果是多CPU的环境,还要初始化其他的CPU。多CPU启动的起点是start_kernel()->rest_init()>init()->smp_init()。而smp_init()函数给每个CPU上调用cpu_up()do_boot_cpu()函数,每个CPU都要再走一遍head.S的流程,然后启动自己的idle进程(内核态0号进程)。

附录2. x64的不同

i386和x64的启动代码主要区别在head.S中。

  • 页表格式不同,i386使用两级页表,x64使用4级页表。
  • 多了兼容32位的代码段和数据段__USER32_CS、__USER32_DS和__KERNEL32_CS
  • x64段寄存器用法和i386的不同:x64下面CS、DS、ES、SS不用了,始终为0。而FS、GS寄存器的用法倒像是实模式下的,主要考虑是保留两个作为基地址好让线性地址计算方便。FS:XX = MSR_FS_BASE + XXGS:XX = MSR_GS_BASE + XX, 不是段描述符索引了(像实模式的分段)。