<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>刘建文 &#124; 学术半·IT歌·文</title>
	<atom:link href="http://arttech.us/feed" rel="self" type="application/rss+xml" />
	<link>http://arttech.us</link>
	<description>掌握艺术的技术 Art tech·nology</description>
	<lastBuildDate>Sun, 05 Feb 2012 01:17:38 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>2011年总结</title>
		<link>http://arttech.us/y-2012/2011-report.html</link>
		<comments>http://arttech.us/y-2012/2011-report.html#comments</comments>
		<pubDate>Thu, 05 Jan 2012 17:51:01 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[随笔]]></category>
		<category><![CDATA[网志]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=569</guid>
		<description><![CDATA[2011谢幕。顺势而为，写下这篇年终报告。 去年的总结我对自己的前路作了个预测，非此即彼的，此为上班，彼为下狱。结果呢？下狱。这一年“地狱”生活概括起来，充实幸福但不快乐。看着自己每天在成长，我感觉生活是充实的；想到生活比我更不幸的同龄人，我是幸福的；但是，忧乐无籍，身体欠安等原因，我总是快乐不起来。另外，再加上物质资源的进一步的枯竭，我过着一种比10年更低质量的生活。这种低质量的“地狱”生活是怎么样的呢？受限于文笔力，接下来我尽量在适合的地方用客观的语言为没有游玩过地狱的小盆友介绍，好让你们某天也构想下来玩一玩。 2011坚持渡己 我的求学耕耘路并不是像去年总结中臆想的那样，能在2011年初收获一份小小的回报；各种客观事实证明，我存在某种乏缺，说不清是什么，姑且叫成熟度吧。这种乏缺注定了2011只是2010的一个延续，未能成熟到收获一份硕果。于是略去过程，2011表面结果与2010一模一样——单身、待业和生活窘迫。而生活过程就如我的生活质量下降一级一样，更加的孤立和封闭。基本上像庄周所描述的独与天地精神往来，自己关起门来，读书思考写字，没有世俗社交，最多在豆瓣网上逗俩小盆友玩玩，安慰那孤单寂寞的小我。 面对这种年复年的非人生活，我和别人开玩笑说，我是个苦行修练的XX居士呢；别人反问，那你怎么那么好色？于是我说我是个看破红尘的现世隐士，别人问，那你怎么住在大城市？这种刻板拷问让我想起了济公被斥为僧不应酒肉穿肠。济公的解释是，他人修口不修心，为我修心不修口。渡人渡己的核心是内心，生活方式我认为不重要。我更确切的身份是一名理想志士，我在为自己的理想奋斗。我的理想是什么？我要过我爱过的生活，我不愿意与现实全面妥协。 不知不觉，这种孤立的求学奋斗生活已经进入第五个年头。即便我的命运如此不顺，我的精神依然是很自信的，甚至为自己的成长感到自傲。但我不会把自己的自信与命运不济的矛盾归结为所谓的怀才不遇。怀才不遇只是庸才的自恋的安慰，是一种不切实际的抱怨。因为怀才与不遇在逻辑上是不通的，是个悖论。不遇的更大可能是无才，至少有所乏缺。我必须能客观认识自己，不然活不到今天。 学业 今年的学业可谓一波三折。先是年初设备驱动转C通用，接着觉得C通用偏软转向8051（单击这里），接着发现8051门槛过低应用领域过窄转回设备驱动，然后又觉得光有设备驱动的经验，竞争力不够，补学内核移植……最后到年底12月忽然发觉有了创业的想法，然后发现C通用是最具投入价值而回归C通用（看这里），现在正在学习算法和数据结构，并在写一些实用程序。 用了一年的时间，从C通用又回到C通用，这不是瞎折腾么？我承认折腾是有，但没有瞎；因为我现在回想起来，确实有走了弯路，但这些弯路没有白走，只是这些投入未能在短期内获得直接回报。例如如果接下来我的工作主要以C应用编程为主，那么设备驱动的经验和知识将被搁置，但是对操作系统的研究不可能对C编程没有影响；又如，如果没有研究8051，我不可能对汇编语言有深入的认识，从而也影响我更深入的认识C语言。 非专业 这一年折腾生活除了夯实了我的编程基础能力外，还孵化出了两个可能改变我一生的东西：第一，创业的念头；第二，机理论。能孵化出这两样东西跟我的哲学基础有莫大的关系；在哲学的基础上，学习的内容产生了创业的念头，学习的方式产生了机理论。前者源于博闻，后者源于对解题（学习和工作）的哲学拷问——以动态规划的学习策略构建出一种学习和设计的理论框架，说白了其实就是学习和设计的理论。 创业的念头 在城市年青人的眼里，创业是个时髦的词。记得第一次思及这个时髦与自己的关系是在五年前的一篇网络日记里。摘当时原话：“目前专注于自我的投资，不排除日后创业，但希望果子结在成熟时”。这个“成熟时”的涵义很泛。在此以后的五年里，我没有感觉到“成熟时”的到来，直到今年深秋。当然此“成熟时”是否彼“成熟时”还有待验证，因为目前只产生的思想的果子。当这个思想上的成熟时出现时，我感叹，创业原来就是这么一回事。所以与其说我有了创业的想法，不如说对创业有了更多的认识。其实一般人的理解创业是狭义的——创办一家企业，生产一种产品丢向市场；广义的创业是指，用你的能力和爱好在能承担的风险范围内做有价值的事情。当这个事情涉及到一种有商业价值的产品的时候，它便是狭义的创业。 机理论 一直以来，我除喜欢捣鼓计算机外，了解人性心理是我的一大业余爱好。过去囿于交际圈小，只能通过一些理论图书和有限的阅人经验来了解人性。近两年来情感类电视节目非常火热，当别人在手指缝饭席间道德审判着这些形形式式的男女的时候，我却不知不觉拿他们当小白鼠了。和很多研究实验室项目类似，当个案积累到一定程度后，研究者面临同一个问题，归类。我也一样。一开始我希望从既有心理学研究里借鉴经验，可是，我未能从的实践派甚至理论派找到一个称心的分类方法。很幸运的是，得益于过去的逻辑学基础，约十一月中旬，我终究在多次山上的头脑风暴中总结出了非常满意的性格分类法，并且从此掌握了科学分类法。 科学分类法 这一出还没完，没想到科学分类法成了一条引火索，点燃了这两年来我思及有关系统论、认识论和方法论的思考碎片。将系统论（系统做功、体运算和元运算）进化为更完整和成熟的机理论（解题、学习和设计）。用认知心理学的理论解释，分类法成为关键的新念，引起过往有关于系统论的认知格局全面的调节。相对同化新念，认知格局的调节是里程碑式的。 机理论（解题、学习和设计） 关于机理论是什么，它形成的关键和意义又是什么，第一，机理论简单分为机结构和机活动两个部分，目前对机活动——机解题（问题、能力及解题过程）总结的较多；第二，逻辑学强化概念作为思考单元（研究对象）的重要性的认识，尤其是像“性格”这样的抽象概念；这一认识促成了科学分类法；第三，机理论的一大意义是将学习过程与设计过程分开独立定性，进一步清晰化了解题的过程，促使艺术进一步的技术化。 生活 汲取了过去的教训，今年的生活在继续学业的前提下，开始注重身体，基本不熬夜了，生活作息也调回来了许多，也坚持每天锻练，可是体质改善效果不明显，整体精神不佳。今年最大的精神不舒服，除了身体忽冷忽热导致的外，一种理想主义者的耻感让我很不舒服。我的学业能度过今年基本上靠借钱。在多次内心惶惶地拿起手机拔响十年甚至二十年都没有联系过的旧同学的号码后，我才感觉到我的理想主义是多么的无耻。为了消淡这种耻感，我必须过一种简朴的生活。花生米、鸡蛋和豆制品是我的主食，一周吃一次肉；前半年的篮球，后半年的上山跑步。这样大半年下来，我的精神没有好转，也没转差，体重没有减，也没有增。说明人的体质与整体生活方式有关，单一的改口或修作息效果有限。 孤独者的幸福 有人从味雷的刺激和鲜感满足中寻得快乐，有人从爱人亲人的需要和被需要的安全感中寻得幸福，有人从各种帐户上的数字跳变寻得尊荣。作为一个孤独者，我有我的快乐幸福和尊荣。我的幸福建立在自我成长上。同是孤独者，台湾作家李敖先生在一次电视访问中被多次问到生活最快乐的事情，他只回答两个字，性交。当时想不愧是李大湿，这么赤裸。我认同性满足也挺快乐，但我觉得知识增长所带来的精神满足胜于做爱，优于撸管；因为前者必须有个对象，条件可遇可买但不可强求；后者，持续短暂，操之过急还会发炎。知识增长所带来的快乐独立、持久、稳定且健康。今年有三次比较明显的快乐：计算理论深入（通用图灵机＋程序）、科学分类法和机理论。 刻板思维 一个人在思想上的不成熟多体现为自以为是，哲学术语叫主观。过去一直以非传统和激进自由主义者自居，特看不惯为数众多的传统保守派人士，总认为他们的生活有太多的条条框框，总自觉比他们成熟客观现代。经这两年的生活体验，我发现原来思维刻板不是保守派人士专有的，成熟也不取决于思维的先进性。 作为一个典型激进完美主义者的思维，刻板思维种种有：我来深圳发展一般都是住在关内的，住在关外还算来大城市么？结果当我搬到关外生活了一段时间后发现，关外诱惑少压力低，更轻松更适合自己；又如07年的那次搬家，头一次准备自己开锅做饭，在已经有了一个烧水电磁炉的前提下，非得再买一个炒菜用的电磁炉和炒锅。过了段日子，当这个炒炉被小强强奸的不行后，唷，烧水炉也可以炒呀；再过了段日子，当炒锅被我强奸的不行的时候，唷汤锅也可以用来炒呀；再过了段日子，烧水炉也支持不住了，唷，电饭煲也可以用来炒菜！ 再如关于素食荤吃。生活在城市的人大部分我想都是无肉不欢的，我对吃不讲究，但也去到了无肉不惯。孤立以后为了省钱，开始戒肉，但是还是生怕弱坏自己，也觉得不吃不自在，隔天还是要吃；后来钱包越来越瘪，然后逐渐发现两天三天一周不吃肉也死不了。 类似的刻板思维的突破的例子还有一些，经历这些经验后，我感叹原来一条生活选题可以多个选项的，而固执没有保守与激进之分，只有合适与否，客观与否之分。 沉重的转身 有了关于刻板思维的反省后，感性的我爆发了。因为一个最具争议的刻板思维例子，就是究竟这四年多的闭关生活是否有错。感性我指着理性吾的鼻子训斥道：看吧看吧，这是你的理性总结出来的结论！早说你不该这样的固执偏激，你那些理想主义都是些不切实际！害我这些年这么辛苦！！理性吾沉默半刻，反驳道：你懂个屁！我认同成熟取决于行为的适度性，你以为这种适度的成熟可以遗传呀？！我们来世就有劫数，需要历练才能解除，历练需要代价！ 谈到成长的代价，让我想起了最近看的一套记录片《沉重的转身》，内容主要反省中国近代制度变革造成传统文化丢失，和重拾被革命派呐喊呼声所淹没的保守派的合理性。从这套片子，我看到了一个国家在变革过程中的艰辛与阵痛，也联想起个人在这些年转身过程的中阵痛。我开始认识到过于保守，不变会痛；过于激进，变化太快也痛。一个人真正的智慧和成熟是其行为的适度性，并且能客观认清自己的局限的同时接受对立立场的合理性。这么长时间，我是有点痛怕了，可是我也必须承认，我又不是圣人，怎么可能一开始就有能力把握保守与激进发展之间的度呢。就如记录片中的五位保皇派人士，一代大儒学贯中西都未能掌握治国合适的度，上演了五场悲剧。 天堂地狱人间 一开头我说让小盆友构想是否想要下来地狱玩一玩，并不全是在开玩笑。 在豆瓣网上我看到不少小盆友有出国梦，以为那里是天堂。在我看来，天堂即人间，人间即地狱，地狱亦天堂。为什么这么说？因为天堂地狱人间都不在外边，天堂在我们心中，地狱在我们面前，人间在我们脚下。正如理性吾所说，我们生来之初不可能传承适度的成熟。面对这个复杂多变的外界，无知的本我、固执的自我和阴暗的超我是我们生来的劫数。“三我”可简单理解为，“本我”天然要我们吃，“自我”自然要求我们乐，“超我”势然要求我们戒。 我们一般是从天堂诞生的，但是三我会在不同的时期向我们施压，如果我们不做足准备，随时会失足堕入人间，甚至奔向地狱。因此，处境如何只取决你是否有足够强大的内心力量抵抗三我。 三我劫数是命，面对它并不在于你是否选择，只在于你是否先知先觉而主动选择，还是后知后觉的被迫选择。 对三我劫数早有认识，我主动选择解除它。我非常了解自己的性格，以自己这种激进刚烈的性格，如果为了暂时应付一下三我的压力，找份普通的程序员工作，找个差不多的人结婚生子，那我可能在人间待上三五年，然后本我很可能爆发，奔向地狱。与其这样，不如我自己做自己的维吉尔[注]，一个人先入地狱耗一段日子，不要连累别人，待我有足够的能量抵抗三我时再回到人间，如果有幸遇到我的贝阿特丽切，再上天堂。 注：三界、维吉尔和贝阿特丽切原型自但丁《神曲》。 注：三我模型来自弗洛伊德《自我与本我》。 2012 2012期望很简单，希望及早回到人间。]]></description>
			<content:encoded><![CDATA[<p>2011谢幕。顺势而为，写下这篇年终报告。</p>
<p>去年的总结我对自己的前路作了个预测，非此即彼的，此为上班，彼为下狱。结果呢？下狱。这一年“地狱”生活概括起来，充实幸福但不快乐。看着自己每天在成长，我感觉生活是充实的；想到生活比我更不幸的同龄人，我是幸福的；但是，忧乐无籍，身体欠安等原因，我总是快乐不起来。另外，再加上物质资源的进一步的枯竭，我过着一种比10年更低质量的生活。这种低质量的“地狱”生活是怎么样的呢？受限于文笔力，接下来我尽量在适合的地方用客观的语言为没有游玩过地狱的小盆友介绍，好让你们某天也构想下来玩一玩。<span id="more-569"></span></p>
<h2>2011坚持渡己</h2>
<p>我的求学耕耘路并不是像去年总结中臆想的那样，能在2011年初收获一份小小的回报；各种客观事实证明，我存在某种乏缺，说不清是什么，姑且叫成熟度吧。这种乏缺注定了2011只是2010的一个延续，未能成熟到收获一份硕果。于是略去过程，2011表面结果与2010一模一样——单身、待业和生活窘迫。而生活过程就如我的生活质量下降一级一样，更加的孤立和封闭。基本上像庄周所描述的独与天地精神往来，自己关起门来，读书思考写字，没有世俗社交，最多在豆瓣网上逗俩小盆友玩玩，安慰那孤单寂寞的小我。</p>
<p>面对这种年复年的非人生活，我和别人开玩笑说，我是个苦行修练的XX居士呢；别人反问，那你怎么那么好色？于是我说我是个看破红尘的现世隐士，别人问，那你怎么住在大城市？这种刻板拷问让我想起了济公被斥为僧不应酒肉穿肠。济公的解释是，他人修口不修心，为我修心不修口。渡人渡己的核心是内心，生活方式我认为不重要。我更确切的身份是一名理想志士，我在为自己的理想奋斗。我的理想是什么？我要过我爱过的生活，我不愿意与现实全面妥协。</p>
<p>不知不觉，这种孤立的求学奋斗生活已经进入第五个年头。即便我的命运如此不顺，我的精神依然是很自信的，甚至为自己的成长感到自傲。但我不会把自己的自信与命运不济的矛盾归结为所谓的怀才不遇。怀才不遇只是庸才的自恋的安慰，是一种不切实际的抱怨。因为怀才与不遇在逻辑上是不通的，是个悖论。不遇的更大可能是无才，至少有所乏缺。我必须能客观认识自己，不然活不到今天。</p>
<h2>学业</h2>
<p>今年的学业可谓一波三折。先是年初设备驱动转C通用，接着觉得C通用偏软转向8051（单击<a href="http://arttech.us/y-2011/2011-2-3-4.html" target="_blank">这里</a>），接着发现8051门槛过低应用领域过窄转回设备驱动，然后又觉得光有设备驱动的经验，竞争力不够，补学内核移植……最后到年底12月忽然发觉有了创业的想法，然后发现C通用是最具投入价值而回归C通用（看<a href="http://www.douban.com/note/188384469/" target="_blank">这里</a>），现在正在学习算法和数据结构，并在写一些实用程序。</p>
<p>用了一年的时间，从C通用又回到C通用，这不是瞎折腾么？我承认折腾是有，但没有瞎；因为我现在回想起来，确实有走了弯路，但这些弯路没有白走，只是这些投入未能在短期内获得直接回报。例如如果接下来我的工作主要以C应用编程为主，那么设备驱动的经验和知识将被搁置，但是对操作系统的研究不可能对C编程没有影响；又如，如果没有研究8051，我不可能对汇编语言有深入的认识，从而也影响我更深入的认识C语言。</p>
<h2>非专业</h2>
<p>这一年折腾生活除了夯实了我的编程基础能力外，还孵化出了两个可能改变我一生的东西：第一，创业的念头；第二，机理论。能孵化出这两样东西跟我的哲学基础有莫大的关系；在哲学的基础上，学习的内容产生了创业的念头，学习的方式产生了机理论。前者源于博闻，后者源于对解题（学习和工作）的哲学拷问——以动态规划的学习策略构建出一种学习和设计的理论框架，说白了其实就是学习和设计的理论。</p>
<h3>创业的念头</h3>
<p>在城市年青人的眼里，创业是个时髦的词。记得第一次思及这个时髦与自己的关系是在五年前的一篇网络日记里。摘当时原话：“目前专注于自我的投资，不排除日后创业，但希望果子结在成熟时”。这个“成熟时”的涵义很泛。在此以后的五年里，我没有感觉到“成熟时”的到来，直到今年深秋。当然此“成熟时”是否彼“成熟时”还有待验证，因为目前只产生的思想的果子。当这个思想上的成熟时出现时，我感叹，创业原来就是这么一回事。所以与其说我有了创业的想法，不如说对创业有了更多的认识。其实一般人的理解创业是狭义的——创办一家企业，生产一种产品丢向市场；广义的创业是指，用你的能力和爱好在能承担的风险范围内做有价值的事情。当这个事情涉及到一种有商业价值的产品的时候，它便是狭义的创业。</p>
<h3>机理论</h3>
<p>一直以来，我除喜欢捣鼓计算机外，了解人性心理是我的一大业余爱好。过去囿于交际圈小，只能通过一些理论图书和有限的阅人经验来了解人性。近两年来情感类电视节目非常火热，当别人在手指缝饭席间道德审判着这些形形式式的男女的时候，我却不知不觉拿他们当小白鼠了。和很多研究实验室项目类似，当个案积累到一定程度后，研究者面临同一个问题，归类。我也一样。一开始我希望从既有心理学研究里借鉴经验，可是，我未能从的实践派甚至理论派找到一个称心的分类方法。很幸运的是，得益于过去的逻辑学基础，约十一月中旬，我终究在多次山上的头脑风暴中总结出了非常满意的性格分类法，并且从此掌握了科学分类法。</p>
<p><a href="http://arttech.us/wp-content/uploads/2012/01/%E7%A7%91%E5%AD%A6%E5%88%86%E7%B1%BB%E6%B3%95.png"><img class="aligncenter size-full wp-image-577" title="科学分类法" src="http://arttech.us/wp-content/uploads/2012/01/%E7%A7%91%E5%AD%A6%E5%88%86%E7%B1%BB%E6%B3%95.png" alt="" width="683" height="406" /></a></p>
<p style="text-align: center;">科学分类法</p>
<p>这一出还没完，没想到科学分类法成了一条引火索，点燃了这两年来我思及有关系统论、认识论和方法论的思考碎片。将系统论（系统做功、体运算和元运算）进化为更完整和成熟的机理论（解题、学习和设计）。用认知心理学的理论解释，分类法成为关键的新念，引起过往有关于系统论的认知格局全面的调节。相对同化新念，认知格局的调节是里程碑式的。</p>
<p><a href="http://arttech.us/wp-content/uploads/2012/01/%E8%A7%A3%E9%A2%98rev2.png"><img class="aligncenter size-full wp-image-578" title="解题rev#2" src="http://arttech.us/wp-content/uploads/2012/01/%E8%A7%A3%E9%A2%98rev2.png" alt="" width="682" height="448" /></a></p>
<p style="text-align: center;">机理论（解题、学习和设计）</p>
<p>关于机理论是什么，它形成的关键和意义又是什么，第一，机理论简单分为机结构和机活动两个部分，目前对机活动——机解题（问题、能力及解题过程）总结的较多；第二，逻辑学强化概念作为思考单元（研究对象）的重要性的认识，尤其是像“性格”这样的抽象概念；这一认识促成了科学分类法；第三，机理论的一大意义是将学习过程与设计过程分开独立定性，进一步清晰化了解题的过程，促使艺术进一步的技术化。</p>
<h2>生活</h2>
<p>汲取了过去的教训，今年的生活在继续学业的前提下，开始注重身体，基本不熬夜了，生活作息也调回来了许多，也坚持每天锻练，可是体质改善效果不明显，整体精神不佳。今年最大的精神不舒服，除了身体忽冷忽热导致的外，一种理想主义者的耻感让我很不舒服。我的学业能度过今年基本上靠借钱。在多次内心惶惶地拿起手机拔响十年甚至二十年都没有联系过的旧同学的号码后，我才感觉到我的理想主义是多么的无耻。为了消淡这种耻感，我必须过一种简朴的生活。花生米、鸡蛋和豆制品是我的主食，一周吃一次肉；前半年的篮球，后半年的上山跑步。这样大半年下来，我的精神没有好转，也没转差，体重没有减，也没有增。说明人的体质与整体生活方式有关，单一的改口或修作息效果有限。</p>
<h3>孤独者的幸福</h3>
<p>有人从味雷的刺激和鲜感满足中寻得快乐，有人从爱人亲人的需要和被需要的安全感中寻得幸福，有人从各种帐户上的数字跳变寻得尊荣。作为一个孤独者，我有我的快乐幸福和尊荣。我的幸福建立在自我成长上。同是孤独者，台湾作家李敖先生在一次电视访问中被多次问到生活最快乐的事情，他只回答两个字，性交。当时想不愧是李大湿，这么赤裸。我认同性满足也挺快乐，但我觉得知识增长所带来的精神满足胜于做爱，优于撸管；因为前者必须有个对象，条件可遇可买但不可强求；后者，持续短暂，操之过急还会发炎。知识增长所带来的快乐独立、持久、稳定且健康。今年有三次比较明显的快乐：计算理论深入（通用图灵机＋程序）、科学分类法和机理论。</p>
<h3>刻板思维</h3>
<p>一个人在思想上的不成熟多体现为自以为是，哲学术语叫主观。过去一直以非传统和激进自由主义者自居，特看不惯为数众多的传统保守派人士，总认为他们的生活有太多的条条框框，总自觉比他们成熟客观现代。经这两年的生活体验，我发现原来思维刻板不是保守派人士专有的，成熟也不取决于思维的先进性。</p>
<p>作为一个典型激进完美主义者的思维，刻板思维种种有：我来深圳发展一般都是住在关内的，住在关外还算来大城市么？结果当我搬到关外生活了一段时间后发现，关外诱惑少压力低，更轻松更适合自己；又如07年的那次搬家，头一次准备自己开锅做饭，在已经有了一个烧水电磁炉的前提下，非得再买一个炒菜用的电磁炉和炒锅。过了段日子，当这个炒炉被小强强奸的不行后，唷，烧水炉也可以炒呀；再过了段日子，当炒锅被我强奸的不行的时候，唷汤锅也可以用来炒呀；再过了段日子，烧水炉也支持不住了，唷，电饭煲也可以用来炒菜！</p>
<p>再如关于素食荤吃。生活在城市的人大部分我想都是无肉不欢的，我对吃不讲究，但也去到了无肉不惯。孤立以后为了省钱，开始戒肉，但是还是生怕弱坏自己，也觉得不吃不自在，隔天还是要吃；后来钱包越来越瘪，然后逐渐发现两天三天一周不吃肉也死不了。</p>
<p>类似的刻板思维的突破的例子还有一些，经历这些经验后，我感叹原来一条生活选题可以多个选项的，而固执没有保守与激进之分，只有合适与否，客观与否之分。</p>
<h3>沉重的转身</h3>
<p>有了关于刻板思维的反省后，感性的我爆发了。因为一个最具争议的刻板思维例子，就是究竟这四年多的闭关生活是否有错。感性我指着理性吾的鼻子训斥道：看吧看吧，这是你的理性总结出来的结论！早说你不该这样的固执偏激，你那些理想主义都是些不切实际！害我这些年这么辛苦！！理性吾沉默半刻，反驳道：你懂个屁！我认同成熟取决于行为的适度性，你以为这种适度的成熟可以遗传呀？！我们来世就有劫数，需要历练才能解除，历练需要代价！</p>
<p>谈到成长的代价，让我想起了最近看的一套记录片《沉重的转身》，内容主要反省中国近代制度变革造成传统文化丢失，和重拾被革命派呐喊呼声所淹没的保守派的合理性。从这套片子，我看到了一个国家在变革过程中的艰辛与阵痛，也联想起个人在这些年转身过程的中阵痛。我开始认识到过于保守，不变会痛；过于激进，变化太快也痛。一个人真正的智慧和成熟是其行为的适度性，并且能客观认清自己的局限的同时接受对立立场的合理性。这么长时间，我是有点痛怕了，可是我也必须承认，我又不是圣人，怎么可能一开始就有能力把握保守与激进发展之间的度呢。就如记录片中的五位保皇派人士，一代大儒学贯中西都未能掌握治国合适的度，上演了五场悲剧。</p>
<h3>天堂地狱人间</h3>
<p>一开头我说让小盆友构想是否想要下来地狱玩一玩，并不全是在开玩笑。</p>
<p>在豆瓣网上我看到不少小盆友有出国梦，以为那里是天堂。在我看来，天堂即人间，人间即地狱，地狱亦天堂。为什么这么说？因为天堂地狱人间都不在外边，天堂在我们心中，地狱在我们面前，人间在我们脚下。正如理性吾所说，我们生来之初不可能传承适度的成熟。面对这个复杂多变的外界，无知的本我、固执的自我和阴暗的超我是我们生来的劫数。“三我”可简单理解为，“本我”天然要我们吃，“自我”自然要求我们乐，“超我”势然要求我们戒。</p>
<p>我们一般是从天堂诞生的，但是三我会在不同的时期向我们施压，如果我们不做足准备，随时会失足堕入人间，甚至奔向地狱。因此，处境如何只取决你是否有足够强大的内心力量抵抗三我。</p>
<p>三我劫数是命，面对它并不在于你是否选择，只在于你是否先知先觉而主动选择，还是后知后觉的被迫选择。</p>
<p>对三我劫数早有认识，我主动选择解除它。我非常了解自己的性格，以自己这种激进刚烈的性格，如果为了暂时应付一下三我的压力，找份普通的程序员工作，找个差不多的人结婚生子，那我可能在人间待上三五年，然后本我很可能爆发，奔向地狱。与其这样，不如我自己做自己的维吉尔[注]，一个人先入地狱耗一段日子，不要连累别人，待我有足够的能量抵抗三我时再回到人间，如果有幸遇到我的贝阿特丽切，再上天堂。</p>
<p>注：三界、维吉尔和贝阿特丽切原型自但丁《神曲》。<br />
注：三我模型来自弗洛伊德《自我与本我》。</p>
<h2>2012</h2>
<p>2012期望很简单，希望及早回到人间。</p>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2012/2011-report.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>存储管理机</title>
		<link>http://arttech.us/y-2011/memory-manager.html</link>
		<comments>http://arttech.us/y-2011/memory-manager.html#comments</comments>
		<pubDate>Mon, 07 Nov 2011 00:28:14 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[操作系统]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[内存管理]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=558</guid>
		<description><![CDATA[1.存储管理机 在单任务计算机系统里，主存被划分为两块：一块装操作系统（内核），一块装应用程序。系统主存被单一的应用进程独占，没有存储管理的需求。但在多任务计算机系统上，装应用程序的那块主存则必须进一步划分数块，一块装一个应用程序。操作系统负责动态划分主存，分配主存给应用程序和回收应用程序释放的主存，这个任务叫存储管理（memory management）。负责管理存储器的操作系统子系统，我们称之存储管理子系统，统称［存储管理机］。 ［存储管理机］的管理效率关系到整个多任务系统的性能。例如，如果系统主存只能装少量几个应用程序，而当主存中全是需频繁读写慢速IO设备的应用程序时，CPU频繁被闲置的同时，可能有其它就绪的应用程序因为没有被装入主存而被闲置，CPU资源没有被得到高效利用。 2.存储管理需求 无论计划设计或正在设计一项产品，我们务必时刻紧记产品所满足的需求（requirements），根据需求来考虑更好的设计策略。现代多任务计算机系统应用对操作系统的［存储管理机］设计提出以下一些需求： 进程移动（Relocation） 进程隔离保护（Protection） 数据共享（Sharing） 进程模块化（Logical organization） 虚拟存储 这些需求深深的影响着从硬件（CPU）到软件（OS、compiler suit）的体系结构设计。 2.1 进程移动 在多任务计算机里，多个进程共享系统可用的主存。通常情况下，程序员在开发程序的时候不可能对程序实际运行在主存时的情况作任何假设，例如程序被具体装入到主存哪里，主存其它位置有什么进程等； 另外，为了最大化CPU的利用率，系统需要利用辅存（主要是磁盘）开辟一块的进程池（pool of ready processes），将阻塞的进程调出主存，将就绪的进程调入主存。当进程被调出主存后，我们很难知道它何时被再次调入，因为它必须被调入回原来的主存位置。然而这种静态置换是低效的。 综合以上两个需求，我们需要某种移动进程（或者叫重定位relocation）的技术，可以将进程移动到主存任意位置。这让我的关注点转移到进程对象运行时的存储结构上来，如下图： 此图展示了一支进程的存储结构。为了简化描述，我们假设进程被装入一块连续的主存区。由存储结构可知，操作系统必须知道进程地址有： 第一，进程控制块的地址； 第二，进程调用栈的地址； 第三，进程的程序入口地址。 由于操作系统是负责将进程装入主存的，因此这些进程地址很容易得到。进程除了以上的结构性地址，程序内部的指令序列还有很多内存引用地址。例如，分支指令（语句）包含了下一条指令的地址；数据引用指令包含了数据字节（或字）的地址。 由上可推得，为了满足进程地址重定位的最直观方案就是，以进程为单位移动，进程内统一使用某种逻辑地址（例如相对地址），逻辑地址在执行时再由CPU硬件和操作系统转为物理地址。 2.2 虚拟存储 以进程为单位移动和管理存储，所得效率还是有限的。如果能将移动的单位深入到进程内部，将不常用的部分暂时移出主存，存储管理效率将会更高。这就向存储管理提出更高级的管理需求——进程内部存储管理。 在多任务技术发展的初期，程序员们曾经开发一种【前内存管理系统】——覆盖系统（overlay system）来实现内部存储管理的问题。因为那个时候操作系统还没有出现，程序员通过显式编码来实现overlay system。程序员把程序划分为不同的段（sections），每个段都会被整个载入主存执行一段时间。随着程序的运行推进，新的程序段会被载入，替换已经不需要的程序段。 内部存储管理的任务原是程序开发者的职责，但出于以下两个原因，管理任务还是收归给系统： 第一，开发者对程序的行为是了解的，但是存储管理毕竟不是解题相关的任务，由开发者实现存储管理，第一，分裂的开发者的角色，第二，增加了开发者的劳动； 第二，某些程序动态性过强，开发者不容易预测。 现代计算机系统统统实现了虚拟存储技术来满足进程内部存储管理需求。虚拟存储技术的核心是［虚拟存储器］的概念。虚拟存储器不是主存，也不是外存，而是操作系统和CPU利用二者实现的抽象的存储器，它的功能与主存一样，但是比主存要慢。虚拟存储技术假想将进程载入虚拟存器上运行，主存是虚拟存器的缓存，进程的常用代码和数据缓存在主存上。 要为进程虚构一个虚拟存储器，并且以主存为缓存，那么存储管理机必须负责管理缓存的［命中］和［不命中］处理，前者就是分页技术中用页表来实现虚实地址转换，后者就是分页技术中缺页处理。 2.3 进程隔离保护 在多任务的环境，每支进程都必须彼此隔离独立。为了避免进程间产生有意或无意相互干扰——某一进程在没有得到允许的情况下读写其它进程所分配的主存空间，操作系统有责任窃查进程行为。由于进程行为都表现对主存的访问，因此进程行为的检查归为存储管理，而不是进程管理。 从某种意义上说，［进程移动］与［进程保护］是对立的，进程重定位实现增加了进程保护的难度。由于［进程重定位］，进程在主存中的位置变得不可预知，不可能在编译时检查绝对地址来实现保护。此外，很多高级语言支持在运行时动态计算来获得数据对象的地址，例如，C语言，增加了进程保护的难度。 进程保护的可行办法是对进程运行时产生的所有地址进行检查，确保它们只访问属于自己的地址空间。可是，操作系统无法对进程的主存引用操作作全面的检查，光有操作系统（软件）是不够的，储存管理要实现［进程保护］，必须得到CPU（硬件）支持。 幸运的是，现代处理器实现了强大的地址硬件，［进程移动］与［进程保护］同时得到实现。 2.4 数据共享 如果进程的隔离保护设计足够灵活，那么进程保护可以现实满足更高级的存储管理需求——数据共享。 在多任务的环境下，多个进程共享数据的例子有：第一，同一程序的多个进程实例可共享一份只读的代码；第二，两支协作进程可共享一份数据来实现通信协作；第三，基于同一个代码库，如标准C库，编写的多个进程可共享一份库代码，大大节省存储空间。 2.5 进程模块化 无论从概念上还是物理实现上，计算机主存都是线性的，无结构的。但是程序是有一定结构的，如一般程序有只读的代码段和可读写的数据段；进程运行时有一部分代码是常用的，有些则很少使用等。如果存储管理能以程序模块为位进行管理，而是不整个程序，那么以下的一些需求将得到满足： 第一，程序模块可是单独编写和编译，并且可以在运行时动态链接和加载； 第二，进程保护可以以模块为单位； 第三，进程共享可以以模块为单位。 [...]]]></description>
			<content:encoded><![CDATA[<h2>1.存储管理机</h2>
<p>在单任务计算机系统里，主存被划分为两块：一块装操作系统（内核），一块装应用程序。系统主存被单一的应用进程独占，没有存储管理的需求。但在多任务计算机系统上，装应用程序的那块主存则必须进一步划分数块，一块装一个应用程序。操作系统负责动态划分主存，分配主存给应用程序和回收应用程序释放的主存，这个任务叫存储管理（memory management）。负责管理存储器的操作系统子系统，我们称之存储管理子系统，统称［存储管理机］。</p>
<p>［存储管理机］的管理效率关系到整个多任务系统的性能。例如，如果系统主存只能装少量几个应用程序，而当主存中全是需频繁读写慢速IO设备的应用程序时，CPU频繁被闲置的同时，可能有其它就绪的应用程序因为没有被装入主存而被闲置，CPU资源没有被得到高效利用。<span id="more-558"></span></p>
<h2>2.存储管理需求</h2>
<p>无论计划设计或正在设计一项产品，我们务必时刻紧记产品所满足的需求（requirements），根据需求来考虑更好的设计策略。现代多任务计算机系统应用对操作系统的［存储管理机］设计提出以下一些需求：</p>
<ol>
<li>进程移动（Relocation）</li>
<li>进程隔离保护（Protection）</li>
<li>数据共享（Sharing）</li>
<li>进程模块化（Logical organization）</li>
<li>虚拟存储</li>
</ol>
<p>这些需求深深的影响着从硬件（CPU）到软件（OS、compiler suit）的体系结构设计。</p>
<h3>2.1 进程移动</h3>
<p>在多任务计算机里，多个进程共享系统可用的主存。通常情况下，程序员在开发程序的时候不可能对程序实际运行在主存时的情况作任何假设，例如程序被具体装入到主存哪里，主存其它位置有什么进程等；</p>
<p>另外，为了最大化CPU的利用率，系统需要利用辅存（主要是磁盘）开辟一块的进程池（pool of ready processes），将阻塞的进程调出主存，将就绪的进程调入主存。当进程被调出主存后，我们很难知道它何时被再次调入，因为它必须被调入回原来的主存位置。然而这种静态置换是低效的。</p>
<p>综合以上两个需求，我们需要某种移动进程（或者叫重定位relocation）的技术，可以将进程移动到主存任意位置。这让我的关注点转移到进程对象运行时的存储结构上来，如下图：</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/11/proc_structure.jpg"><img class="aligncenter size-full wp-image-559" title="proc_structure" src="http://arttech.us/wp-content/uploads/2011/11/proc_structure.jpg" alt="" width="439" height="383" /></a></p>
<p>此图展示了一支进程的存储结构。为了简化描述，我们假设进程被装入一块连续的主存区。由存储结构可知，操作系统必须知道进程地址有：</p>
<ul>
<li>第一，进程控制块的地址；</li>
<li>第二，进程调用栈的地址；</li>
<li>第三，进程的程序入口地址。</li>
</ul>
<p>由于操作系统是负责将进程装入主存的，因此这些进程地址很容易得到。进程除了以上的结构性地址，程序内部的指令序列还有很多内存引用地址。例如，分支指令（语句）包含了下一条指令的地址；数据引用指令包含了数据字节（或字）的地址。</p>
<p>由上可推得，为了满足进程地址重定位的最直观方案就是，以进程为单位移动，进程内统一使用某种逻辑地址（例如相对地址），逻辑地址在执行时再由CPU硬件和操作系统转为物理地址。</p>
<h3>2.2 虚拟存储</h3>
<p>以进程为单位移动和管理存储，所得效率还是有限的。如果能将移动的单位深入到进程内部，将不常用的部分暂时移出主存，存储管理效率将会更高。这就向存储管理提出更高级的管理需求——进程内部存储管理。</p>
<p>在多任务技术发展的初期，程序员们曾经开发一种【前内存管理系统】——覆盖系统（overlay system）来实现内部存储管理的问题。因为那个时候操作系统还没有出现，程序员通过显式编码来实现overlay system。程序员把程序划分为不同的段（sections），每个段都会被整个载入主存执行一段时间。随着程序的运行推进，新的程序段会被载入，替换已经不需要的程序段。</p>
<p>内部存储管理的任务原是程序开发者的职责，但出于以下两个原因，管理任务还是收归给系统：</p>
<ul>
<li>第一，开发者对程序的行为是了解的，但是存储管理毕竟不是解题相关的任务，由开发者实现存储管理，第一，分裂的开发者的角色，第二，增加了开发者的劳动；</li>
<li>第二，某些程序动态性过强，开发者不容易预测。</li>
</ul>
<p>现代计算机系统统统实现了虚拟存储技术来满足进程内部存储管理需求。虚拟存储技术的核心是［虚拟存储器］的概念。虚拟存储器不是主存，也不是外存，而是操作系统和CPU利用二者实现的抽象的存储器，它的功能与主存一样，但是比主存要慢。虚拟存储技术假想将进程载入虚拟存器上运行，主存是虚拟存器的缓存，进程的常用代码和数据缓存在主存上。</p>
<p>要为进程虚构一个虚拟存储器，并且以主存为缓存，那么存储管理机必须负责管理缓存的［命中］和［不命中］处理，前者就是分页技术中用页表来实现虚实地址转换，后者就是分页技术中缺页处理。</p>
<h3>2.3 进程隔离保护</h3>
<p>在多任务的环境，每支进程都必须彼此隔离独立。为了避免进程间产生有意或无意相互干扰——某一进程在没有得到允许的情况下读写其它进程所分配的主存空间，操作系统有责任窃查进程行为。由于进程行为都表现对主存的访问，因此进程行为的检查归为存储管理，而不是进程管理。</p>
<p>从某种意义上说，［进程移动］与［进程保护］是对立的，进程重定位实现增加了进程保护的难度。由于［进程重定位］，进程在主存中的位置变得不可预知，不可能在编译时检查绝对地址来实现保护。此外，很多高级语言支持在运行时动态计算来获得数据对象的地址，例如，C语言，增加了进程保护的难度。</p>
<p>进程保护的可行办法是对进程运行时产生的所有地址进行检查，确保它们只访问属于自己的地址空间。可是，操作系统无法对进程的主存引用操作作全面的检查，光有操作系统（软件）是不够的，储存管理要实现［进程保护］，必须得到CPU（硬件）支持。</p>
<p>幸运的是，现代处理器实现了强大的地址硬件，［进程移动］与［进程保护］同时得到实现。</p>
<h3>2.4 数据共享</h3>
<p>如果进程的隔离保护设计足够灵活，那么进程保护可以现实满足更高级的存储管理需求——数据共享。</p>
<p>在多任务的环境下，多个进程共享数据的例子有：第一，同一程序的多个进程实例可共享一份只读的代码；第二，两支协作进程可共享一份数据来实现通信协作；第三，基于同一个代码库，如标准C库，编写的多个进程可共享一份库代码，大大节省存储空间。</p>
<h3>2.5 进程模块化</h3>
<p>无论从概念上还是物理实现上，计算机主存都是线性的，无结构的。但是程序是有一定结构的，如一般程序有只读的代码段和可读写的数据段；进程运行时有一部分代码是常用的，有些则很少使用等。如果存储管理能以程序模块为位进行管理，而是不整个程序，那么以下的一些需求将得到满足：</p>
<ul>
<li>第一，程序模块可是单独编写和编译，并且可以在运行时动态链接和加载；</li>
<li>第二，进程保护可以以模块为单位；</li>
<li>第三，进程共享可以以模块为单位。</li>
</ul>
<h2>3.存储管理机设计</h2>
<h3>3.1 主存框</h3>
<p>存储管理机管理的［资源］是主存，存储管理设计的第一任务是划分主存作管理的单位（memory allocation unit），这里统称其为主存框（memory frame）；其次就是管理操作——为装入主存运行的进程分配主存区的分配算法。</p>
<p>划分主存的方式决定了存储管理机的类型，各种类型的优点与缺点都不同，适用于不的计算机应用系统。固定实存分配不适于通用PC，但非常适合用于嵌入式单机系统，因为它调度算法简单高效。</p>
<h3>3.2 分配算法</h3>
<p>将进程装入主存，装入分两种情况，第一，当主存有剩余［主存框］时，选择合适的［主存框］直接装入（Placement）；第二，当主存没有剩余［主存框］时，必须先解决将哪个［主存框］调出主存，再装入（Replacement）。</p>
<h2>4.存储管理机分类</h2>
<p>根据实现［进程移动］时的地址翻译的难度，存储管理机分为［一次翻译的实存管理］和［二次翻译的虚存管理］两大类。在大类内又可根据［主存框］的类型进一步细分。</p>
<p>根据进程分配粒度，实存管理分为静态和动态实存； 静态实存管理按进程为单位进行分配和调度，并且主存框的划分是固定的（起始地址固定）；根据主存框的［大小是否相等］，静态实存管理机有［固定块式实存管理机］和［不定块实存管理机］两个变种。</p>
<p>动态实存主要有页式实存管理机和段式实存管理机两类。页框是大小固定的，段框则大小不一。</p>
<p>实存管理现在已经很少使用，由于它的实现比较原始，更好展示存储管理原理，对理解虚存管理有帮助；</p>
<p>虚存管理为了实现更灵活的存储管理，需要二次地址翻译——一张中间数据表提供翻译信息，例如页表。虚存管理设计的［主存框］类型主要有页和段，相应的存储管理机分别称为［页式虚存管理机］和［段式虚存管理机］。</p>
<ul>
<li>实存管理机
<ul>
<li>静态实存管理机
<ul>
<li>固定块式实存管理机</li>
<li>不定块式实存管理机</li>
</ul>
</li>
<li>动态实存管理机
<ul>
<li>页式实存管理机</li>
<li>段式实存管理机</li>
</ul>
</li>
</ul>
</li>
<li>虚存管理机
<ul>
<li>页式虚存管理机</li>
<li>段式虚存管理机</li>
</ul>
</li>
</ul>
<h2>5.实存管理机</h2>
<p>［固定块式实存管理机］优点是，第一，因为［主存框］大小和位置都是固定的，所以Placement算法很简单；第二，主存容纳进程的数量是固定的，Replacement算法也比较简单；缺点是，如果进程太大（超过［主存框］大小）则必须手动管理——程序中有部分代码用来管理内存（如将部分代码或者调入调出），增加USER开发者劳动；如果太小，产生［内部碎片］——主存框没有装满，但也不能再被利用；［不定块式实存管理机］缓解（但不能解决）这两个不足。</p>
<p>［动态实存管理机］很好的解决了手动管理和内部碎片，但会产生［外部碎片］——主存没有装满，但剩余空间因不足再装一个新进程而不能再被利用。外部碎片必须重新回收——移动主存上的进程来合并碎片——才能再用，直接的结果，第一，增加系统开销；第二，增加开发技术难度（增加OS开发者劳动），因为进程必须能重定位才能移动。</p>
<p>由于［动态实存管理机］的［主存框］大小是不固定的，Placement算法相对复杂，因为如果有多块可用的［主存框］时，必须先决定装入哪块［主存框］，常用的选择算法有Best-fit 、First-fit 和Next-fit。</p>
<p>实存管理机基本实现了进程移动、进程保护和进程模块化的存储管理需求。例如，80&#215;86体系CPU实现的段寻址技术，操作系统在载入进程时，将其基地址配置入CPU的基地址寄存器（base register），CPU通过基地址寄存器和相对地址来产生物理地址。CPU提供的边界寄存器（bounds register）可用来实现对进程的越界保护，当产生的物理地址越出指定范围，CPU产生一中断事件给操作系统处理。</p>
<h2>6.页式虚存管理机</h2>
<p>实存管理技术（无论是静态还是动态的）对主存的利用率不高，原因是［一次地址翻译］使主存框的分配方式不够灵活，只能连续的分配。试想如果进程的主存框能灵活地分配在不连续的主存框上，主存的利用率一定能得到改善。但这种管理方案的基础和前提是实现分割进程的技术。这就是目前被普遍实现的［页式虚拟存储技术］。</p>
<p>页式虚拟存储技术的思想是将进程分割为大小相等的页（page），主存分割为同样大小的页框（page frame），操作系统和CPU负责管理管理实现页与页框的映射关系。分页技术使用更小的主存框，降低了内部碎片的浪费；另外，分页技术支持不连续分配页框，也降低了外部碎片的浪费。</p>
<h3>6.1 地址翻译原理</h3>
<p>为了实现页式虚拟存储技术的［进程分割］和［不连续分配页框］，程序的［逻辑地址］使用单一的［基地址寄存器加相对地址］进行一次转换的是不够的，存储管理机必须为每一支进程创建一张页表（page table）对程序的［逻辑地址］作二次转换。存储管理机将可用主存分割为同样大小页框，进程的页表保存着它所占用的页框的页框号。而页表的索引就是进程分割的页号（由0开始）。例如某进程D被分为五页，分别载入员框号为4，5， 6， 11和 12的页框内，如下图：</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/11/proc-page-table.gif"><img class="aligncenter size-full wp-image-560" title="proc-page-table" src="http://arttech.us/wp-content/uploads/2011/11/proc-page-table.gif" alt="" width="500" height="375" /></a></p>
<p>程序内的［相对地址］在分为两部分：页号和页内偏移。进程的物理地址通过用页号查得页框号，再由页框号和页内偏移合并得到。例如，假设系统使用16位地址，页大小为1K。那么一支2700字节大小的进程占用三页，如下图。当程序有相对地址的1502，那么如何算进程对应的物理地址呢？</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/11/va2pa01.jpg"><img class="aligncenter size-full wp-image-561" title="va2pa01" src="http://arttech.us/wp-content/uploads/2011/11/va2pa01.jpg" alt="" width="598" height="446" /></a></p>
<p>1502的二进制是0000010111011110，由于页大小为1K，所以低10位用作页内偏移，高6位用作页号。那么相对地址1502位于1号页 (000001)内的478 (0111011110) 处。如果程序载入时1号页被分配到6号页框（000110），那相对地址的1502的对应的物理地址是0001100111011110，如下图。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/11/va2pa02.jpg"><img class="aligncenter size-full wp-image-562" title="va2pa02" src="http://arttech.us/wp-content/uploads/2011/11/va2pa02.jpg" alt="" width="478" height="236" /></a></p>
<p>分页技术最巧妙的地方是，当页大小为2的乘幂（2^n）时，页号和页内偏移对程序员、编译程序和链接程序是透明的。另外，地址转换过程只是将高位页号替换为页框号，硬件可非常容易并且高效实现。</p>
<h3>6.2 多级页表</h3>
<p>原理上，一支进程对应一张页表，但当某支进程所使用的虚地址范围很大的时候，它的页表将大得难以接受。例如，在VAX architecture的系统里，每支进程占用 2^31 = 2 Gib的虚地址范围，按每页2^9 = 512-byte大小算，页表项高达2^22 条。为了解决这个问题，大多数虚存管理使用虚存本身——多级页表——来保存页表，而不是使用物理内存。当然［顶级页表］必须使用物理内存。使用虚存来保存页表意味着进程的部分页表可以不在物理内存上。</p>
<p>一些处理器使用两级页表，像Pentium processor。在这种设计里，［顶级页表］称为页目录（page directory），一条目录项指向一张二级页表；这样如果页目录长度为X，页表最大长度为Y，那么每支进程可占用 X * Y 页。另外，页表最大长度取决于页大小，例如页大小为4Kib，那么如果页项为4byte，页表最大长度为1024。</p>
<p>下图（上部）展示了典型的使用二级页表划分32位地址空间的例子。假设我们是按字节寻址，页大小为4Kib，那么4-Gib (2^32) 虚拟地址空间由2^20页组成。如果页项（page table entry or PTE）大小为4byte，那么页表占用 4 Mib (2^22)的主存空间。这张庞大的页表（称为尾页表）本身就可被另一张页表（称为根页表）使用2^10页进一步映入虚存中。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/11/two-level-pagetable.gif"><img class="aligncenter size-full wp-image-563" title="two-level-pagetable" src="http://arttech.us/wp-content/uploads/2011/11/two-level-pagetable.gif" alt="" width="495" height="557" /></a></p>
<p>上图（下部）展示了使用两级页表进行地址转换步骤。其中，进程的根页表是始终保存在主存上的，并由CPU根页表指针指向（具体如X86的cr3寄存器）；虚拟地址前10位是一级页号，用以索引根页表，查得保存尾页表的页框（的基地址）。如果该页不在主存中，产生缺页事件；如果在，虚拟地址接着的10位用作二级页号，用以索引尾页表，查得最终的页框（的基地址），最后将页框的基地址和12位的偏移地址合并得到物理地址。</p>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/memory-manager.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>编写Linux网卡设备驱动（下）</title>
		<link>http://arttech.us/y-2011/writing-network-device-driver-b.html</link>
		<comments>http://arttech.us/y-2011/writing-network-device-driver-b.html#comments</comments>
		<pubDate>Fri, 09 Sep 2011 15:06:07 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[计算机网络]]></category>
		<category><![CDATA[驱动程序]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=542</guid>
		<description><![CDATA[本文介绍基于Realtek 8139芯片PCI接口的网卡驱动程序。我选择了Realtek芯片有两个原因：首先，Realtek提供免费的芯片技术手册； 第二，芯片相当便宜。 本文介绍的驱动程序是最基本的，它只有发送和接收数据包功能，和做一些简单的统计。对于一个全面和专业级的驱动程序，请参阅Linux源码。 本文（下）的主要内容是在前一文（上）实现的驱动模板的基础上进一步实现网卡驱动的分组收发功能。除了实现发包接口和收包接口，原模板上的初始化接口、打开接口和私有数据都要改动。另外，网卡的硬件收发原理在《RTL8139的收发原理》（下述简称《收发原理》）已经阐述得很清楚，本文是对《收发原理》中的理念用代码具体实现。 目录 网络设备驱动程序的开发，分解成以下步骤： 上： 1.检测设备 2.启用设备 3.认识网络设备 4.总线无关的设备访问 5.理解PCI配置空间 6.初始化网络设备（net_device） 中： 7.RTL8139收发原理 下： 8.编写网络设备的发包功能 9.编写网络设备的收包功能 8.实现网络设备的发包功能 8.1 扩展rtl8139_private 在实现网络设备的打开和发送接口前，我必须先扩展我们的设备私有数据结构——rtl8139_private。 #define NUM_TX_DESC 4 struct rtl8139_private { struct pci_dev *pci_dev; /* PCI device */ void *mmio_addr; /* memory mapped I/O addr */ unsigned long regs_len; /* length of I/O or MMI/O region */ [...]]]></description>
			<content:encoded><![CDATA[<p>本文介绍基于Realtek 8139芯片PCI接口的网卡驱动程序。我选择了Realtek芯片有两个原因：首先，Realtek提供免费的芯片技术手册； 第二，芯片相当便宜。</p>
<p>本文介绍的驱动程序是最基本的，它只有发送和接收数据包功能，和做一些简单的统计。对于一个全面和专业级的驱动程序，请参阅Linux源码。</p>
<p>本文（下）的主要内容是在前一文（上）实现的驱动模板的基础上进一步实现网卡驱动的分组收发功能。除了实现发包接口和收包接口，原模板上的初始化接口、打开接口和私有数据都要改动。另外，网卡的硬件收发原理在《RTL8139的收发原理》（下述简称《收发原理》）已经阐述得很清楚，本文是对《收发原理》中的理念用代码具体实现。<span id="more-542"></span></p>
<h2>目录</h2>
<p>网络设备驱动程序的开发，分解成以下步骤：</p>
<p>上：</p>
<ul>
<li>1.检测设备</li>
<li>2.启用设备</li>
<li>3.认识网络设备</li>
<li>4.总线无关的设备访问</li>
<li>5.理解PCI配置空间</li>
<li>6.初始化网络设备（net_device）</li>
</ul>
<p>中：</p>
<ul>
<li>7.RTL8139收发原理</li>
</ul>
<p>下：</p>
<ul>
<li>8.编写网络设备的发包功能</li>
<li>9.编写网络设备的收包功能</li>
</ul>
<h2>8.实现网络设备的发包功能</h2>
<h3>8.1 扩展rtl8139_private</h3>
<p>在实现网络设备的打开和发送接口前，我必须先扩展我们的设备私有数据结构——rtl8139_private。</p>
<div class="codeblock">
<pre>

<font color="0000ff"><strong>#define NUM_TX_DESC 4</strong></font>
<strong>struct</strong> <font color="#2040a0">rtl8139_private</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">pci_dev</font> <font color="4444FF">*</font><font color="#2040a0">pci_dev</font><font color="4444FF">;</font>  <font color="#444444">/* PCI device */</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font> <font color="#444444">/* memory mapped I/O addr */</font>
    <strong>unsigned</strong> <strong>long</strong> <font color="#2040a0">regs_len</font><font color="4444FF">;</font> <font color="#444444">/* length of I/O or MMI/O region */</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">tx_flag</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">cur_tx</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">dirty_tx</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="4444FF">*</font><font color="#2040a0">tx_buf</font><font color="4444FF">[</font><font color="#2040a0">NUM_TX_DESC</font><font color="4444FF">]</font><font color="4444FF">;</font>   <font color="#444444">/* Tx bounce buffers */</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="4444FF">*</font><font color="#2040a0">tx_bufs</font><font color="4444FF">;</font>        <font color="#444444">/* Tx bounce buffer region. */</font>
    <font color="#2040a0">dma_addr_t</font> <font color="#2040a0">tx_bufs_dma</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font><font color="4444FF">;</font>
</pre>
</div>
<p>Table 9: rtl8139_private structure</p>
<p>rtl8139_private为实现发包功能新添6个成员，我们一一介绍它们。tx_flag 成员从名字可知，发送状态字，它是《收发原理》中的TSD在主机方的一个副本，用来启动发送，下面代码中你将会看到；cur_tx成员和dirty_tx成员是《收发原理》中发包原理部分提到的两个全局变量——write_buff（记录最新可用缓冲区号）和read_buff（记录最后发送缓冲区号）的实现。tx_bufs是缓冲区的起始地址，tx_buf是地址数组，分别记录四块缓冲区的起始地址，它是《收发原理》中的TSAD在主机方的一个副本。注意这两种地址都是逻辑地址，在配置TSAD前必须转换为物理地址（转换方法在代码中可以看到）。转换的根据就是下一个成员，tx_bufs_dma。tx_bufs_dma保存缓冲区的物理地址。</p>
<h3>8.2 扩展open接口</h3>
<p>改写了rtl8139_private后，我们看open接口的新实现：</p>
<div><span id="stateBut1" style="font-size: 35px; cursor: hand;" onclick="do_folding(this,'cb1')">></span>Table 11: Writing the open function</div>
<div id="cb1" class="codeblock" style="display: none;">
<pre>
<strong>static</strong> <strong>int</strong> <font color="#2040a0">rtl8139_open</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>int</strong> <font color="#2040a0">retval</font><font color="4444FF">;</font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>

    <font color="#444444">/* get the IRQ
    * second arg is interrupt handler
    * third is flags, 0 means no IRQ sharing
    */</font>
    <font color="#2040a0">retval</font> <font color="4444FF">=</font> <font color="#2040a0">request_irq</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">irq</font>, <font color="#2040a0">rtl8139_interrupt</font>, <font color="#FF0000">0</font>, <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">name</font>, <font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">retval</font><font color="4444FF">)</font>
        <strong>return</strong> <font color="#2040a0">retval</font><font color="4444FF">;</font>

    <font color="#444444">/* get memory for Tx buffers
    * memory must be DMAable
    */</font>
    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font> <font color="4444FF">=</font> <font color="#2040a0">pci_alloc_consistent</font><font color="4444FF">(</font>
    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">pci_dev</font>, <font color="#2040a0">TOTAL_TX_BUF_SIZE</font>, <font color="4444FF">&amp;</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs_dma</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <strong>if</strong><font color="4444FF">(</font><font color="4444FF">!</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
        <font color="#2040a0">free_irq</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">irq</font>, <font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <strong>return</strong> <font color="4444FF">-</font><font color="#2040a0">ENOMEM</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>

    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_flag</font> <font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">;</font>
    <font color="#2040a0">rtl8139_init_ring</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="#2040a0">rtl8139_hw_start</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>

<strong>static</strong> <strong>void</strong> <font color="#2040a0">rtl8139_init_ring</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
    <strong>int</strong> <font color="#2040a0">i</font><font color="4444FF">;</font>

    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_tx</font> <font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">;</font>
    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font> <font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">;</font>

    <strong>for</strong> <font color="4444FF">(</font><font color="#2040a0">i</font> <font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">;</font> <font color="#2040a0">i</font> <font color="4444FF">&lt;</font> <font color="#2040a0">NUM_TX_DESC</font><font color="4444FF">;</font> <font color="#2040a0">i</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">)</font>
        <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_buf</font><font color="4444FF">[</font><font color="#2040a0">i</font><font color="4444FF">]</font> <font color="4444FF">=</font> <font color="4444FF">&amp;</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font><font color="4444FF">[</font><font color="#2040a0">i</font> <font color="4444FF">*</font> <font color="#2040a0">TX_BUF_SIZE</font><font color="4444FF">]</font><font color="4444FF">;</font>

    <strong>return</strong><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>现在简要分析一下，open函数首先通过API（request_irq）向系统申请绑定IRQ号（KEMIN：逻辑中断号是怎么得到的？）和中断处理函数（rtl8139_interrupt）；接着向PCI子系统申请内存空间给发送缓冲区（tp-&gt;tx_bufs），注意，pci_alloc_consistent直接返回的是虚拟地址，物理地址通过第三个参数返回。然后在rtl8139_init_ring 函数中将缓冲区分为四段（tp-&gt;tx_buf[i]）。</p>
<div class="codeblock">
<pre>
<strong>static</strong> <strong>void</strong> <font color="#2040a0">rtl8139_chip_reset</font> <font color="4444FF">(</font><strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">ioaddr</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>int</strong> <font color="#2040a0">i</font><font color="4444FF">;</font>

    <font color="#444444">/* Soft reset the chip. */</font>
    <font color="#2040a0">writeb</font><font color="4444FF">(</font><font color="#2040a0">CmdReset</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">CR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* Check that the chip has finished the reset. */</font>
    <strong>for</strong> <font color="4444FF">(</font><font color="#2040a0">i</font> <font color="4444FF">=</font> <font color="#FF0000">1000</font><font color="4444FF">;</font> <font color="#2040a0">i</font> <font color="4444FF">&gt;</font> <font color="#FF0000">0</font><font color="4444FF">;</font> <font color="#2040a0">i</font><font color="4444FF">-</font><font color="4444FF">-</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
        <font color="#2040a0">barrier</font><font color="4444FF">(</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <strong>if</strong> <font color="4444FF">(</font><font color="4444FF">(</font><font color="#2040a0">readb</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">CR</font><font color="4444FF">)</font> <font color="4444FF">&amp;</font> <font color="#2040a0">CmdReset</font><font color="4444FF">)</font> <font color="4444FF">=</font><font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">)</font>
            <strong>break</strong><font color="4444FF">;</font>
        <font color="#2040a0">udelay</font> <font color="4444FF">(</font><font color="#FF0000">10</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>
    <strong>return</strong><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>缓冲区分配好后，rtl8139_hw_start函数可以启动硬件了。我们首先做是的重置（reset）设备——向设备命令寄存器（CR）写入重置值，让RTL8139回到预定状态；rtl8139_chip_reset函数使用了一个循环来检测重置操作，注意循环开始用了一个内存防护（barrier）操作，确保内核每次循环确切读取设备，而不作优化去读缓存。</p>
<div class="codeblock">
<pre>
<strong>static</strong> <strong>void</strong> <font color="#2040a0">rtl8139_hw_start</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">ioaddr</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font>
    <font color="#2040a0">u32</font> <font color="#2040a0">i</font><font color="4444FF">;</font>

    <font color="#2040a0">rtl8139_chip_reset</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* Must enable Tx before setting transfer thresholds! */</font>
    <font color="#2040a0">writeb</font><font color="4444FF">(</font><font color="#2040a0">CmdTxEnb</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">CR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* tx config */</font>
    <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="#FF0000">0x00000600</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">TCR</font><font color="4444FF">)</font><font color="4444FF">;</font> <font color="#444444">/* DMA burst size 1024 */</font>

    <font color="#444444">/* init Tx buffer DMA addresses */</font>
    <strong>for</strong> <font color="4444FF">(</font><font color="#2040a0">i</font> <font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">;</font> <font color="#2040a0">i</font> <font color="4444FF">&lt;</font> <font color="#2040a0">NUM_TX_DESC</font><font color="4444FF">;</font> <font color="#2040a0">i</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
        <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs_dma</font> <font color="4444FF">+</font> <font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_buf</font><font color="4444FF">[</font><font color="#2040a0">i</font><font color="4444FF">]</font> <font color="4444FF">-</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font><font color="4444FF">)</font>,<font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">TSAD0</font> <font color="4444FF">+</font> <font color="4444FF">(</font><font color="#2040a0">i</font> <font color="4444FF">*</font> <font color="#FF0000">4</font><font color="4444FF">)</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>

    <font color="#444444">/* Enable all known interrupts by setting the interrupt mask. */</font>
    <font color="#2040a0">writew</font><font color="4444FF">(</font><font color="#2040a0">INT_MASK</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">IMR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#2040a0">netif_start_queue</font> <font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>完成重置操作后，我们启用设备的发送功能——向设备命令寄存器（CR）写入启用值。然后，我们配置发送模式—— TCR (Transmission Configuration Register)，这里我们只配置了“DMA突发传输的最大值”（Max DMA Burst Size per Tx DMA Burst），其它使用默认配置。接着我们把刚分配好四段缓冲区地址（转为物理地址后）写入四个发送描述符（TSAD），最后打开所有中断——将IMR (Interrupt Mask Register)全部位置1；</p>
<p>至此，设备使用前配置基本完成，最后调用netif_start_queue，把设备挂到系统活动的网络设备列表，供网络协议栈使用。</p>
<h3>8.3 发送接口hard_start_xmit</h3>
<p>我们现在可以进一点充实发包接口了。代码如下：</p>
<div class="codeblock">
<pre><font color="0000ff"><strong>#define ETH_MIN_LEN 60 <font color="#444444"> /* minimum Ethernet frame size */</font></strong></font>

<strong>static</strong> <strong>int</strong> <font color="#2040a0">rtl8139_start_xmit</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">sk_buff</font> <font color="4444FF">*</font><font color="#2040a0">skb</font>, <strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">ioaddr</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">entry</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_tx</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">len</font> <font color="4444FF">=</font> <font color="#2040a0">skb</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">len</font><font color="4444FF">;</font>

    <strong>if</strong> <font color="4444FF">(</font><font color="#2040a0">len</font> <font color="4444FF">&lt;</font> <font color="#2040a0">TX_BUF_SIZE</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
        <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">len</font> <font color="4444FF">&lt;</font> <font color="#2040a0">ETH_MIN_LEN</font><font color="4444FF">)</font>
            <font color="#2040a0">memset</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_buf</font><font color="4444FF">[</font><font color="#2040a0">entry</font><font color="4444FF">]</font>, <font color="#FF0000">0</font>, <font color="#2040a0">ETH_MIN_LEN</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <font color="#2040a0">skb_copy_and_csum_dev</font><font color="4444FF">(</font><font color="#2040a0">skb</font>, <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_buf</font><font color="4444FF">[</font><font color="#2040a0">entry</font><font color="4444FF">]</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <font color="#2040a0">dev_kfree_skb</font><font color="4444FF">(</font><font color="#2040a0">skb</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font> <strong>else</strong> <font color="4444FF"><strong>{</strong></font>
        <font color="#2040a0">dev_kfree_skb</font><font color="4444FF">(</font><font color="#2040a0">skb</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>

    <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_flag</font> <font color="4444FF">|</font> <font color="#2040a0">max</font><font color="4444FF">(</font><font color="#2040a0">len</font>, <font color="4444FF">(</font><strong>unsigned</strong> <strong>int</strong><font color="4444FF">)</font><font color="#2040a0">ETH_MIN_LEN</font><font color="4444FF">)</font>,
    <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">TSD0</font> <font color="4444FF">+</font> <font color="4444FF">(</font><font color="#2040a0">entry</font> <font color="4444FF">*</font> <strong>sizeof</strong> <font color="4444FF">(</font><font color="#2040a0">u32</font><font color="4444FF">)</font><font color="4444FF">)</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="#2040a0">entry</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">;</font>
    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_tx</font> <font color="4444FF">=</font> <font color="#2040a0">entry</font> <font color="4444FF">%</font> <font color="#2040a0">NUM_TX_DESC</font><font color="4444FF">;</font>

    <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_tx</font> <font color="4444FF">=</font><font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
        <font color="#2040a0">netif_stop_queue</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>
    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>Table 12: Writing start_xmit function</p>
<p>发送接口函数还是比较直观的。首先，函数检查数据包大小，确保不大于缓冲区，亦不小于以太网帧最小值（60字节）；然后调用skb_copy_and_csum_dev函数将其拷入第cur_tx号缓冲区。完了后，配置tx_flag（这里只配置了数据包的大小，不限阀值）后调用writel将其写TSD，启动发送。接着，更新cur_tx，代码用模操作（%）来实现缓冲区循环制使用。最后判断缓冲区是否满，然则通知协议栈停止发送。</p>
<h3>8.4 发包完成中断处理</h3>
<p>数据包发走后还需要后续处理，处理在中断处理函数内完成。设备驱动只有一支中断处理函数，设备的中断事件需在函数内进一步区分。</p>
<div><span id="stateBut2" style="font-size: 35px; cursor: hand;" onclick="do_folding(this,'cb2')">></span>rtl8139_interrupt</div>
<div id="cb2" class="codeblock" style="display: none;">
<pre><strong>static</strong> <strong>void</strong> <font color="#2040a0">rtl8139_interrupt</font> <font color="4444FF">(</font><strong>int</strong> <font color="#2040a0">irq</font>, <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">dev_instance</font>, <strong>struct</strong> <font color="#2040a0">pt_regs</font> <font color="4444FF">*</font><font color="#2040a0">regs</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font> <font color="4444FF">=</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font><font color="4444FF">*</font><font color="4444FF">)</font><font color="#2040a0">dev_instance</font><font color="4444FF">;</font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">ioaddr</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>short</strong> <font color="#2040a0">isr</font> <font color="4444FF">=</font> <font color="#2040a0">readw</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">ISR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* clear all interrupt.
    * Specs says reading ISR clears all interrupts and writing
    * has no effect. But this does not seem to be case. I keep on
    * getting interrupt unless I forcibly clears all interrupt <img src='http://arttech.us/wp-includes/images/smilies/icon_sad.gif' alt=':-(' class='wp-smiley' />
    */</font>
    <font color="#2040a0">writew</font><font color="4444FF">(</font><font color="#FF0000">0xffff</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">ISR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <strong>if</strong><font color="4444FF">(</font><font color="4444FF">(</font><font color="#2040a0">isr</font> <font color="4444FF">&amp;</font> <font color="#2040a0">TxOK</font><font color="4444FF">)</font> <font color="4444FF">|</font><font color="4444FF">|</font> <font color="4444FF">(</font><font color="#2040a0">isr</font> <font color="4444FF">&amp;</font> <font color="#2040a0">TxErr</font><font color="4444FF">)</font><font color="4444FF">)</font>
    <font color="4444FF"><strong>{</strong></font>
        <strong>while</strong><font color="4444FF">(</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font> <font color="4444FF">!</font><font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_tx</font><font color="4444FF">)</font> <font color="4444FF">|</font><font color="4444FF">|</font> <font color="#2040a0">netif_queue_stopped</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">)</font>
        <font color="4444FF"><strong>{</strong></font>
            <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">txstatus</font> <font color="4444FF">=</font>
            <font color="#2040a0">readl</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">TSD0</font> <font color="4444FF">+</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font> <font color="4444FF">*</font> <strong>sizeof</strong><font color="4444FF">(</font><strong>int</strong><font color="4444FF">)</font><font color="4444FF">)</font><font color="4444FF">;</font>

            <strong>if</strong><font color="4444FF">(</font><font color="4444FF">!</font><font color="4444FF">(</font><font color="#2040a0">txstatus</font> <font color="4444FF">&amp;</font> <font color="4444FF">(</font><font color="#2040a0">TxStatOK</font> <font color="4444FF">|</font> <font color="#2040a0">TxAborted</font> <font color="4444FF">|</font> <font color="#2040a0">TxUnderrun</font><font color="4444FF">)</font><font color="4444FF">)</font><font color="4444FF">)</font>
                <strong>break</strong><font color="4444FF">;</font> <font color="#444444">/* yet not transmitted */</font>

            <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">txstatus</font> <font color="4444FF">&amp;</font> <font color="#2040a0">TxStatOK</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
                <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Transmit OK interrupt</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
                <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stats</font>.<font color="#2040a0">tx_bytes</font> <font color="4444FF">+</font><font color="4444FF">=</font> <font color="4444FF">(</font><font color="#2040a0">txstatus</font> <font color="4444FF">&amp;</font> <font color="#FF0000">0x1fff</font><font color="4444FF">)</font><font color="4444FF">;</font>
                <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stats</font>.<font color="#2040a0">tx_packets</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">;</font>
            <font color="4444FF"><strong>}</strong></font>
            <strong>else</strong> <font color="4444FF"><strong>{</strong></font>
                <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Transmit Error interrupt</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
                <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stats</font>.<font color="#2040a0">tx_errors</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>

        <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">;</font>
        <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font> <font color="4444FF">%</font> <font color="#2040a0">NUM_TX_DESC</font><font color="4444FF">;</font>

        <strong>if</strong><font color="4444FF">(</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dirty_tx</font> <font color="4444FF">=</font><font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_tx</font><font color="4444FF">)</font> <font color="4444FF">&amp;</font> <font color="#2040a0">netif_queue_stopped</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">)</font>
        <font color="4444FF"><strong>{</strong></font>
            <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;waking up queue</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
            <font color="#2040a0">netif_wake_queue</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>
        <font color="4444FF"><strong>}</strong></font>
    <font color="4444FF"><strong>}</strong></font>
.......
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>代码中我们可以看到，中断处理首先将8139的中断状态复制到本地，然后重置中断状态；接着判断是什么中断事件，如果是发包完成（TxOK），则取得发送状态信息，然后作一些统计。最后更新读指针（dirty_tx），并唤醒发包队列。</p>
<p>值得注意的是，统计和更新操作在一个while循环内完成的，那是因为8139不是在成功发送一个包后发出中断，而是将缓冲区上所有包发走后才发出中断的。可以想像一下发包情形，由于相对于8139，主机CPU较快，它会在很短的时间内（或在发完第一个包之前）[注]将发包缓冲区填满而停掉发包队列，等待缓冲区再次可用，而8139在处理完所有TSD.OWN为0的缓冲区后，发出中断，唤醒发包队列，如此循环往复。</p>
<p class="comment">
注：事实上，主机CPU在8139发完哪个包之前停掉发包队列均可，因为主机CPU和8139是独立工作的。只有一个情况值得特别注意，就是当主机CPU在完成写入一块缓冲区前，发包完成中断（TxOK）出现。由于发包完成中断是硬件中断，优先级较高，它会中断发包函数，优先处理。所以必须特别小心两个函数的共享变量——cur_tx和dirty_tx的访问顺序。</p>
<p>现在我们的驱动程序已经具有发包功能了。你可以编译并安装它，尝试ping一个远程主机，如无意外，你将在远程主机看到有ARP数据包收到；不过在本地，我们看不到远程主机发回的ARP应答包，因为我们还没有实现收包功能。</p>
<h2>9.实现网络设备的收包功能</h2>
<p>接下来，我们实现网络设备的收包功能，与发包功能实现相似，我们需要扩展现有打开接口和设备私有数据。</p>
<h3>9.1 扩展rtl8139_private</h3>
<div class="codeblock">
<pre><strong>struct</strong> <font color="#2040a0">rtl8139_private</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">pci_dev</font> <font color="4444FF">*</font><font color="#2040a0">pci_dev</font><font color="4444FF">;</font>  <font color="#444444">/* PCI device */</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font> <font color="#444444">/* memory mapped I/O addr */</font>
    <strong>unsigned</strong> <strong>long</strong> <font color="#2040a0">regs_len</font><font color="4444FF">;</font> <font color="#444444">/* length of I/O or MMI/O region */</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">tx_flag</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">cur_tx</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">dirty_tx</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="4444FF">*</font><font color="#2040a0">tx_buf</font><font color="4444FF">[</font><font color="#2040a0">NUM_TX_DESC</font><font color="4444FF">]</font><font color="4444FF">;</font>   <font color="#444444">/* Tx bounce buffers */</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="4444FF">*</font><font color="#2040a0">tx_bufs</font><font color="4444FF">;</font>        <font color="#444444">/* Tx bounce buffer region. */</font>
    <font color="#2040a0">dma_addr_t</font> <font color="#2040a0">tx_bufs_dma</font><font color="4444FF">;</font>

    <strong>struct</strong> <font color="#2040a0">net_device_stats</font> <font color="#2040a0">stats</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="4444FF">*</font><font color="#2040a0">rx_ring</font><font color="4444FF">;</font>
    <font color="#2040a0">dma_addr_t</font> <font color="#2040a0">rx_ring_dma</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">cur_rx</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font><font color="4444FF">;</font>
</pre>
</div>
<p>Table 13: Extending rtl8139_private structure</p>
<p>rtl8139_private为实现收包功能新添4个成员。stats成员是net_device_stats实例[注]，记录设备运作的统计信息（如大部分通过ifconfig查询到的统计信息来源此成员）。rx_ring成员是收包缓冲区（环形缓冲区）的逻辑地址，rx_ring_dma成员是对应的物理地址。cur_rx成员是环缓冲的读指针，不过它的类型不是指针，因为它是一个16位偏移值。</p>
<p class="comment">
注：本文使用的代码比较原始，此成员目前已经被标准化，抽象到通用的结构net_device上去了。</p>
<h3>9.2 扩展open接口</h3>
<p>扩展的第一步是分配收包缓冲区。</p>
<div class="codeblock">
<pre><font color="#444444">/* Size of the in-memory receive ring. */</font>
<font color="0000ff"><strong>#define RX_BUF_LEN_IDX 2        <font color="#444444"> /* 0==8K, 1==16K, 2==32K, 3==64K */</font></strong></font>
<font color="0000ff"><strong>#define RX_BUF_LEN     (8192 &lt;&lt; RX_BUF_LEN_IDX)</strong></font>
<font color="0000ff"><strong>#define RX_BUF_PAD     16          <font color="#444444"> /* see 11th and 12th bit of RCR: 0x44 */</font></strong></font>
<font color="0000ff"><strong>#define RX_BUF_WRAP_PAD 2048  <font color="#444444"> /* spare padding to handle pkt wrap */</font></strong></font>
<font color="0000ff"><strong>#define RX_BUF_TOT_LEN  (RX_BUF_LEN + RX_BUF_PAD + RX_BUF_WRAP_PAD)</strong></font>

<font color="#444444">/* this we have already done */</font>
<font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font> <font color="4444FF">=</font> <font color="#2040a0">pci_alloc_consistent</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">pci_dev</font>, <font color="#2040a0">TOTAL_TX_BUF_SIZE</font>, <font color="4444FF">&amp;</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs_dma</font><font color="4444FF">)</font><font color="4444FF">;</font>

<font color="#444444">/* add this code to rtl8139_function */</font>
<font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring</font> <font color="4444FF">=</font> <font color="#2040a0">pci_alloc_consistent</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">pci_dev</font>, <font color="#2040a0">RX_BUF_TOT_LEN</font>,
               <font color="4444FF">&amp;</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring_dma</font><font color="4444FF">)</font><font color="4444FF">;</font>

<strong>if</strong><font color="4444FF">(</font><font color="4444FF">(</font><font color="4444FF">!</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font><font color="4444FF">)</font>  <font color="4444FF">|</font><font color="4444FF">|</font> <font color="4444FF">(</font><font color="4444FF">!</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring</font><font color="4444FF">)</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">free_irq</font><font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">irq</font>, <font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>

<strong>if</strong><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">pci_free_consistent</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">pci_dev</font>, <font color="#2040a0">TOTAL_TX_BUF_SIZE</font>, <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font>, <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs_dma</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="#2040a0">p</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font> <font color="4444FF">=</font> <font color="#2040a0">NULL</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
<strong>if</strong><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">pci_free_consistent</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">pci_dev</font>, <font color="#2040a0">RX_BUF_TOT_LEN</font>, <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring</font>,
                    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring_dma</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring</font> <font color="4444FF">=</font> <font color="#2040a0">NULL</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>
<strong>return</strong> <font color="4444FF">-</font><font color="#2040a0">ENOMEM</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>Table 14: Extending rtl8139_open function</p>
<p>代码14首先算得收包缓冲区所需大小。RX_BUF_TOT_LEN 的值取决于收包配置（RCR寄存器）。我将在下面扩展的rtl8139_hw_start函数看到，我们配置了RCR的位12-11为10，位7为1；前者意为收包缓冲区大小为32K+16，后者意为当写入收到的数据包写到了缓冲区的末端，而数据包还没有收完时，剩下的数据继续往下写，而不写到缓冲区的始端，因此我们为收包缓冲区分配了2K额外的附加区。</p>
<p>现在我们扩展rtl8139_hw_start：</p>
<div><span id="stateBut3" style="font-size: 35px; cursor: hand;" onclick="do_folding(this,'cb3')">></span>Table 15: Extending rtl8139_hw_start function</div>
<div id="cb3" class="codeblock" style="display:none">
<pre><strong>static</strong> <strong>void</strong> <font color="#2040a0">rtl8139_hw_start</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">ioaddr</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font>
    <font color="#2040a0">u32</font> <font color="#2040a0">i</font><font color="4444FF">;</font>

    <font color="#2040a0">rtl8139_chip_reset</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* Must enable Tx/Rx before setting transfer thresholds! */</font>
    <font color="#2040a0">writeb</font><font color="4444FF">(</font><font color="#2040a0">CmdTxEnb</font> <font color="4444FF">|</font> <font color="#2040a0">CmdRxEnb</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">CR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* tx config */</font>
    <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="#FF0000">0x00000600</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">TCR</font><font color="4444FF">)</font><font color="4444FF">;</font> <font color="#444444">/* DMA burst size 1024 */</font>

    <font color="#444444">/* rx config */</font>
    <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="4444FF">(</font><font color="4444FF">(</font><font color="#FF0000">1</font> <font color="4444FF">&lt;</font><font color="4444FF">&lt;</font> <font color="#FF0000">12</font><font color="4444FF">)</font> <font color="4444FF">|</font> <font color="4444FF">(</font><font color="#FF0000">7</font> <font color="4444FF">&lt;</font><font color="4444FF">&lt;</font> <font color="#FF0000">8</font><font color="4444FF">)</font> <font color="4444FF">|</font> <font color="4444FF">(</font><font color="#FF0000">1</font> <font color="4444FF">&lt;</font><font color="4444FF">&lt;</font> <font color="#FF0000">7</font><font color="4444FF">)</font> <font color="4444FF">|</font>
    <font color="4444FF">(</font><font color="#FF0000">1</font> <font color="4444FF">&lt;</font><font color="4444FF">&lt;</font> <font color="#FF0000">3</font><font color="4444FF">)</font> <font color="4444FF">|</font> <font color="4444FF">(</font><font color="#FF0000">1</font> <font color="4444FF">&lt;</font><font color="4444FF">&lt;</font> <font color="#FF0000">2</font><font color="4444FF">)</font> <font color="4444FF">|</font> <font color="4444FF">(</font><font color="#FF0000">1</font> <font color="4444FF">&lt;</font><font color="4444FF">&lt;</font> <font color="#FF0000">1</font><font color="4444FF">)</font><font color="4444FF">)</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">RCR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* init Tx buffer DMA addresses */</font>
    <strong>for</strong> <font color="4444FF">(</font><font color="#2040a0">i</font> <font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">;</font> <font color="#2040a0">i</font> <font color="4444FF">&lt;</font> <font color="#2040a0">NUM_TX_DESC</font><font color="4444FF">;</font> <font color="#2040a0">i</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
        <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs_dma</font> <font color="4444FF">+</font> <font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_buf</font><font color="4444FF">[</font><font color="#2040a0">i</font><font color="4444FF">]</font> <font color="4444FF">-</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">tx_bufs</font><font color="4444FF">)</font>,
        <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">TSAD0</font> <font color="4444FF">+</font> <font color="4444FF">(</font><font color="#2040a0">i</font> <font color="4444FF">*</font> <font color="#FF0000">4</font><font color="4444FF">)</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>

    <font color="#444444">/* init RBSTART */</font>
    <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring_dma</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">RBSTART</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* initialize missed packet counter */</font>
    <font color="#2040a0">writel</font><font color="4444FF">(</font><font color="#FF0000">0</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">MPC</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* no early-rx interrupts */</font>
    <font color="#2040a0">writew</font><font color="4444FF">(</font><font color="4444FF">(</font><font color="#2040a0">readw</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">MULINT</font><font color="4444FF">)</font> <font color="4444FF">&amp;</font> <font color="#FF0000">0xF000</font><font color="4444FF">)</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">MULINT</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* Enable all known interrupts by setting the interrupt mask. */</font>
    <font color="#2040a0">writew</font><font color="4444FF">(</font><font color="#2040a0">INT_MASK</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">IMR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#2040a0">netif_start_queue</font> <font color="4444FF">(</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>代码15中，首先改动的是写入CR寄存器值 CmdTxEnb | CmdRxEnb，意为同时开启收包和发包功能；接着配置了收包功能，我在代码没有使用macros作配置值，但意思已经很明白了，RCR的配置位意思如下：</p>
<ul>
<li>Bit 1 &#8211; 接受物理匹配的包</li>
<li>Bit 2 &#8211; 接受组播包</li>
<li>Bit 3 &#8211; 接受广播包</li>
<li>Bit 7 &#8211; WRAP，当写入收到的数据包写到了缓冲区的末端，而数据包还没有收完时，剩下的数据继续往下写，还是截断写到始端</li>
<li>Bit 8-10 &#8211; 最大DMA单次突发传输量，我们配置为111，即无限制</li>
<li>Bit 11-12 &#8211; 收包缓冲区大小，我们配置为10，即32K+16 bytes</li>
</ul>
<p>接着的改动是配置了8139的RBSTART寄存器，告诉8139收包缓冲区的起始地址；最后初始化了MPC (Missed Packet Counter)寄存和屏蔽了预收包中断（early rx interrupts）。</p>
<h3>9.3 收包中断处理函数</h3>
<p>收包功能最后一步是收包中断处理。</p>
<div><span id="stateBut4" style="font-size: 35px; cursor: hand;" onclick="do_folding(this,'cb4')">></span>Table 16: Interrupt Handler</div>
<div id="cb4" class="codeblock" style="display:none">
<pre><strong>static</strong> <strong>void</strong> <font color="#2040a0">rtl8139_interrupt</font> <font color="4444FF">(</font><strong>int</strong> <font color="#2040a0">irq</font>, <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">dev_instance</font>, <strong>struct</strong> <font color="#2040a0">pt_regs</font> <font color="4444FF">*</font><font color="#2040a0">regs</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font> <font color="4444FF">=</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font><font color="4444FF">*</font><font color="4444FF">)</font><font color="#2040a0">dev_instance</font><font color="4444FF">;</font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">ioaddr</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>short</strong> <font color="#2040a0">isr</font> <font color="4444FF">=</font> <font color="#2040a0">readw</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">ISR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#444444">/* clear all interrupt.
    * Specs says reading ISR clears all interrupts and writing
    * has no effect. But this does not seem to be case. I keep on
    * getting interrupt unless I forcibly clears all interrupt <img src='http://arttech.us/wp-includes/images/smilies/icon_sad.gif' alt=':-(' class='wp-smiley' />
    */</font>
    <font color="#2040a0">writew</font><font color="4444FF">(</font><font color="#FF0000">0xffff</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">ISR</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">isr</font> <font color="4444FF">&amp;</font> <font color="#2040a0">RxOK</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;receive interrupt received</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>while</strong><font color="4444FF">(</font><font color="4444FF">(</font><font color="#2040a0">readb</font><font color="4444FF">(</font><font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">CR</font><font color="4444FF">)</font> <font color="4444FF">&amp;</font> <font color="#2040a0">RxBufEmpty</font><font color="4444FF">)</font> <font color="4444FF">=</font><font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">)</font>
    <font color="4444FF"><strong>{</strong></font>
        <strong>unsigned</strong> <strong>int</strong> <font color="#2040a0">rx_status</font><font color="4444FF">;</font>
        <strong>unsigned</strong> <strong>short</strong> <font color="#2040a0">rx_size</font><font color="4444FF">;</font>
        <strong>unsigned</strong> <strong>short</strong> <font color="#2040a0">pkt_size</font><font color="4444FF">;</font>
        <strong>struct</strong> <font color="#2040a0">sk_buff</font> <font color="4444FF">*</font><font color="#2040a0">skb</font><font color="4444FF">;</font>

        <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_rx</font> <font color="4444FF">&gt;</font> <font color="#2040a0">RX_BUF_LEN</font><font color="4444FF">)</font>
            <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_rx</font> <font color="4444FF">=</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_rx</font> <font color="4444FF">%</font> <font color="#2040a0">RX_BUF_LEN</font><font color="4444FF">;</font>

        <font color="#444444">/* TODO: need to convert rx_status from little to host endian
        * XXX: My CPU is little endian only <img src='http://arttech.us/wp-includes/images/smilies/icon_smile.gif' alt=':-)' class='wp-smiley' />
        */</font>
        <font color="#2040a0">rx_status</font> <font color="4444FF">=</font> <font color="4444FF">*</font><font color="4444FF">(</font><strong>unsigned</strong> <strong>int</strong><font color="4444FF">*</font><font color="4444FF">)</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring</font> <font color="4444FF">+</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_rx</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <font color="#2040a0">rx_size</font> <font color="4444FF">=</font> <font color="#2040a0">rx_status</font> <font color="4444FF">&gt;</font><font color="4444FF">&gt;</font> <font color="#FF0000">16</font><font color="4444FF">;</font>

        <font color="#444444">/* first two bytes are receive status register
        * and next two bytes are frame length
        */</font>
        <font color="#2040a0">pkt_size</font> <font color="4444FF">=</font> <font color="#2040a0">rx_size</font> <font color="4444FF">-</font> <font color="#FF0000">4</font><font color="4444FF">;</font>

        <font color="#444444">/* hand over packet to system */</font>
        <font color="#2040a0">skb</font> <font color="4444FF">=</font> <font color="#2040a0">dev_alloc_skb</font> <font color="4444FF">(</font><font color="#2040a0">pkt_size</font> <font color="4444FF">+</font> <font color="#FF0000">2</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <strong>if</strong> <font color="4444FF">(</font><font color="#2040a0">skb</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
            <font color="#2040a0">skb</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dev</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">;</font>
            <font color="#2040a0">skb_reserve</font> <font color="4444FF">(</font><font color="#2040a0">skb</font>, <font color="#FF0000">2</font><font color="4444FF">)</font><font color="4444FF">;</font> <font color="#444444">/* 16 byte align the IP fields */</font>

            <font color="#2040a0">eth_copy_and_sum</font><font color="4444FF">(</font>
            <font color="#2040a0">skb</font>, <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">rx_ring</font> <font color="4444FF">+</font> <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_rx</font> <font color="4444FF">+</font> <font color="#FF0000">4</font>, <font color="#2040a0">pkt_size</font>, <font color="#FF0000">0</font><font color="4444FF">)</font><font color="4444FF">;</font>

            <font color="#2040a0">skb_put</font> <font color="4444FF">(</font><font color="#2040a0">skb</font>, <font color="#2040a0">pkt_size</font><font color="4444FF">)</font><font color="4444FF">;</font>
            <font color="#2040a0">skb</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">protocol</font> <font color="4444FF">=</font> <font color="#2040a0">eth_type_trans</font> <font color="4444FF">(</font><font color="#2040a0">skb</font>, <font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
            <font color="#2040a0">netif_rx</font> <font color="4444FF">(</font><font color="#2040a0">skb</font><font color="4444FF">)</font><font color="4444FF">;</font>

            <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">last_rx</font> <font color="4444FF">=</font> <font color="#2040a0">jiffies</font><font color="4444FF">;</font>
            <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stats</font>.<font color="#2040a0">rx_bytes</font> <font color="4444FF">+</font><font color="4444FF">=</font> <font color="#2040a0">pkt_size</font><font color="4444FF">;</font>
            <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stats</font>.<font color="#2040a0">rx_packets</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>
        <strong>else</strong> <font color="4444FF"><strong>{</strong></font>
            <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Memory squeeze, dropping packet.</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
            <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stats</font>.<font color="#2040a0">rx_dropped</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>

        <font color="#444444">/* update tp-&gt;cur_rx to next writing location  * /
        tp-&gt;cur_rx = (tp-&gt;cur_rx + rx_size + 4 + 3) &amp; ~3;

        /* update CAPR */</font>
        <font color="#2040a0">writew</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">cur_rx</font>, <font color="#2040a0">ioaddr</font> <font color="4444FF">+</font> <font color="#2040a0">CAPR</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>
    <font color="4444FF"><strong>}</strong></font>
//......
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>代码中我们可以看到，中断处理首先将8139的中断状态复制到本地，然后重置中断状态；接着判断收包中断事件（RxOK）。收包处理使用了一个while循环，可见收包与发包类似，都不是每包一次中断，而每缓冲区一次中断，这样降低的中断次数，提高收发效率。收包循环第一件事是判断读指针（cur_rx）是否越出缓冲区边界（从前面的收包功能配置可知，收包缓冲区可配置附加区的），如果越出，则wrap回来。完了后开始正式的收包操作，如取得收包状态、分析包大小、分配skb、对skb作一些链路层处理后将其交与内核的收包接口netif_rx，最后统计。</p>
<p>收包处理最后一步是更新读指针，包括主机CPU副本cur_rx和8139的CAPR。更新8139的CAPR还有一个重要的副作用，就是如果CAPR==CBA时，8139置CR.RxBufEmpty为1，表示缓冲为空，8139对外发出消除暂停控制帧，驱动程序退出while循环，完成收包中断处理。</p>
<h3>9.4 统计</h3>
<p>最后实现的接口是rtl8139_get_stats，它只是简单地返回tp-&gt;stats：</p>
<div class="codeblock">
<pre><strong>static</strong> <strong>struct</strong> <font color="#2040a0">net_device_stats</font><font color="4444FF">*</font> <font color="#2040a0">rtl8139_get_stats</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
    <strong>return</strong> <font color="4444FF">&amp;</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stats</font><font color="4444FF">)</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>Table 17: rtl8139_get_stats function</p>
<p>到此，网卡设备驱动基本完成，你可以再次编译并安装它，尝试ping一个远程主机，如无意外，你将在远程主机看到有ARP数据包收到；在本地，亦可以收到远程主机发回的ARP应答包。</p>
<h2>10.小结</h2>
<p>虽然专业级的设备驱动比本文的基本驱动需要更多的功能，然而，本文的基本驱动对你理解网卡驱动和开发产品级驱动是有帮助的。</p>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/writing-network-device-driver-b.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>RTL8139收发原理</title>
		<link>http://arttech.us/y-2011/rtl8139-rx-tx.html</link>
		<comments>http://arttech.us/y-2011/rtl8139-rx-tx.html#comments</comments>
		<pubDate>Tue, 06 Sep 2011 09:41:19 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[计算机网络]]></category>
		<category><![CDATA[驱动程序]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=529</guid>
		<description><![CDATA[RTL8139的硬件接口由256个字节寄存器组成，这些寄存器按可读性分为只读和可读写两类；按实现收发功能分为发送相关的和接收相关的；按实现收发原理的角色分为，配置寄存器、命令寄存器（或者叫控制寄存器）和状态寄存器；没有数据寄存器，因为RTL8139是PCI设备，使用了一片DMAble的缓冲区作“数据寄存器”。 本文简要地介绍一下RTL8139收发原理，和实现原理的基本寄存器，其它特色功能寄存器请查阅手册。本文先按实现收包原理、发包原理和全局原理，三类精要地介绍相关实现的寄存器。最后简述收包发包过程。 1.寄存器类型 CMD：命令类型寄存器 CFG：配置类型寄存器 STA：状态类型寄存器 “命令”和“配置”的操作语义需要区分，用户发命令，管理员配置，因此，像软重置、开启发送功能等被一贯认为是命令操作，个人认为划归为配置类比较确切。判别“命令”和“配置”的一条简单的方法就是看这些操作是用在设备驱动哪个函数上 ，例如，开启发送功能的（CmdTxEnb）操作是在初始化函数里，所以CmdTxEnb是配置操作，而非命令操作。另一种方法是寄存器（对于主机CPU）的可读写性，STA必定是只读的，CFG必定是只写的，而CMD是可读可写的。 2. 全局原理 2.1 命令寄存器（CR） Command Register (Offset 0037h, R/W, CMD) /* CR register commands */ #define RxBufEmpty 0x01 //STA &#124; 收包缓冲区是否空 #define CmdTxEnb 0x04 //CFG &#124; 激活发送状态机 #define CmdRxEnb 0x08 //CFG &#124; 激活接包状态机 #define CmdReset 0x10 //CFG &#124; 软件重置RTL8139 2.2 中断状态寄存器（ISR） 当RTL8139运行中出现各种事件需要报告给主机CPU，RTL8139置相应中断状态位，并发出中断请求信号。当然信号是否能发出，还取决于中断屏蔽的配置。 Interrupt Status Register (Offset 003Eh-003Fh, [...]]]></description>
			<content:encoded><![CDATA[<p>RTL8139的硬件接口由256个字节寄存器组成，这些寄存器按可读性分为只读和可读写两类；按实现收发功能分为发送相关的和接收相关的；按实现收发原理的角色分为，配置寄存器、命令寄存器（或者叫控制寄存器）和状态寄存器；没有数据寄存器，因为RTL8139是PCI设备，使用了一片DMAble的缓冲区作“数据寄存器”。</p>
<p>本文简要地介绍一下RTL8139收发原理，和实现原理的基本寄存器，其它特色功能寄存器请查阅手册。本文先按实现收包原理、发包原理和全局原理，三类精要地介绍相关实现的寄存器。最后简述收包发包过程。<span id="more-529"></span></p>
<h2>1.寄存器类型</h2>
<ul>
<li>CMD：命令类型寄存器</li>
<li>CFG：配置类型寄存器</li>
<li>STA：状态类型寄存器</li>
</ul>
<p>“命令”和“配置”的操作语义需要区分，用户发命令，管理员配置，因此，像软重置、开启发送功能等被一贯认为是命令操作，个人认为划归为配置类比较确切。判别“命令”和“配置”的一条简单的方法就是看这些操作是用在设备驱动哪个函数上 ，例如，开启发送功能的（CmdTxEnb）操作是在初始化函数里，所以CmdTxEnb是配置操作，而非命令操作。另一种方法是寄存器（对于主机CPU）的可读写性，STA必定是只读的，CFG必定是只写的，而CMD是可读可写的。</p>
<h2>2. 全局原理</h2>
<h3>2.1 命令寄存器（CR）</h3>
<div class="ref-block">
<pre>Command Register
(Offset 0037h, R/W, CMD)
/* CR register commands */
#define RxBufEmpty 0x01    //STA | 收包缓冲区是否空
#define CmdTxEnb   0x04    //CFG | 激活发送状态机
#define CmdRxEnb   0x08    //CFG | 激活接包状态机
#define CmdReset   0x10    //CFG | 软件重置RTL8139
</pre>
</div>
<h3>2.2 中断状态寄存器（ISR）</h3>
<p>当RTL8139运行中出现各种事件需要报告给主机CPU，RTL8139置相应中断状态位，并发出中断请求信号。当然信号是否能发出，还取决于中断屏蔽的配置。</p>
<div class="ref-block">
<pre>Interrupt Status Register
(Offset 003Eh-003Fh, R/W, STA)
/* ISR Bits */
#define RxOK       0x01
#define RxErr      0x02
#define TxOK       0x04
#define TxErr      0x08
#define RxOverFlow 0x10
#define RxUnderrun 0x20
#define RxFIFOOver 0x40
#define CableLen   0x2000
#define TimeOut    0x4000
#define SysErr     0x8000
</pre>
</div>
<h3>2.3 中断屏蔽寄存器（IMR）</h3>
<div class="ref-block">
<pre>Interrupt Mask Register
(Offset 003Ch-003Dh, R/W, CFG)
PciErr        = (1 &lt;&lt; 15), /* System error on the PCI bus */
TimerIntr    = (1 &lt;&lt; 14), /* Asserted when TCTR reaches TimerInt value */
LenChg        = (1 &lt;&lt; 13), /* Cable length change */
SWInt        = (1 &lt;&lt; 8),  /* Software-requested interrupt */
TxEmpty        = (1 &lt;&lt; 7),  /* No Tx descriptors available */
RxFIFOOvr    = (1 &lt;&lt; 6),  /* Rx FIFO Overflow */
LinkChg        = (1 &lt;&lt; 5),  /* Packet underrun, or link change */
RxEmpty        = (1 &lt;&lt; 4),  /* No Rx descriptors available */
TxErr        = (1 &lt;&lt; 3),  /* Tx error */
TxOK        = (1 &lt;&lt; 2),  /* Tx packet sent */
RxErr        = (1 &lt;&lt; 1),  /* Rx error */
RxOK        = (1 &lt;&lt; 0),  /* Rx packet received */
</pre>
</div>
<h2>3.发包原理</h2>
<p>RTL8139被设计使用四块DMAble的缓冲区作“发包数据寄存器”，保存驱动程序发来的数据包。这些缓冲区的地址被配置到RTL8139称为发送描述符（Transmission Descriptors or TD）的寄存器上，共四个，如下图。实际上TD由两个单独的寄存器组成，TSAD保存缓冲区地址，TSD是发送状态，下面会详细介绍。<br />
<a href="http://arttech.us/wp-content/uploads/2011/09/tx_desc_fifo.jpg"><img src="http://arttech.us/wp-content/uploads/2011/09/tx_desc_fifo.jpg" alt="" title="tx_desc_fifo" width="612" height="346" class="aligncenter size-full wp-image-531" /></a></p>
<p>发包大致过程是，驱动程序首先将数据包拷进一块发送缓冲区，然后将发送状态数据（数据包大小、发送阈值和PCI操作命令）写入缓冲区对应的TSD来启动数据发送；当RTL8139检测到发送命令后[注]，RTL8139通过PCI的DMA操作将该数据包读进内部的［Transmit FIFO］。［Transmit FIFO］是芯片内部的2K缓冲区，当［Transmit FIFO］达到由TSD配置的阈值（early transmit threshold）或已将整个数据包拷入时，数据包开始被发到网线上。</p>
<p class="comment">
注：发包原理中目前存有疑点，在手册上找不到说法，就是是主机主动通过TSD.OWN命令8139执行发送，还是TSD.OWN只是被动的命令配置位，8139循环检测TSD.OWN（四个），自动执行发送操作。目前的理解倾向于后者，因为如果前者的话，发送操作是主机与设备同步的，显然没必要设计四块缓冲区。如果是后者，那么“命令”与“配置”的语义界限变得模糊。这里用我的假设——8139循环检测命令位自动执行发送操作——来描述发包原理，有待进一步确认。</p>
<p>四个TD以循环制（round-robin）的方式使用。驱动程序必须使用两个全局变量——write_buff（记录最新可用缓冲区号）和read_buff（记录最后发送缓冲区号）——来协调循环制的方式使用。驱动程序将数据包拷进［最新可用］的一块发送缓冲区后，更新write_buff，如果write_buff等于read_buff，证明缓冲区已满，必须通知协议栈停止发送；当8139发送完一个数据包后发出“发送完中断请求”，中断处理必须更新read_buff，并唤醒发送队列 。<br />
<a href="http://arttech.us/wp-content/uploads/2011/09/tx_buffer.jpg"><img src="http://arttech.us/wp-content/uploads/2011/09/tx_buffer.jpg" alt="" title="tx_buffer" width="628" height="433" class="aligncenter size-full wp-image-532" /></a></p>
<h3>3.1 发送状态寄存器（TSD）</h3>
<p>这是一个非常重要的寄存器，手册很多描述发送原理细节的地方使用了［发送状态描述符（Transmit Status Descriptors or TSD）］概念，但标准标定义却是TSR，看来RTL8139的设计师不只一位，并且没有沟通好。不管名字是什么，反正它就是一四字节寄存器。TSD很混杂，兼有状态、配置和命令的功能，当然只是针对单次发送的。</p>
<div class="ref-block">
<pre>Transmit Status Register
(TSD0-3)(Offset 0010h-001Fh, R/W, STA|CFG|CMD )
//STA
Bit-14:    TxUnderrun
Bit-15:    TxStatOK
Bit-29:    TxOutOfWindow
Bit-30:    TxAborted
Bit-31:    TxCarrierLost
//CFG
Bit-0~12:    Transmit Byte Count
Bit-16~21:threshold level in the Tx FIFO
//CMD
Bit-13:    TxHostOwns
</pre>
</div>
<h3>3.2 发送起始地址寄存器（TSAD）</h3>
<p>这就是一个32位地址指针，指向一PCI设备能DAM访问的内存区，名曰发包缓冲区。而这一段缓冲区正是设备——建立在RTL8139硬件之上逻辑网络设备的“数据寄存器”。TSAD（四个）只在设备驱动的初始化代码配置好，一般不会再改写，证明了这一点。</p>
<div class="ref-block">
<pre>Transmit Start Address of Descriptors
(TSAD0-3)(Offset 0020h-002Fh, R/W, CFG )
</pre>
</div>
<h3>3.3 发送配置寄存器（TCR）</h3>
<p>配置8139的发送功能，如错误重发、两帧间的时间间隙、添加CRC头和DMA突发访问的数据大小等。</p>
<div class="ref-block">
<pre>Transmit Configuration Register
(Offset 0040h-0043h, R/W)
/* Bits in TxConfig. */
enum tx_config_bits {
    /* Interframe Gap Time. Only TxIFG96 doesn't violate IEEE 802.3 */
    TxIFGShift    = 24,
    TxIFG84        = (0 &lt;&lt; TxIFGShift), /* 8.4us / 840ns (10 / 100Mbps) */
    TxIFG88        = (1 &lt;&lt; TxIFGShift), /* 8.8us / 880ns (10 / 100Mbps) */
    TxIFG92        = (2 &lt;&lt; TxIFGShift), /* 9.2us / 920ns (10 / 100Mbps) */
    TxIFG96        = (3 &lt;&lt; TxIFGShift), /* 9.6us / 960ns (10 / 100Mbps) */

    TxLoopBack    = (1 &lt;&lt; 18) | (1 &lt;&lt; 17), /* enable loopback test mode */
    TxCRC        = (1 &lt;&lt; 16),    /* DISABLE Tx pkt CRC append */
    TxClearAbt    = (1 &lt;&lt; 0),    /* Clear abort (WO) */
    TxDMAShift    = 8, /* DMA burst value (0-7) is shifted X many bits */
    TxRetryShift    = 4, /* TXRR value (0-15) is shifted X many bits */

    TxVersionMask    = 0x7C800000, /* mask out version bits 30-26, 23 */
};
</pre>
</div>
<h2>4. 收包原理</h2>
<p>RTL8139被设计成使用环形缓冲区（ring buffer）作为“收包数据寄存器”来接收数据包。环形缓冲区也必须是DMAble，也就是它是一段物理上连续的内存。RTL8139内置了三个寄存器——RBSTART、CAPR和CBA——实现与主机协作读写环形缓冲区来传递数据。RBSTART是缓冲区的首地址，外部数据包首先通过网线进入RTL8139内部的 Receive FIFO，然后当数据量达到预设的阀值，RTL8139将数据通过PCI拷到缓冲区CBA地址处，完了更新CBA；驱动程序的收包中断则从CAPR地址处读取数据，完了后更CAPR。所以，寄存器CBA（Current Buffer Address）是环形缓冲区的写指针，寄存器CAPR（Current Address of Packet Read）是环形缓冲区的读指针。整个过程是由RTL8139主导的，它负责缓冲区溢出管理。<br />
<a href="http://arttech.us/wp-content/uploads/2011/09/rx_buffer.jpg"><img src="http://arttech.us/wp-content/uploads/2011/09/rx_buffer.jpg" alt="" title="rx_buffer" width="612" height="311" class="aligncenter size-full wp-image-533" /></a></p>
<h3>4.1 接收状态寄存器</h3>
<p>这不是一个寄存器，而是对［预-接收状态］的一个备份，附加在数据包的头，给驱动程序接收，以便后续处理。</p>
<div class="ref-block">
<pre>Receive Status Register in Rx Packet Header
enum RxStatusBits {
    RxMulticast    = 0x8000,
    RxPhysical    = 0x4000,
    RxBroadcast    = 0x2000,
    RxBadSymbol    = 0x0020,
    RxRunt        = 0x0010,
    RxTooLong    = 0x0008,
    RxCRCErr    = 0x0004,
    RxBadAlign    = 0x0002,
    RxStatusOK    = 0x0001,
};
</pre>
</div>
<h3>4.2 预-接收状态寄存器（ERSR）</h3>
<div class="ref-block">
<pre>Early Rx Status Register
(Offset 0036h, R, STA)
</pre>
</div>
<h3>4.3 接收配置寄存器（RCR）</h3>
<div class="ref-block">
<pre>Receive Configuration Register
(Offset 0044h-0047h, R/W, CFG)

//接收模式配置位
enum rx_mode_bits {
    AcceptErr    = 0x20,
    AcceptRunt    = 0x10,
    AcceptBroadcast    = 0x08,
    AcceptMulticast    = 0x04,
    AcceptMyPhys    = 0x02,
    AcceptAllPhys    = 0x01,
};
//FIFO阀值、DMA burst大小和环缓冲长度的［配置值］
enum RxConfigBits {
    /* rx fifo threshold */
    RxCfgFIFOShift    = 13,
    RxCfgFIFONone    = (7 &lt;&lt; RxCfgFIFOShift),

    /* Max DMA burst */
    RxCfgDMAShift    = 8,
    RxCfgDMAUnlimited = (7 &lt;&lt; RxCfgDMAShift),

    /* rx ring buffer length */
    RxCfgRcv8K    = 0,
    RxCfgRcv16K    = (1 &lt;&lt; 11),
    RxCfgRcv32K    = (1 &lt;&lt; 12),
    RxCfgRcv64K    = (1 &lt;&lt; 11) | (1 &lt;&lt; 12),

    /* Disable packet wrap at end of Rx buffer. (not possible with 64k) */
    RxNoWrap    = (1 &lt;&lt; 7),
};
</pre>
</div>
<h2>5.发包过程</h2>
<p>此过程假设已经配置好发包缓冲区。数据包的发送过程：</p>
<ol>
<li>驱动程序将数据包拷到四块缓冲区中空闲的一块上；</li>
<li>驱动程序写入TSD，包括数据包大小、发送阈值（early transmit threshold）和PCI操作命令（清除OWN位）；</li>
<li>RTL8139通过PCI读取数据，在读取过程中，如果RTL8139检测到FIFO达到了阈值，或者发现已经读取了整个数据包，RTL8139开始将FIFO上的数据发到网线上；</li>
<li>当RTL8139将缓冲区上的数据包整个拷进了FIFO后，RTL8139置OWN为1；</li>
<li>当RTL8139将整个数据包发到网线上后，RTL8139置ISR.TOK为1，发出“发送完成中断”，如果IMR.TOK 是1，中断请求才成功发出；</li>
<li>驱动程序的中断处理函数被调用，处理函数过程中必须清除ISR.TOK。</li>
</ol>
<p>发送过程的状态转换图 (ISR.TOK,ISR.OWN)：</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/09/tx_state_diagram.jpg"><img src="http://arttech.us/wp-content/uploads/2011/09/tx_state_diagram.jpg" alt="" title="tx_state_diagram" width="579" height="248" class="aligncenter size-full wp-image-534" /></a></p>
<h2>6.收包过程</h2>
<p>此过程假设已经配置好收包缓冲区。数据包的接收过程：</p>
<ol>
<li>RTL8139启动后开始监测网线上数据；</li>
<li>如果有数据包，外部数据包首先通过网线进入芯片内部的 Receive FIFO；</li>
<li>当数据量达到预设的阀值，RTL8139将数据通过PCI拷到环形缓冲区；</li>
<li>当整个数据包都拷缓冲区后，RTL8139会在数据包的前面写数据头（包括接收状态和包长度信息），然后更新缓冲区的写指针CBA；</li>
<li>RTL8139置CR.BUFE为1，收包缓冲不为空；然后置ISR.ROK，发出收包中断；</li>
<li>RTL8139计算收包缓冲大小（比对CAPR和CBA），如果太小（如&lt;3K），对外发出暂停控制帧，否则回到第一步；</li>
<li>驱动程序的中断处理函数被调用，处理收包，完了后清除ISR.ROK，还有更新CAPR。</li>
</ol>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/rtl8139-rx-tx.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>编写Linux网络设备驱动（上）</title>
		<link>http://arttech.us/y-2011/writing-network-device-driver-a.html</link>
		<comments>http://arttech.us/y-2011/writing-network-device-driver-a.html#comments</comments>
		<pubDate>Thu, 01 Sep 2011 16:59:37 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[驱动程序]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=518</guid>
		<description><![CDATA[译自：《Writing Network Device Drivers for Linux》 原作： Mohan Lal Jangir 刘建文略译 本文介绍基于Realtek 8139芯片PCI接口的网卡驱动程序。我选择了Realtek芯片有两个原因：首先，Realtek提供免费的芯片技术手册； 第二，芯片相当便宜。 本文介绍的驱动程序是最基本的，它只有发送和接收数据包功能，和做一些简单的统计。对于一个全面和专业级的驱动程序，请参阅Linux源码。 本文代码是基于Linux2.4.18上测试的，建议编译一个内核，此内核没有任何形式RealTek8139驱动程序，以避免有莫名的BUG。最后，你将网卡插入PCI插槽，我们可以开始了。 目录 网络设备驱动程序的开发，分解成以下步骤： 上： 1.检测设备 2.启用设备 3.认识网络设备 4.总线无关的设备访问 5.理解PCI配置空间 6.初始化网络设备（net_device） 中： 7.RTL8139收发原理 下： 8.编写网络设备的发包功能 9.编写网络设备的收包功能 1.设备检测 第一步，我们需要检测的网卡设备。 Linux内核提供了丰富的API检测PCI总线上的设备，我们这只用其中最简单的一个API——​​pci_find_device。 #define REALTEK_VENDER_ID 0x10EC #define REALTEK_DEVICE_ID 0x8139 #include &#60;linux/kernel.h&#62; #include &#60;linux/module.h&#62; #include &#60;linux/stddef.h&#62; #include &#60;linux/pci.h&#62; int init_module(void) { struct pci_dev *pdev; pdev = [...]]]></description>
			<content:encoded><![CDATA[<div class="postmeta">
<dl>
<dd>
<div class="summary">
<ul>
<li>译自：《Writing Network Device Drivers for Linux》</li>
<li>原作： Mohan Lal Jangir</li>
<li>刘建文略译</li>
</ul>
</div>
</dd>
</dl>
</div>
<p>本文介绍基于Realtek 8139芯片PCI接口的网卡驱动程序。我选择了Realtek芯片有两个原因：首先，Realtek提供免费的芯片技术手册； 第二，芯片相当便宜。</p>
<p>本文介绍的驱动程序是最基本的，它只有发送和接收数据包功能，和做一些简单的统计。对于一个全面和专业级的驱动程序，请参阅Linux源码。</p>
<p>本文代码是基于Linux2.4.18上测试的，建议编译一个内核，此内核没有任何形式RealTek8139驱动程序，以避免有莫名的BUG。最后，你将网卡插入PCI插槽，我们可以开始了。<span id="more-518"></span></p>
<h1>目录</h1>
<p>网络设备驱动程序的开发，分解成以下步骤：</p>
<p>上：</p>
<ul>
<li>1.检测设备</li>
<li>2.启用设备</li>
<li>3.认识网络设备</li>
<li>4.总线无关的设备访问</li>
<li>5.理解PCI配置空间</li>
<li>6.初始化网络设备（net_device）</li>
</ul>
<p>中：</p>
<ul>
<li>7.RTL8139收发原理</li>
</ul>
<p>下：</p>
<ul>
<li>8.编写网络设备的发包功能</li>
<li>9.编写网络设备的收包功能</li>
</ul>
<h2>1.设备检测</h2>
<p>第一步，我们需要检测的网卡设备。 Linux内核提供了丰富的API检测PCI总线上的设备，我们这只用其中最简单的一个API——​​pci_find_device。</p>
<div class="codeblock">
<pre>
<font color="0000ff"><strong>#define REALTEK_VENDER_ID  0x10EC</strong></font>
<font color="0000ff"><strong>#define REALTEK_DEVICE_ID   0x8139</strong></font>

<font color="0000ff"><strong>#include <font color="#008000">&lt;linux/kernel.h&gt;</font></strong></font>
<font color="0000ff"><strong>#include <font color="#008000">&lt;linux/module.h&gt;</font></strong></font>
<font color="0000ff"><strong>#include <font color="#008000">&lt;linux/stddef.h&gt;</font></strong></font>
<font color="0000ff"><strong>#include <font color="#008000">&lt;linux/pci.h&gt;</font></strong></font>
<strong>int</strong> <font color="#2040a0">init_module</font><font color="4444FF">(</font><strong>void</strong><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">pci_dev</font> <font color="4444FF">*</font><font color="#2040a0">pdev</font><font color="4444FF">;</font>
    <font color="#2040a0">pdev</font> <font color="4444FF">=</font> <font color="#2040a0">pci_find_device</font><font color="4444FF">(</font><font color="#2040a0">REALTEK_VENDER_ID</font>, <font color="#2040a0">REALTEK_DEVICE_ID</font>, <font color="#2040a0">NULL</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>if</strong><font color="4444FF">(</font><font color="4444FF">!</font><font color="#2040a0">pdev</font><font color="4444FF">)</font>
        <font color="#2040a0">printk</font><font color="4444FF">(</font><font color="#008000">&quot;&lt;1&gt;Device not found</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>else</strong>
        <font color="#2040a0">printk</font><font color="4444FF">(</font><font color="#008000">&quot;&lt;1&gt;Device found</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>Table 1: Detecting the device</p>
<p>PCI标准为每个供应商分配一个唯一的Vendor ID，供应商会为每一个特定类型的设备分配一个唯一的Device ID。宏REALTEK_VENDER_ID、REALTEK_DEVICE_ID表示这些ID。你可以在RealTek8139规范的“PCI配置空间表”找到这些值。</p>
<h2>2.设备启用（Enabling）</h2>
<p>检测到设备后，我们使用设备之前，我必须先激活设备，这个步骤称为［启用设备］。表2所示的代码片段是［设备检测］和［设备启用］合并的代码。</p>
<div><span id="stateBut" style="font-size:35px;cursor:hand" onclick="do_folding(this,'cb1')">∨</span>Table 2: Detecting and Enabling the Device</div>
<div class="codeblock" id="cb1">
<pre>
<font color="0000ff"><strong>#define REALTEK_VENDER_ID  0x10EC</strong></font>
<font color="0000ff"><strong>#define REALTEK_DEVICE_ID  0X8139</strong></font>

<strong>static</strong> <strong>struct</strong> <font color="#2040a0">pci_dev</font><font color="4444FF">*</font> <font color="#2040a0">probe_for_realtek8139</font><font color="4444FF">(</font><strong>void</strong><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">pci_dev</font> <font color="4444FF">*</font><font color="#2040a0">pdev</font> <font color="4444FF">=</font> <font color="#2040a0">NULL</font><font color="4444FF">;</font>
    <font color="#444444">/* Ensure we are not working on a non-PCI system *
    if(!pci_present( )) {
        LOG_MSG(&quot;&lt;1&gt;pci not present\n&quot;);
        return pdev;
    }

    /* Look for RealTek 8139 NIC */</font>
    <font color="#2040a0">pdev</font> <font color="4444FF">=</font> <font color="#2040a0">pci_find_device</font><font color="4444FF">(</font><font color="#2040a0">REALTEK_VENDER_ID</font>, <font color="#2040a0">REALTEK_DEVICE_ID</font>, <font color="#2040a0">NULL</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">pdev</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
        <font color="#444444">/* device found, enable it */</font>
        <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">pci_enable_device</font><font color="4444FF">(</font><font color="#2040a0">pdev</font><font color="4444FF">)</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
            <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Could not enable the device</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
            <strong>return</strong> <font color="#2040a0">NULL</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>
    <strong>else</strong>
        <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Device enabled</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>
    <strong>else</strong> <font color="4444FF"><strong>{</strong></font>
        <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;device not found</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
        <strong>return</strong> <font color="#2040a0">pdev</font><font color="4444FF">;</font>
    <font color="4444FF"><strong>}</strong></font>
    <strong>return</strong> <font color="#2040a0">pdev</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>

<strong>int</strong> <font color="#2040a0">init_module</font><font color="4444FF">(</font><strong>void</strong><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">pci_dev</font> <font color="4444FF">*</font><font color="#2040a0">pdev</font><font color="4444FF">;</font>
    <font color="#2040a0">pdev</font> <font color="4444FF">=</font> <font color="#2040a0">probe_for_realtek8139</font><font color="4444FF">(</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>if</strong><font color="4444FF">(</font><font color="4444FF">!</font><font color="#2040a0">pdev</font><font color="4444FF">)</font>
        <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>

    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>在表2，函数probe_for_realtek8139执行以下任务：</p>
<ul>
<li>确保系统支持PCI总线</li>
<li>检测Realtek8139设备</li>
<li>如果发现设备，则启用的设备（通过调用pci_enable_device）</li>
</ul>
<p>现在，为了更好地理解代码，我们先暂停一下驱动程序代码的研究，转而看一下Linux内核是怎样［处理］设备和设备驱动的。我们将着眼于［网络设备的定义］，内存映射I/O和独立端口I/O之间的差异，还有PCI配置空间的概念。</p>
<h2>3.理解何为网络设备</h2>
<p>我们是检测到了PCI设备，并启用它，但它只是一支硬件设备（网卡设备），而Linux的网络协议栈只认得［网络设备］。［网络设备］是一支逻辑设备，由结构net_device表征。也就是说，网络协议栈向［网络设备］发出命令，而［网络设备］的驱动将这些命令传递到PCI［网卡设备］。表3列出了结构net_device的一些重要数据域，这将在本文稍后使用。</p>
<div class="codeblock">
<pre>
<strong>struct</strong> <font color="#2040a0">net_device</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>char</strong> <font color="4444FF">*</font><font color="#2040a0">name</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>long</strong> <font color="#2040a0">base_addr</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="#2040a0">addr_len</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="#2040a0">dev_addr</font><font color="4444FF">[</font><font color="#2040a0">MAX_ADDR_LEN</font><font color="4444FF">]</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="#2040a0">broadcast</font><font color="4444FF">[</font><font color="#2040a0">MAX_ADDR_LEN</font><font color="4444FF">]</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>short</strong> <font color="#2040a0">hard_header_len</font><font color="4444FF">;</font>
    <strong>unsigned</strong> <strong>char</strong> <font color="#2040a0">irq</font><font color="4444FF">;</font>
    <strong>int</strong> <font color="4444FF">(</font><font color="4444FF">*</font><font color="#2040a0">open</font><font color="4444FF">)</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>int</strong> <font color="4444FF">(</font><font color="4444FF">*</font><font color="#2040a0">stop</font><font color="4444FF">)</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>int</strong> <font color="4444FF">(</font><font color="4444FF">*</font><font color="#2040a0">hard_start_xmit</font><font color="4444FF">)</font> <font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">sk_buff</font> <font color="4444FF">*</font><font color="#2040a0">skb</font>,  <strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>struct</strong> <font color="#2040a0">net_device_stats</font><font color="4444FF">*</font> <font color="4444FF">(</font><font color="4444FF">*</font><font color="#2040a0">get_stats</font><font color="4444FF">)</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">priv</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font><font color="4444FF">;</font>
</pre>
</div>
<p>Table 3: Structure net_device</p>
<p>上表只列出C结构net_device部分成员，不过，对于我们最小驱动程序，这些成员已经足够。以下简介这些成员的用途：</p>
<ul>
<li>name &#8211; 设备的名称。如果名称的第一个字符是null，那么register_netdev分配给它取名为“ethN”，其中N是合适的数字。例如，如果您的系统已经有eth0和eth1，您的设备将被命名的eth2。</li>
<li>base_addr &#8211; I/O基地址。 I/O地址在本文后面，我们将更深入的讨论。</li>
<li>addr_len &#8211; 硬件地址（MAC地址）的长度。以太网接口地址长度为6字节。</li>
<li>dev_addr &#8211; 硬件地址（以太网地址或MAC地址）</li>
<li>broadcast &#8211; 设备的广播地址。以太网接口的广播地址是FF:FF:FF:FF:FF:FF</li>
<li>hard_header_len &#8211; “硬件头的长度”是数据包硬件头的八位位组（octets）的数量。 以太网接口的hard_header_len的值是14</li>
<li>IRQ &#8211; 分配的中断号</li>
<li>open &#8211; 这是打开设备函数的指针。这个函数在用ifconfig命令激活设备时被调用，例如“ifconfig eth0 up”。 open函数负责向系统申请所需的系统资源需求（I/O端口，IRQ，DMA等），启用硬件和递增模块的使用计数。</li>
<li>stop &#8211; 这是停止设备函数的指针。这个函数在用ifconfig命令停用设备时被调用，例如“ifconfig eth0 down”。 stop函数释放所有open函数获得的资源。</li>
<li>hard_start_xmit &#8211; 此函数在传输线路上发送一个给定的数据包。该函数的第一个参数是指向结构sk_buff指针。结构sk_buff的是通过Linux网络协议栈的数据包。本文并不需要详细了解有关的sk_buff的结构的细节，你可以在下网址获得更多的结构sk_buff的信息：<a href="http://www.tldp.org/LDP/khg/HyperNews/get/net/net-intro.html" target="_blank">http://www.tldp.org/LDP/khg/HyperNews/get/net/net-intro.html</a>。</li>
<li>get_stats &#8211; 此函数提供了接口统计信息。命令“ifconfig eth0”的很多输出内容来自get_stats。</li>
<li>priv &#8211; 驱动程序的私有数据域。驱动程序拥有这一数据域，并可以使用它。我们稍后会看到，我们的驱动程序使用这一数据域保存与PCI设备相关的数据。</li>
</ul>
<p>请特别注意，net_device没有接收数据包的成员函数，这是因为接收数据包是由设备的［中断处理程序］负责的，我们将在本文后面看到。</p>
<h3>4.总线无关的设备访问（Bus-Independent）</h3>
<p class="comment">注：本小节摘自Alan Cox的《Bus-Independent Device Accesses》<a href="http://tali.admingilde.org/linux-docbook/deviceiobook.pdf" target="_blank">http://tali.admingilde.org/linux-docbook/deviceiobook.pdf</a>
</p>
<p>Linux提供了一个API集（下文称为［设备操作API］），抽象所有总线和设备的I/O操作，使设备驱动程序的编写独立于总线类型。</p>
<h3>4.1 内存映射的I/O</h3>
<p>最广泛支持的I/O的操作是［内存映射I/O］。［内存映射I/O］是指，部分的CPU地址空间被解释为访问设备，而不是访问内存。一些体系结构为［内存映射I/O］的设备定义了固定的地址，但大多数体系提供了检测设备地址的方法。 PCI总线是很好的例子。本文不教你如何获得一个设备地址，假设你已经知道设备地址。</p>
<p>物理地址是unsigned long类型，你不能直接使用这些地址。你应该调用ioremap，来获得一个适合（传递给下面函数）的虚拟地址。当你使用完的设备（比如模块卸载），必须调用iounmap以返还虚拟地址给内核。</p>
<h3>4.2 访问设备</h3>
<p>在Linux提供［设备操作API］中，驱动程序最常用的接口是访问的设备寄存器的读和写函数。 Linux提供了读取和写入8位，16位，32位和64位量的函数，分别为 byte, word, long, 和 quad，函数命名readb，readw，readl，readq，writeb，writew，writel和writeq。</p>
<p>有些设备（如帧缓冲）更倾向一次内发起超过8个字节的传输。对于这些设备，可使用memcpy_toio，memcpy_fromio和memset_io功能。不要使用memset或memcpy对I/O地址操作，因为它们不能保证［按顺序］复制数据。</p>
<p>［设备操作API］中的读写函数是假设严格［按照源码字面顺序］执行的，编译器不能对它进行乱序优化。如果希望设备读写有一定的优化，可使用原始的__readb函数（等原始无抽象的函数）。 但是要非常小心，要在适当的地方插入内存屏障指令——rmb()/wmb()。</p>
<h3>4.3 独立端口IO</h3>
<p>另外一种常用的IO操作是［独立端口IO］。端口IO的地址是独立于内存地址空间的，端口IO的访问速度不如内存映射I/O，地址空间也小很多。不过，不像内存映射I/O，访问端口IO的设备相对直观，不需要考虑以上提到的一些问题。</p>
<p>［设备操作API］中提供了访问端口IO的函数，分别操作字节（byte）、双字（word）和四字（long）：inb, inw, inl, outb, outw 和 outl。</p>
<p>以上函数还有提供给慢速设备的变种：后加一“_p”；还有类似memcpy功能的ins 和 outs。</p>
<h2>5.理解PCI配置空间</h2>
<p>RTL8139是一支PCI接口设备，PCI是一种通用的扩展总线，而非与CPU体系相关的本地总线（local bus），从而CPU不能直接对RTL8139寻址访问，必须经PCI总线控制器转译。PCI总线设计实现的核心是PCI总线控制器（有的地方译为PIC主桥，PCI Host Bridge），它将整个系统划分两个数字通信域，两个域独立编址。一个是原来CPU与内存和设备通信的［CPU域］，一个是PCI总线控制器的（因它本身就是一CPU），这里称为［PCI总线域］。为了跨越两域通信，系统将CPU域划出一个“window”——将CPU部分寻址空间划给PCI总线用。PCI总线控制器对这部分地址进行管理，实现即插即用等一些现代总线功能。而所谓的［配置空间］只是配合PCI总线控制器实现地址管理提供必要的状态信息[注]。</p>
<p class="comment">注：个人觉得［配置空间］用“空间”一词欠佳，容易混淆其它地址空间概念，增加理解PCI总线原理的难度。</p>
<p>［配置空间］是每支PCI设备（包括PCI桥）集成一集寄存器，［配置空间］是面向PCI总线控制器而言的，此空间的基地址是PCI设备的拓扑位置（总线号/设备号/功能号）。PCI定义每支PCI设备的［配置空间］为256字节，如下图，其中最前面的64个字节已由标准定义，余下的空间由设备自定义。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/09/rtl8139-driver-pci-config-space.jpg"><img src="http://arttech.us/wp-content/uploads/2011/09/rtl8139-driver-pci-config-space.jpg" alt="" title="rtl8139-driver-pci-config-space" width="516" height="292" class="aligncenter size-full wp-image-520" /></a></p>
<p>围绕［配置空间］有两种事务和三种操作角色，事务是［配置］和［使用配置］，角色有静态配置的厂商和动态配置的操作系统，还有使用配置的设备驱动。静态配置的例子，如厂商在设备生产时配置其Vendor ID和Device ID；动态配置的例子，如操作系统初始化代码根据PCI设备的拓扑位置，配置设备的基地址（Base Address0~5）[注]。</p>
<p class="comment">注：这个地址属于PCI总线域的地址，而不是CPU域的地址。</p>
<p>使用配置的例子，如设备驱动的初始接口函数读取基地址寄存器（Base Address Registers），确定设备接口的基地址，下面的RTL8139设备初始化时你可以看到具体例子。</p>
<h2>6.初始化net_device</h2>
<p>现在我们回到驱动程序代码的开发上来。刚才我们已经讨论了设备驱动模块初始化中的设备检测和启用的任务，还有网络设备的表征结构，接下来我们先看看逻辑设备的初始化任务。</p>
<h3>6.1 rtl8139_private</h3>
<p>首先，作为一支特殊的网络设备，除了有标准的net_device表征，8139有其特殊数据，这是由C结构rtl8139_private 表征，由net_device-&gt;priv指向。rtl8139_private的定义如下：</p>
<div class="codeblock">
<pre>
<strong>struct</strong> <font color="#2040a0">rtl8139_private</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">pci_dev</font> <font color="4444FF">*</font><font color="#2040a0">pci_dev</font><font color="4444FF">;</font>  <font color="#444444">/* PCI device */</font>
    <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">mmio_addr</font><font color="4444FF">;</font>     <font color="#444444">/* memory mapped I/O addr */</font>
    <strong>unsigned</strong> <strong>long</strong> <font color="#2040a0">regs_len</font><font color="4444FF">;</font> <font color="#444444">/* length of I/O or MMI/O region */</font>
<font color="4444FF"><strong>}</strong></font><font color="4444FF">;</font>
</pre>
</div>
<p>Table 4: rtl8139_private structure</p>
<h3>6.2 init_module</h3>
<p>现在我们扩展init_module 函数，添加逻辑设备的初始化的任务。先看代码：</p>
<div class="codeblock">
<pre>
<strong>int</strong> <font color="#2040a0">init_module</font><font color="4444FF">(</font><strong>void</strong><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
        <strong>struct</strong> <font color="#2040a0">pci_dev</font> <font color="4444FF">*</font><font color="#2040a0">pdev</font><font color="4444FF">;</font>

        <strong>unsigned</strong> <strong>long</strong> <font color="#2040a0">mmio_start</font>, <font color="#2040a0">mmio_end</font>, <font color="#2040a0">mmio_len</font>, <font color="#2040a0">mmio_flags</font><font color="4444FF">;</font>
        <strong>void</strong> <font color="4444FF">*</font><font color="#2040a0">ioaddr</font><font color="4444FF">;</font>

        <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font><font color="4444FF">;</font>
        <strong>int</strong> <font color="#2040a0">i</font><font color="4444FF">;</font>

        <font color="#2040a0">pdev</font> <font color="4444FF">=</font> <font color="#2040a0">probe_for_realtek8139</font><font color="4444FF">(</font> <font color="4444FF">)</font><font color="4444FF">;</font>

        <strong>if</strong><font color="4444FF">(</font><font color="4444FF">!</font><font color="#2040a0">pdev</font><font color="4444FF">)</font>
               <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>

        <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">rtl8139_init</font><font color="4444FF">(</font><font color="#2040a0">pdev</font>, <font color="4444FF">&amp;</font><font color="#2040a0">rtl8139_dev</font><font color="4444FF">)</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>

               <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Could not initialize device</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
               <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>

        <font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font> <font color="#444444">/* rtl8139 private information */</font>
</pre>
</div>
<p>首先probe_for_realtek8139函数检测和启用设备后返回一个PCI设备——pdev，然后rtl8139_init用pdev初始化rtl8139_private，转而初始化网络设备rtl8139_dev。</p>
<p>我们下一个目标是得到（初始化）设备的基地址——net_device的base_addr域。这是设备寄存器的内存映射的起始地址。本设备驱动程序只使用内存映射IO。</p>
<div class="codeblock">
<pre>

        <font color="#444444">/* get PCI memory mapped I/O space base address from BAR1 */</font>
        <font color="#2040a0">mmio_start</font> <font color="4444FF">=</font> <font color="#2040a0">pci_resource_start</font><font color="4444FF">(</font><font color="#2040a0">pdev</font>, <font color="#FF0000">1</font><font color="4444FF">)</font><font color="4444FF">;</font>

        <font color="#2040a0">mmio_end</font> <font color="4444FF">=</font> <font color="#2040a0">pci_resource_end</font><font color="4444FF">(</font><font color="#2040a0">pdev</font>, <font color="#FF0000">1</font><font color="4444FF">)</font><font color="4444FF">;</font>
        <font color="#2040a0">mmio_len</font> <font color="4444FF">=</font> <font color="#2040a0">pci_resource_len</font><font color="4444FF">(</font><font color="#2040a0">pdev</font>, <font color="#FF0000">1</font><font color="4444FF">)</font><font color="4444FF">;</font>

        <font color="#2040a0">mmio_flags</font> <font color="4444FF">=</font> <font color="#2040a0">pci_resource_flags</font><font color="4444FF">(</font><font color="#2040a0">pdev</font>, <font color="#FF0000">1</font><font color="4444FF">)</font><font color="4444FF">;</font>

        <font color="#444444">/* make sure above region is MMI/O */</font>

        <strong>if</strong><font color="4444FF">(</font><font color="4444FF">!</font><font color="4444FF">(</font><font color="#2040a0">mmio_flags</font> <font color="4444FF">&amp;</font> <font color="#2040a0">I</font>/<font color="#2040a0">ORESOURCE_MEM</font><font color="4444FF">)</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
               <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;region not MMI/O region</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>

               <strong>goto</strong> <font color="#2040a0">cleanup1</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>

        <font color="#444444">/* get PCI memory space */</font>
        <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">pci_request_regions</font><font color="4444FF">(</font><font color="#2040a0">pdev</font>, <font color="#2040a0">DRIVER</font><font color="4444FF">)</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>

               <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Could not get PCI region</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
               <strong>goto</strong> <font color="#2040a0">cleanup1</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>

        <font color="#2040a0">pci_set_master</font><font color="4444FF">(</font><font color="#2040a0">pdev</font><font color="4444FF">)</font><font color="4444FF">;</font>
</pre>
</div>
<p>为了取得基地址，我们利用了内核PCI总线子系统提供的API：pci_resource_start, pci_resource_end, pci_resource_len, pci_resource_flags。注意这些API函数的第二个参数——BAR号1。PCI规定PCI设备最多可以申请6个PCI总线地址区，这些空间区的基地址分别保存在6个BAR里。在RealTek8139手册定义里，RTL只申请了两个区，第一个BAR（编号为0）是I/OAR，第二个 BAR（编号为1）是MEMAR。由于本设备驱动程序只使用内存映射IO，故BAR选用1。</p>
<p>现在，在使用这些地址之前，我们还有两件事要做。</p>
<div class="codeblock">
<pre>

        <font color="#444444">/* ioremap MMI/O region */</font>
        <font color="#2040a0">ioaddr</font> <font color="4444FF">=</font> <font color="#2040a0">ioremap</font><font color="4444FF">(</font><font color="#2040a0">mmio_start</font>, <font color="#2040a0">mmio_len</font><font color="4444FF">)</font><font color="4444FF">;</font>

        <strong>if</strong><font color="4444FF">(</font><font color="4444FF">!</font><font color="#2040a0">ioaddr</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
               <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Could not ioremap</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>

               <strong>goto</strong> <font color="#2040a0">cleanup2</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>

        <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">base_addr</font> <font color="4444FF">=</font> <font color="4444FF">(</font><strong>long</strong><font color="4444FF">)</font><font color="#2040a0">ioaddr</font><font color="4444FF">;</font>

        <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">mmio_addr</font> <font color="4444FF">=</font> <font color="#2040a0">ioaddr</font><font color="4444FF">;</font>
        <font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">regs_len</font> <font color="4444FF">=</font> <font color="#2040a0">mmio_len</font><font color="4444FF">;</font>
</pre>
</div>
<p>这两个事就是，第一，为设备驱动保留这些地址（调用pci_request_regions函数），以免被误用；第二，将这些物理地址重映射（remap）；这在前面“内存映射的I/O”小节已经提到，驱动代码不能用直接使用物理地址。重映射后的地址io_addr填入 net_device的base_addr域后，我们可以读定设备的寄存器了。</p>
<p>剩下的代码比较直观和易理解了。</p>
<div class="codeblock">
<pre>
        <font color="#444444">/* UPDATE NET_DEVICE */</font>

        <strong>for</strong><font color="4444FF">(</font><font color="#2040a0">i</font> <font color="4444FF">=</font> <font color="#FF0000">0</font><font color="4444FF">;</font> <font color="#2040a0">i</font> <font color="4444FF">&lt;</font> <font color="#FF0000">6</font><font color="4444FF">;</font> <font color="#2040a0">i</font><font color="4444FF">+</font><font color="4444FF">+</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>  <font color="#444444">/* Hardware Address */</font>

               <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">dev_addr</font><font color="4444FF">[</font><font color="#2040a0">i</font><font color="4444FF">]</font> <font color="4444FF">=</font> <font color="#2040a0">readb</font><font color="4444FF">(</font><font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">base_addr</font><font color="4444FF">+</font><font color="#2040a0">i</font><font color="4444FF">)</font><font color="4444FF">;</font>

               <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">broadcast</font><font color="4444FF">[</font><font color="#2040a0">i</font><font color="4444FF">]</font> <font color="4444FF">=</font> <font color="#FF0000">0xff</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>
        <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">hard_header_len</font> <font color="4444FF">=</font> <font color="#FF0000">14</font><font color="4444FF">;</font>

        <font color="#2040a0">memcpy</font><font color="4444FF">(</font><font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">name</font>, <font color="#2040a0">DRIVER</font>, <strong>sizeof</strong><font color="4444FF">(</font><font color="#2040a0">DRIVER</font><font color="4444FF">)</font><font color="4444FF">)</font><font color="4444FF">;</font> <font color="#444444">/* Device Name */</font>

        <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">irq</font> <font color="4444FF">=</font> <font color="#2040a0">pdev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">irq</font><font color="4444FF">;</font>  <font color="#444444">/* Interrupt Number */</font>
        <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">open</font> <font color="4444FF">=</font> <font color="#2040a0">rtl8139_open</font><font color="4444FF">;</font>

        <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">stop</font> <font color="4444FF">=</font> <font color="#2040a0">rtl8139_stop</font><font color="4444FF">;</font>
        <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">hard_start_xmit</font> <font color="4444FF">=</font> <font color="#2040a0">rtl8139_start_xmit</font><font color="4444FF">;</font>

        <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">get_stats</font> <font color="4444FF">=</font> <font color="#2040a0">rtl8139_get_stats</font><font color="4444FF">;</font>

        <font color="#444444">/* register the device */</font>
        <strong>if</strong><font color="4444FF">(</font><font color="#2040a0">register_netdev</font><font color="4444FF">(</font><font color="#2040a0">rtl8139_dev</font><font color="4444FF">)</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>

               <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;Could not register netdevice</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
               <strong>goto</strong> <font color="#2040a0">cleanup0</font><font color="4444FF">;</font>
        <font color="4444FF"><strong>}</strong></font>

        <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>我们用了一个for循环来读取设备的硬件地址和广播地址（注意这回是直接用内核［设备操作API］的readb，而不是 PCI总线系统API），设备的硬件地址位于基地址的最前面。另外值得注意的是几个网络设备的接口函数指针，如open,hard_start_xmit 等，它们指向还没有实现的函数。为了编译驱动模块并进行测试，到此暂时为这些接口函数写一些Dummy测试代码。</p>
<h3>6.3 逻辑设备的其它接口</h3>
<div class="codeblock">
<pre>
<strong>static</strong> <strong>int</strong> <font color="#2040a0">rtl8139_open</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font> <font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;rtl8139_open iscalled</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>

<strong>static</strong> <strong>int</strong> <font color="#2040a0">rtl8139_stop</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;rtl8139_open is called</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>

<strong>static</strong> <strong>int</strong> <font color="#2040a0">rtl8139_start_xmit</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">sk_buff</font> <font color="4444FF">*</font><font color="#2040a0">skb</font>, <strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;rtl8139_start_xmit is called</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>

<strong>static</strong> <strong>struct</strong> <font color="#2040a0">net_device_stats</font><font color="4444FF">*</font> <font color="#2040a0">rtl8139_get_stats</font><font color="4444FF">(</font><strong>struct</strong> <font color="#2040a0">net_device</font> <font color="4444FF">*</font><font color="#2040a0">dev</font><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <font color="#2040a0">LOG_MSG</font><font color="4444FF">(</font><font color="#008000">&quot;rtl8139_get_stats is called</font><font color="#77dd77">\n</font>&quot;<font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong> <font color="#FF0000">0</font><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>Table 6: Dummy functions</p>
<h3>6.4 注销函数</h3>
<p>最后是注销函数：</p>
<div class="codeblock">
<pre>
<strong>void</strong> <font color="#2040a0">cleanup_module</font><font color="4444FF">(</font><strong>void</strong><font color="4444FF">)</font>
<font color="4444FF"><strong>{</strong></font>
    <strong>struct</strong> <font color="#2040a0">rtl8139_private</font> <font color="4444FF">*</font><font color="#2040a0">tp</font><font color="4444FF">;</font>
    <font color="#2040a0">tp</font> <font color="4444FF">=</font> <font color="#2040a0">rtl8139_dev</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">priv</font><font color="4444FF">;</font>

    <font color="#2040a0">iounmap</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">mmio_addr</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="#2040a0">pci_release_regions</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">pci_dev</font><font color="4444FF">)</font><font color="4444FF">;</font>

    <font color="#2040a0">unregister_netdev</font><font color="4444FF">(</font><font color="#2040a0">rtl8139_dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <font color="#2040a0">pci_disable_device</font><font color="4444FF">(</font><font color="#2040a0">tp</font><font color="4444FF">-</font><font color="4444FF">&gt;</font><font color="#2040a0">pci_dev</font><font color="4444FF">)</font><font color="4444FF">;</font>
    <strong>return</strong><font color="4444FF">;</font>
<font color="4444FF"><strong>}</strong></font>
</pre>
</div>
<p>Table 7: Function cleanup_module</p>
<h3>6.5 编译测试</h3>
<p>到此，一支完整的8139网卡设备驱动基本完成了，当然目前还只是一个模板，没有实质性的功能。我们可以编译并安装它了。</p>
<div class="cmdconsole">
$ gcc -c rtl8139.c -D__KERNEL__  -DMODULE   -I /usr/src/linux-2.4.18/include<br />
$ insmod  rtl8139.o
</div>
<p>Table 8: Compiling the driver</p>
<p>安装不出问题的话，我们可以用SHELL命令进行测试：&#8221;ifconfig&#8221;, &#8220;ifconfig &#8211; a&#8221;, &#8220;ifconfig rtl8139 up&#8221;, &#8220;ifconfig&#8221; 和 &#8220;ifconfig rtl8139 down&#8221;。如无意外，&#8221;ifconfig &#8211; a&#8221; 会列出设备rtl8139；执行 &#8220;ifconfig rtl8139 up&#8221;会返回消息&#8221;function rtl8139_open called&#8221;等等……</p>
<p>好了，通过测试后，下一步是实现网络设备真正的数据收发了。为了更好理解实现代码，我们还是需要一些背景知识——理解RTL8139收发原理。</p>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/writing-network-device-driver-a.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>IP网络层与网络设备之间分组收发原理</title>
		<link>http://arttech.us/y-2011/ip-layer-interact-nic-layer.html</link>
		<comments>http://arttech.us/y-2011/ip-layer-interact-nic-layer.html#comments</comments>
		<pubDate>Sun, 28 Aug 2011 04:24:19 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[计算机网络]]></category>
		<category><![CDATA[驱动程序]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=509</guid>
		<description><![CDATA[1.引子 当我们为字符设备或块设备编写驱动程序时，我们实现逻辑设备的接口是文件读写接口——file_operations，这个接口基本上直接面向用户空间程序的。而我们要为网络设备编写驱动则不然了，用户空间程序是通过标准套接口（sockets）系统调用来使用网络功能的，用户空间程序与网络设备之间夹着一层TCP/IP协议栈程序。也就是说，当我们为网络设备编写驱动程序时，实现的设备接口是给TCP/IP协议栈“用”的。 事实上，现代操作系统的实现里，TCP/IP协议栈还不是直接“用”网络设备发送接收数据包的，因为为了实现更好网络性能、控制网络流量等，协议栈的第三层——IP网络层和第二层——网络设备层之间发生很多代码活动（activity）。如果不深入理解这两层之间的代码活动（数据流和控制流），那是很难理解和编写网络设备驱动的。下图概观了这些代码活动： 这些活动包括： 网卡的硬件中断将收到数据包的入列CPU的收包队列； 收包软中断（NET_RX_SOFTIRQ）将数据向上层传递或入列网卡的发包队列进行转发； 用户进程通过系统调用将数据发给协议栈，最后入列网卡的发包队列； 各种内核活动（softirq,tasklet,timer）将数据入网卡的发包队列； 发包软中断（NET_TX_SOFTIRQ）直接或间接（经队列调度算法）将数据包出列，通过网卡发走。 &#160; 2.术语 为了方便描述，有必要统一一下术语。 2.1 packet、包与分组 “packet”可以一般地译为数据包，也可以特别地译为网络帧，为了不产生歧义，这里在描述宏观原理时译为［网络分组］，简称为分组，特指分组交换网络（packet switching）原理中两个网络节点间传输的数据包格式；在描述微观原理时简单的使用一个字，包。如收包队列，发包过程。 什么是packet？ TCP/IP协议规定，在因特网传输的消息（message）必须打包成一个个叫分组（packets）的数据包进行传输，这些分组数据从一个主机到另一个主机，直到它来到它的目标主机为止。分组网络的主要原理是将每一次通信的消息分解为数个大小相同的分组包，这些分组包单独地被发到网络上，经过网络路由到达目标主机后再组装为原来的消息。由于分组的路由线路不是固定的，当网络部分出现故障时，分组仍然可通过可用的线路到达目标主机。 ［网络分组］的长度一般为1K，因而，比较大数据可能需要分解为数千个分组。每个分组都有一个“头”，包括以下一些信息： * Source. 源主机IP地址 * Destination. 目标主机IP地址 * Length. 分组长度，以字节为单位 * Number. 分组数，此分组所属的消息的长度 * Sequence. 分组序号，此分组在所属的消息的序号，目标主机或路由器在传送出错时可根据此序号要求原主机单独重发分组。 2.2 收包过程与发包过程 本文只关注二三层的数据交互，所以［收包过程］与［发包过程］指一层到三层或反方向止。 2.3 入列与出列 分别对［CPU的收包队列］和［网络设备的发包队列］的两个操作——enqueue和dequeue的中译。 3.收包过程 对内核（设备驱动）而言，IP网络分组（packet）的到来是没法预知的，因此网络设备驱动必须使用中断处理函数来接收IP网络分组。每当网卡设备收到一个分组时，它向内核发出中断请求，内核调用相应的中断处理函数，将网卡设备上的分组数据拷入RAM[注]。 注：在具体的实现上，例如PCI以太网卡，驱动程序会与网卡芯片［协作］将收到的分组数据保存在RAM中一个DMAble的缓冲区上，再转拷进协议栈可访问的socket buffer，DMAble缓冲区是在网络设备驱动初始化的分配的，可以理解为逻辑设备的一部分，所以上面的“网卡设备上的分组数据”在这个前提下成立。 目前版本的内核（的网络子系统）实现了两种［收包］算法。其中一种已经存在内核中很长一段时间了，我们称其为传统算法。传统算法的大致原理是，网络设备的分组的异步收与发统一使用中断方式，一次中断一个分组；CPU和网络设备之间使用［等待队列］缓解二者速度上的差异，并作相应的拥塞管理——网络设备的启停，防止队列溢出。 由于传统算法跟不上网络设备的发展，在高速网卡的应用场景里存在不足，主要的不足是过多的中断产生，让CPU不断重得做一件无效的事——掉包；于是内核开发者重新设计了新算法，新API——NAPI。NAPI引入最古老的设备访问方式——轮询（polling），也就是CPU通过定期检测设备状态来与设备通信。轮询在网络流量负载重的情况下是高效的，但在网络流量很少时，浪费CPU资源，于是NAPI结合轮询和中断的优点，自动地按网络流量选择适当的通信方式。由于传统比较容易理解，并且还有很多网络设备速度并不是很高，还在用旧算法，我们这里只讨论传统算法，NAPI的更多内容请参考文尾的文献。 下图大略展示了网络分组到达网卡后从驱动程序传到网络层的情况。从图可见，收包任务可划分两个部分： 第一，将分组入列收包队列的上半部——硬中断处理； 第二，将分组出列收包队列的下半部——软中断处理。 Figure 6-3. 内核收包发包路径图 由于收包的上半部是在中断上下文内完成，因此中断处理函数必须只执行收包的关键操作，以免因为执行一些不重要的操作影响系统正常行为[注]。 注：因为中断可打断系统上任何正在运行的进程，如果中断处理时间过长甚至被阻塞，那么被打断的进程（对用户而言）表现为性能低下，反应缓慢，甚至没有任何反应。另外中断处理进会关闭系统中断，如果中断处理时间过长可能丢失一些不可重现的中断信号。 3.1 [...]]]></description>
			<content:encoded><![CDATA[<h2>1.引子</h2>
<p>当我们为字符设备或块设备编写驱动程序时，我们实现逻辑设备的接口是文件读写接口——file_operations，这个接口基本上直接面向用户空间程序的。而我们要为网络设备编写驱动则不然了，用户空间程序是通过标准套接口（sockets）系统调用来使用网络功能的，用户空间程序与网络设备之间夹着一层TCP/IP协议栈程序。也就是说，当我们为网络设备编写驱动程序时，实现的设备接口是给TCP/IP协议栈“用”的。</p>
<p>事实上，现代操作系统的实现里，TCP/IP协议栈还不是直接“用”网络设备发送接收数据包的，因为为了实现更好网络性能、控制网络流量等，协议栈的第三层——IP网络层和第二层——网络设备层之间发生很多代码活动（activity）。如果不深入理解这两层之间的代码活动（数据流和控制流），那是很难理解和编写网络设备驱动的。<span id="more-509"></span>下图概观了这些代码活动：<br />
<a href="http://arttech.us/wp-content/uploads/2011/08/packet-rx-tx-activity.jpg"><img src="http://arttech.us/wp-content/uploads/2011/08/packet-rx-tx-activity.jpg" alt="" title="packet-rx-tx-activity" width="509" height="342" class="aligncenter size-full wp-image-512" /></a><br />
这些活动包括：</p>
<ol>
<li>网卡的硬件中断将收到数据包的入列CPU的收包队列；</li>
<li>收包软中断（NET_RX_SOFTIRQ）将数据向上层传递或入列网卡的发包队列进行转发；</li>
<li>用户进程通过系统调用将数据发给协议栈，最后入列网卡的发包队列；</li>
<li>各种内核活动（softirq,tasklet,timer）将数据入网卡的发包队列；</li>
<li>发包软中断（NET_TX_SOFTIRQ）直接或间接（经队列调度算法）将数据包出列，通过网卡发走。</li>
</ol>
<p>&nbsp;</p>
<h2>2.术语</h2>
<p>为了方便描述，有必要统一一下术语。</p>
<h3>2.1 packet、包与分组</h3>
<p>“packet”可以一般地译为数据包，也可以特别地译为网络帧，为了不产生歧义，这里在描述宏观原理时译为［网络分组］，简称为分组，特指分组交换网络（packet switching）原理中两个网络节点间传输的数据包格式；在描述微观原理时简单的使用一个字，包。如收包队列，发包过程。</p>
<blockquote>
<p>什么是packet？</p>
<p style="padding-left: 30px;">TCP/IP协议规定，在因特网传输的消息（message）必须打包成一个个叫分组（packets）的数据包进行传输，这些分组数据从一个主机到另一个主机，直到它来到它的目标主机为止。分组网络的主要原理是将每一次通信的消息分解为数个大小相同的分组包，这些分组包单独地被发到网络上，经过网络路由到达目标主机后再组装为原来的消息。由于分组的路由线路不是固定的，当网络部分出现故障时，分组仍然可通过可用的线路到达目标主机。</p>
<p style="padding-left: 30px;">［网络分组］的长度一般为1K，因而，比较大数据可能需要分解为数千个分组。每个分组都有一个“头”，包括以下一些信息：<br />
* Source. 源主机IP地址<br />
* Destination. 目标主机IP地址<br />
* Length. 分组长度，以字节为单位<br />
* Number. 分组数，此分组所属的消息的长度<br />
* Sequence. 分组序号，此分组在所属的消息的序号，目标主机或路由器在传送出错时可根据此序号要求原主机单独重发分组。</p>
</blockquote>
<h3>2.2 收包过程与发包过程</h3>
<p>本文只关注二三层的数据交互，所以［收包过程］与［发包过程］指一层到三层或反方向止。</p>
<h3>2.3 入列与出列</h3>
<p>分别对［CPU的收包队列］和［网络设备的发包队列］的两个操作——enqueue和dequeue的中译。</p>
<h2>3.收包过程</h2>
<p>对内核（设备驱动）而言，IP网络分组（packet）的到来是没法预知的，因此网络设备驱动必须使用中断处理函数来接收IP网络分组。每当网卡设备收到一个分组时，它向内核发出中断请求，内核调用相应的中断处理函数，将网卡设备上的分组数据拷入RAM[注]。</p>
<p class="comment">
注：在具体的实现上，例如PCI以太网卡，驱动程序会与网卡芯片［协作］将收到的分组数据保存在RAM中一个DMAble的缓冲区上，再转拷进协议栈可访问的socket buffer，DMAble缓冲区是在网络设备驱动初始化的分配的，可以理解为逻辑设备的一部分，所以上面的“网卡设备上的分组数据”在这个前提下成立。</p>
<p>目前版本的内核（的网络子系统）实现了两种［收包］算法。其中一种已经存在内核中很长一段时间了，我们称其为传统算法。传统算法的大致原理是，网络设备的分组的异步收与发统一使用中断方式，一次中断一个分组；CPU和网络设备之间使用［等待队列］缓解二者速度上的差异，并作相应的拥塞管理——网络设备的启停，防止队列溢出。</p>
<p>由于传统算法跟不上网络设备的发展，在高速网卡的应用场景里存在不足，主要的不足是过多的中断产生，让CPU不断重得做一件无效的事——掉包；于是内核开发者重新设计了新算法，新API——NAPI。NAPI引入最古老的设备访问方式——轮询（polling），也就是CPU通过定期检测设备状态来与设备通信。轮询在网络流量负载重的情况下是高效的，但在网络流量很少时，浪费CPU资源，于是NAPI结合轮询和中断的优点，自动地按网络流量选择适当的通信方式。由于传统比较容易理解，并且还有很多网络设备速度并不是很高，还在用旧算法，我们这里只讨论传统算法，NAPI的更多内容请参考文尾的文献。</p>
<p>下图大略展示了网络分组到达网卡后从驱动程序传到网络层的情况。从图可见，收包任务可划分两个部分：</p>
<p>第一，将分组入列收包队列的上半部——硬中断处理；</p>
<p>第二，将分组出列收包队列的下半部——软中断处理。<br />
<a href="http://arttech.us/wp-content/uploads/2011/08/path-of-packet-bw-L2-L3.jpg"><img src="http://arttech.us/wp-content/uploads/2011/08/path-of-packet-bw-L2-L3.jpg" alt="" title="path-of-packet-bw-L2-L3" width="643" height="485" class="aligncenter size-full wp-image-514" /></a><br />
Figure 6-3. 内核收包发包路径图</p>
<p>由于收包的上半部是在中断上下文内完成，因此中断处理函数必须只执行收包的关键操作，以免因为执行一些不重要的操作影响系统正常行为[注]。</p>
<p class="comment">
注：因为中断可打断系统上任何正在运行的进程，如果中断处理时间过长甚至被阻塞，那么被打断的进程（对用户而言）表现为性能低下，反应缓慢，甚至没有任何反应。另外中断处理进会关闭系统中断，如果中断处理时间过长可能丢失一些不可重现的中断信号。</p>
<h3>3.1 硬中断处理</h3>
<p>在收包的硬中断处理里，有三个短小的函数完成以下任务：</p>
<p>1. net_interrupt函数是驱动注册的中断处理入口函数，它判断中断信号是不是真的由收包事件产生的（因为中断信号有可能是错误处理中断，或者发包完成中断），如果是，调用net_rx；</p>
<p>2. net_rx函数是网卡特定相关的，它首先创建一个新socket buffer（dev_alloc_skb()），然后将网卡设备上的分组数据拷入这个socket buffer。接着驱动开始调用内核的API（eth_type_trans()）分析分组头部，用以判断分组使用的网络层协议类型，例如IP。最后把创建好的socket buffer（地址）传给netif_rx；</p>
<p>3. netif_rx是网络设备与网络层协议之间的通路，与上面两个函数不同， netif_rx是独立于网络设备驱动的函数（实现在net/core/dev.c），它的主要任务是做一些记录和统计，例如记录分组的时间截更新收包总数；然后判断当前CPU的收包队列是否已满，决定是将分组入列［收包队列］，还是丢掉；如果分组入列，入列后向内核发出软中断——NET_RX_SOFTIRQ，然后退出中断上下文。</p>
<h3>3.2 softnet_data</h3>
<p>为了让系统有更灵活的性能，内核用［等待队列］来管理系统的网络分组的接收和发送。内核定义了全局数组来实现［等待队列］——名为softnet_data，元素类型为softnet_data。为了进一步提升在多处理器系统的性能，内核为每个CPU单独创建专用的［等待队列］，实现并行处理分组的收发。没必要对等待队列进行显式的并发管理，因为每个CPU只会对自己的［等待队列］操作，不会影响其它CPU。<br />
softnet_data定义大略如下：</p>
<div class="codeblock">
<pre>

&lt;netdevice.h&gt;
struct softnet_data
{
    ...
    struct sk_buff_head    input_pkt_queue;
    struct net_device      *output_queue;
    struct sk_buff         *completion_queue;
...
}
</pre>
</div>
<p>其中列出本文讨论到的三个成员：</p>
<ol>
<li>收包队列，input_pkt_queue，类型为sk_buff_head，从名字可知，input_pkt_queue指向由sk_buff_head构造的链表，逻辑语义就是［分组］的数据库；</li>
<li>设备列表，output_queue，被调度的等待发包的设备列表；</li>
<li>完成列表，completion_queue，发送完成的分组列表，用于缓冲区延迟处理。</li>
</ol>
<h3>3.3 软中断处理</h3>
<p>系统退出收包的硬中断处理后返回到原来被中断的进程继续执行，片刻后内核调度进程（schedule() in kernel/sched.c) 被时钟中断唤醒，调度进程在执行进程调度前，先检测软中断信号[Q]。发现有软中断则执行软中断处理总入口——do_softirq()。</p>
<p class="comment">Q：内核有多处检测软中断信号的操作，进程调度前只是其中之一。如每次硬中断退出前，irq_exit( )会检测。研究到此，在软中断功能的实现上出现了多处理解冲突或理解不了。第一，软中断处理函数属于哪个内核线程；多处软中断检测都是直接执行do_softirq()，调度线程和硬中断处理函数不可能会是软中断处理函数的父线程，这个怎么理解；第二，我在进程调度前的代码里没有找到软中断检测；</p>
<p>do_softirq会逐个检查软中断——假设只有NET_RX_SOFTIRQ，并调用它们的软中断处理函数——net_rx_action()。</p>
<p>net_rx_action是一个中立的通用函数，它不偏向特定网络设备和网络层协议，只负责管理收包队列。net_rx_action主要是通过一个无条件循环(for(;;){&#8230;})将［当前CPU的收包队列］上的分组一个接一个地转给网络层协议，直到队列为空。为了防止DOS（ denial-of-service）攻击，net_rx_action的无条件循环在以下两条件满足时，必须退出：</p>
<ul>
<li>第一，执行时间超过一滴答（one tick or 10 ms）；</li>
<li>第二，此次连续收包数达到了队列的最大容量（budget =net_dev_max_backlog），不管队列是否为空。</li>
</ul>
<p>它的流程大略如下：</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/08/net_rx_action_codeflow.jpg"><img src="http://arttech.us/wp-content/uploads/2011/08/net_rx_action_codeflow.jpg" alt="" title="net_rx_action_codeflow" width="478" height="253" class="aligncenter size-full wp-image-515" /></a></p>
<p>net_rx_action先做一些准备工作，然后收包任务转给process_backlog，process_backlog反复执行以下任务（为了简化讨论，假设process_backlog循环执行直到没有分组包需要处理为止，并且处理过程中没有中断）：</p>
<ol>
<li>__skb_dequeue将分组数据（socket buffer）从收包队列中出列；</li>
<li>调用netif_receive_skb分析分组数据的网络层类型，然后调用网络层协议的收包接口——deliver_skb 。</li>
<li>deliver_skb根据网络层协议类型转而调用相应的收包接口，如IP协议的ip_rcv</li>
</ol>
<p>值得注意的是，net_rx_action是内核中网络代码的入口点之一，net_rx_action有两个出口点：</p>
<ul>
<li>第一，将分组向上传，最后来到socket&#8217;s wait queue；</li>
<li>第二，在IP层被发现是转发包，转入发包队列。</li>
</ul>
<p>更详细的net_rx_action描述和更多ip_rcv以后的代码控制请参考文献。</p>
<h2>4.发包过程</h2>
<p>由上可知，收包过程由两个主要内核活动完成的，这两个活动的上下文是断开的。发包过程的内核活动比较收包多一些，并且有在单一线程上下文内完成的。因此可以将发包线路（活动）分为两种：</p>
<p>第一，直接发包：在单一线程上下文上的直接发包；单一线程亦分为两种情况，从收包过程中转发过来的收包软中断上下文，和由上层传下来的系统调用上下文；</p>
<p>第二，软中断发包：分开两个线程的————［入列线程］和［软中断线程］，有点像收包过程上半部和下半部。软中断发包统一在软中断上下文。</p>
<h3>4.1 发包队列</h3>
<p>无论是直接发包还是软中断发包，分组包必须先入列网络设备的发包队列[注]。</p>
<p class="comment">
注：有一种例外，就是设备的入列接口(dev-&gt;enqueue == NULL)没有实现，在这种情况下，分组会直接发走，例如loopback设备。</p>
<p>发包线程从调用dev_queue_xmit(skb)开始发包。参数skb是socket buffer对象，skb-&gt;dev指向了非常重要信息——net_device。这个信息是在IP协议层由路由算法算得的，它决定分组入列哪个网络设备的发包队列。确定入列目标后[注1]，dev_queue_xmit调用dev-&gt;qdisc-&gt;enqueue()将分组入列[注2]。</p>
<ul class="comment">
<li>注1：dev_queue_xmit还会对分组进行链路层协议的处理，如分片，这里不详述，请参考文献。</li>
<li>注2：发包队列可能以简单的FIFO实现，也可能由多个队列实现复杂的队列算法。这个队列算法名叫Queuing Discipline，是设备相关的（dev-&gt;qdisc），这里不详述，请参考文献。</li>
</ul>
<h3>4.2 直接发包</h3>
<p>分组入列后，发包线程调用qdisc_run(dev)启动队列发送。qdisc_run主要做一件简单的事，重复调用qdisc_restart()将队列上的分组逐个发走。qdisc_restart(dev)的主要任务是将分组从发包队列出列（dev-&gt;qdisc-&gt;dequeue()），然后调用网络设备的发包接口（dev-&gt;hard_start_xmit()）将其发走。</p>
<blockquote><p>网络设备的并发控制</p>
<p>由于网卡是慢速的硬件设备，并且网络设备（及其发包队列）是公共资源，因而需要对其作并发控制。例如，qdisc_restart正式调用dev-&gt;hard_start_xmit前必须取得（acquire）发送锁（dev-&gt;xmit_lock），如果发送接口已经锁上，还判断是当前CPU还是其它CPU锁上的，并且相应的处理。关于qdisc_restart(dev)内的并发控制这里不详述，详见文献。</p>
</blockquote>
<p>qdisc_run(dev)的具体执行大概如下：</p>
<ol>
<li>检查发包队列是否停止，如果是则立即退出；发包线程通过读取［网络设备的流控制（Flow Control）API］——netif_queue_stopped(dev)确认发包队列的状态；</li>
<li>调用 qdisc_restart( ) ，转而执行以下子步骤：
<ol>
<li>调用网络设备的队列算法中的出列函数，如果队列为空，返回0；</li>
<li>检查内核是否实现了帧嗅探策略（packet sniffing policy），然则调用dev_queue_xmit_nit()向其传一份分组拷贝；</li>
<li>调用网络设备的发包接口（dev-&gt;hard_start_xmit()）</li>
<li>如果hard_start_xmit发送失败，分组重新入列，执行设备调度（netif_schedule(dev)），向内核发出发包软中断——NET_TX_SOFTIRQ；返回-1；</li>
</ol>
</li>
<li>如果队列为空（出列操作返回空指针），或者hard_start_xmit发送失败，立即退出；否则返回第一步发送下一个分组。</li>
</ol>
<p>根据实际发包情况，qdisc_restart向qdisc_run返回三种值：</p>
<ul>
<li>= 0: 队列为空，发包完成；</li>
<li>&gt; 0: 队列上仍有分组，但是队列算法（dev-&gt;qdisc）为了流量控制（Traffic Control）决定暂时停止发送；</li>
<li>&lt; 0: 队列上仍有分组，网络设备因空间不足暂不能接收更多的分组。</li>
</ul>
<p>无论返回什么值，发送线程到此结束，分组要么直接发走，要么整个任务被重新调度，等待软中断处理。</p>
<h3>4.3 设备调度</h3>
<p>发包线程将分组出列后，会因为各种原因不能立即发送，设备不能立即发送的原因有：</p>
<ol>
<li>设备被独占，锁上了</li>
<li>设备缓冲区满了，放不下一个分组</li>
<li>分组发送流量管制，重要性高的分组先发送</li>
<li>控制发送的频率，在指定的时间点发送</li>
</ol>
<p>那么，发包线程只能将分组重新入列，并执行设备调度操作：_ _netif_schedule 。</p>
<p>_ _netif_schedule 主要完以下两个任务:</p>
<ol>
<li> 将发包队列不为空的设备入列CPU的output_queue；</li>
<li>发出发包软中断——NET_TX_SOFTIRQ；</li>
</ol>
<p>由于调度一个被停掉的设备是无意义的，所以_ _netif_schedule被抽象包装为两个高级接口：</p>
<p>1. 网络层方向的接口 netif_schedule</p>
<p>实现如下，很直观，只对活动的设备进行调度（_ _LINK_STATE_XOFF标志）</p>
<div class="codeblock">
<pre>
static inline void netif_schedule(struct net_device *dev)
{
    if (!test_bit(_ _LINK_STATE_XOFF, &amp;dev-&gt;state))
        _ _netif_schedule(dev);
}
</pre>
</div>
<p>2. 网络设备层方向的接口 netif_wake_queue</p>
<p>此接口的典型应用是设备的发包完成中断处理函数，逻辑语义是，已经发完一个包了，唤醒发包队列。实现如下，也很直观，先启用，后调度，注意，此接口功能相当于netif_start_queue 加 netif_schedule。</p>
<div class="codeblock">
<pre>
static inline void netif_wake_queue(struct net_device *dev)
{
    ...
    if (test_and_clear_bit(_ _LINK_STATE_XOFF, &amp;dev-&gt;state))
        _ _netif_schedule(dev);
}
</pre>
</div>
<h3>4.4 软中断发包</h3>
<p>最后的一项重要内核活动是发包软中断，net_tx_action是软中断处理函数。如期望的功能，net_tx_action主要任务是取得当前CPU的调度设备列表上的设备，将设备的发包队列上的分组发走。另外，由于设备驱动的发包操作是在中断上下文内完成的，为了让硬件发包操作耗时更短，发包完成后的缓冲区清理工作亦被延迟到net_tx_action来完成。</p>
<p>我们先看看缓冲区清理任务。设备驱动成功将一个分组发走后会调用dev_kfree_skb_irq将分组（的地址）添加到当前CPU的完成队列（softnet_data-&gt;completion_queue）。由于缓冲区清理很耗时，所以这里只是使用dev_kfree_skb_irq将分组的地址保存起来，等软中断稍后处理，而不是直接调用dev_kfree_skb进行清理。</p>
<p>由于net_tx_action工作在硬件中断上下文以外，而设备驱动会随时中断系统，将新分组添加到completion_queue，因此，net_tx_action必须在访问softnet_data时关闭中断。为了让关闭中断时间尽量的短，net_tx_action只是简单的将completion_queue保存到本地，然后置NULL。这样，net_tx_action可以调用_ _kfree_skb逐个清理缓冲区的同时，设备驱动可以将新分组添加completion_queue。</p>
<div class="codeblock">
<pre>
if (sd-&gt;completion_queue) {
    struct sk_buff *clist;

    local_irq_disable( );
    clist = sd-&gt;completion_queue;
    sd-&gt;completion_queue = NULL;
    local_irq_enable( );

    while (clist != NULL) {
        struct sk_buff *skb = clist;
        clist = clist-&gt;next;
        BUG_TRAP(!atomic_read(&amp;skb-&gt;users));
        _ _kfree_skb(skb);
    }
}
</pre>
</div>
<p>我们再看发包任务。与清理缓冲区类似，操作被隔离出硬中断，设备列表（output_queue）保存到本地处理。值得注意的是，在调用qdisc_run(dev)发包前，必须取得发包队列锁（dev-&gt;queue_lock），如果没能取得队列锁（有其它CPU正发这个设备上的分组，将其锁上），设备再被调度（netif_schedule）。</p>
<div class="codeblock">
<pre>
if (sd-&gt;output_queue) {
    struct net_device *head;

    local_irq_disable( );
    head = sd-&gt;output_queue;
    sd-&gt;output_queue = NULL;
    local_irq_enable( );

    while (head) {
        struct net_device *dev = head;
        head = head-&gt;next_sched;
        smp_mb_ _before_clear_bit( );
        clear_bit(_ _LINK_STATE_SCHED, &amp;dev-&gt;state);
        if (spin_trylock(&amp;dev-&gt;queue_lock)) {
            qdisc_run(dev);
            spin_unlock(&amp;dev-&gt;queue_lock);
        } else {
        netif_schedule(dev);
        }
    }
}
</pre>
</div>
<p>以上代码有一点值得注意，就是设备列表是从头部开始按顺序处理的，而调度函数netif_schedule也是将最后调度的保存在头部，那么设备列表是按照先进后出（LIFO）的规则处理，在某些情况下可能会不公平。</p>
<h2>5.小结</h2>
<p>回应文首，字符设备或块设备驱动设计的重心是对硬件一方的接口的理解，因为它们上层的逻辑接口语义基本固定；而从网络设备收发分组的原理里，我们隐隐约约发现在IP层与设备之间还存在着一层机构，协调着分组的收发。这层机构叫什么呢？姑且叫［网络设备驱动子系统］好了。网络设备驱动设计必须掌握硬件和协议栈两个方向的实现原理。</p>
<h2>6.参考</h2>
<ul>
<li>1.《Understanding Linux Network Internals》10.4. Notifying the Kernel of Frame Reception: NAPI and netif_rx</li>
<li>2.《UTLK 2nd》18.4 Receiving Packets from the Network Card</li>
<li>3.《The LInux networking Architecture》6.2 Processes on the Data-Link Layer</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/ip-layer-interact-nic-layer.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>设备、驱动和开发者</title>
		<link>http://arttech.us/y-2011/device-driver-developer.html</link>
		<comments>http://arttech.us/y-2011/device-driver-developer.html#comments</comments>
		<pubDate>Tue, 19 Jul 2011 10:28:32 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[驱动程序]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=503</guid>
		<description><![CDATA[自接触Linux设备驱动程序以来，一直在思考设备、设备驱动和设备驱动开发者三者之间的关系，也曾经写下一些粗略的思考碎片（看这里）；随着对硬件和内核的认识的增加，三者的关系也便更清晰。 设备 设备是系统的一种，有接口、用户、设计者和维护者等系统属性。特别注意的是设备可以是物理的，也可以是逻辑的；因而，设备的实现形式不同，其接口、用户、设计者和维护者的实现形式也各有不同。例如在设备接口上，［显示终端设备］的接口显示器（上的字符）和键盘，串口设备的接口是12个寄存器，一支软件虚拟的TTY设备的接口是某种C函数等。 一些直观概念扰乱了我们认清设备、驱动和开发者三者的关系，例如，“驱动程序是为某设备编写的一集C函数”和“设备就是主板上芯片、扩展板和通过USB线链接的移动盘”。下文开始讲解设备、驱动和开发者三者的关系，可能有点费解，解理的关键是何为“逻辑设备”。 设备驱动 我们说设备有接口，设备可以是逻辑的，那么设备驱动就是实现［逻辑设备的接口］的软性代码。另外，设备作为一种系统，必然有内部的工作状态，包括静态属性，例如设备名，还有动态属性，例如内部临时记忆（缓冲区），所以设备驱动还必需包含用于构建设备的其它材料，常见的做法是定义一个表征设备状态的C结构。 值得注意的是，除了［设备使用接口］，由于现在计算机系统动态性和多道性，设备的［安装接口］和［使用前接口］也是设备驱动的内容。拿标准常用设备接口file_operations举例，open/release是使用前接口，read/write/ioctl等为使用接口。安装接口没有标准，一般为reg_xxx/unreg_xxx，或者xxx_init/xxx_cleanup。 ［安装接口］的“安装”语义是让逻辑设备的功能[注]可用，过程大略为初始化一些设备状态记忆，将软性的［设备驱动二进制］拷进主存，并作相应的配置。安装过程是设备相关的，一般内容有： 逻辑设备初始化，如为设备的内部状态分配记忆空间； 申请系统资源，如IO端口、IRQ号和设备号； 把设备添加进系统的设备管理系统，如往设备驱动模型添加新设备类，创建设备文件等。 注：集成了设备驱动的设备均为逻辑设备，Linux设备驱动开发者都是为逻辑设备开发驱动，但是我们的直观印象都是为物理设备开发驱动，这在理解上出现冲突。 ［使用前接口］主要协调多个用户同时使用设备。 构建设备（驱动）的一些重要C结构有inode、cdev（字符设备）、file和file_operations等，为了更好区分设备的［安装］与［使用］——两个过程，我们分析一下在这些过程中内核的空间这些对象的创建及对象间链接关系。 第一，安装前，内核没有上图任何对象，设备驱动的二进制代码一般存在外存； 第二，安装后打开前，cdev和一个或多个（取决于次设备号）file_operations出现在内核空间，代码从外存拷入，对象由内核的设备管理系统管理；接着，inode也随设备文件一并创建好；cdev与file_operations有关联，cdev与inode还没有关联； 第三，设备打开后，cdev与inode关联上，VFS通过inode的主设备号查询内核的设备管理系统找到cdev；file被创建，并且通过inode-&#62;cdev-&#62;file_operatins，关联上设备驱动代码。至此，由用户空间到设备驱动的调用路径被接通，用户进程可以通过file对象使用设备了。 设备驱动开发者 我们说，如果设备可以逻辑构建的，那么有没有多重抽象实现的逻辑设备呢？事实上，Linux好一些子系统就这么干的，一个例子是它的TTY设备驱动子系统。我们假设整个计算机系统分为多层设备，高层设备依赖低层设备，对低层设备进行逻辑抽象，提供更“友好”的接口，那么第N层的［设备驱动开发者］利用N-1层设备的接口实现N层设备的接口——N层设备的驱动程序。举例如，一个使用少量几个IO端口的字符设备（《LDD3》的字符驱动一章有实例代码），N-1层设备的接口就是IO端口，N层设备的接口就是file_operations。 设备用户 设备驱动开发者并不是设备的用户，开发者是作为一个位创造者的身份，分析设计，并创造设备。设备可构建为逻辑类型后，设备的用户变得复杂和让人困惑。我们假设提供硬件接口的设备为硬件层设备，提供文件接口（file_operation）的设备为文件层设备，文件层设备实现了设备驱动程序（如下图中的file device driver），那么，硬件层设备的［用户］是实现文件层设备的［设备驱动］，文件层设备的［用户］是用户空间的［程序］。低层设备的用户不是人，而是更抽象的程序代码，人是位于抽象最顶层的用户。 human usr -kb-mouse-window&#8212;&#8212;&#8212;&#8212; usr space process -fop&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- file device(build file device driver in) -reg&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;- hw device]]></description>
			<content:encoded><![CDATA[<p>自接触Linux设备驱动程序以来，一直在思考设备、设备驱动和设备驱动开发者三者之间的关系，也曾经写下一些粗略的思考碎片（看<a href="http://arttech.us/y-2010/programmer-responsibility.html" target="_blank">这里</a>）；随着对硬件和内核的认识的增加，三者的关系也便更清晰。</p>
<h2>设备</h2>
<p>设备是<a href="http://arttech.us/y-2010/what-is-system.html" target="_blank">系统</a>的一种，有接口、用户、设计者和维护者等系统属性。特别注意的是设备可以是物理的，也可以是逻辑的；因而，设备的实现形式不同，其接口、用户、设计者和维护者的实现形式也各有不同。例如在设备接口上，［显示终端设备］的接口显示器（上的字符）和键盘，串口设备的接口是12个寄存器，一支软件虚拟的TTY设备的接口是某种C函数等。</p>
<p>一些直观概念扰乱了我们认清设备、驱动和开发者三者的关系，例如，“驱动程序是为某设备编写的一集C函数”和“设备就是主板上芯片、扩展板和通过USB线链接的移动盘”。下文开始讲解设备、驱动和开发者三者的关系，可能有点费解，解理的关键是何为“逻辑设备”。<br />
<span id="more-503"></span></p>
<h2>设备驱动</h2>
<p>我们说设备有接口，设备可以是逻辑的，那么设备驱动就是实现［逻辑设备的接口］的软性代码。另外，设备作为一种系统，必然有内部的工作状态，包括静态属性，例如设备名，还有动态属性，例如内部临时记忆（缓冲区），所以设备驱动还必需包含用于构建设备的其它材料，常见的做法是定义一个表征设备状态的C结构。</p>
<p>值得注意的是，除了［设备使用接口］，由于现在计算机系统动态性和多道性，设备的［安装接口］和［使用前接口］也是设备驱动的内容。拿标准常用设备接口file_operations举例，open/release是使用前接口，read/write/ioctl等为使用接口。安装接口没有标准，一般为reg_xxx/unreg_xxx，或者xxx_init/xxx_cleanup。</p>
<p>［安装接口］的“安装”语义是让逻辑设备的功能[注]可用，过程大略为初始化一些设备状态记忆，将软性的［设备驱动二进制］拷进主存，并作相应的配置。安装过程是设备相关的，一般内容有：</p>
<ul>
<li>逻辑设备初始化，如为设备的内部状态分配记忆空间；</li>
<li>申请系统资源，如IO端口、IRQ号和设备号；</li>
<li>把设备添加进系统的设备管理系统，如往设备驱动模型添加新设备类，创建设备文件等。</li>
</ul>
<p class="comment">注：集成了设备驱动的设备均为逻辑设备，Linux设备驱动开发者都是为逻辑设备开发驱动，但是我们的直观印象都是为物理设备开发驱动，这在理解上出现冲突。</p>
<p>［使用前接口］主要协调多个用户同时使用设备。</p>
<p>构建设备（驱动）的一些重要C结构有inode、cdev（字符设备）、file和file_operations等，为了更好区分设备的［安装］与［使用］——两个过程，我们分析一下在这些过程中内核的空间这些对象的创建及对象间链接关系。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/device-driver-dev.jpg"><img src="http://arttech.us/wp-content/uploads/2011/07/device-driver-dev.jpg" alt="" title="device-driver-dev" width="580" height="269" class="aligncenter size-full wp-image-504" /></a></p>
<p>第一，安装前，内核没有上图任何对象，设备驱动的二进制代码一般存在外存；</p>
<p>第二，安装后打开前，cdev和一个或多个（取决于次设备号）file_operations出现在内核空间，代码从外存拷入，对象由内核的设备管理系统管理；接着，inode也随设备文件一并创建好；cdev与file_operations有关联，cdev与inode还没有关联；</p>
<p>第三，设备打开后，cdev与inode关联上，VFS通过inode的主设备号查询内核的设备管理系统找到cdev；file被创建，并且通过inode-&gt;cdev-&gt;file_operatins，关联上设备驱动代码。至此，由用户空间到设备驱动的调用路径被接通，用户进程可以通过file对象使用设备了。</p>
<h2>设备驱动开发者</h2>
<p>我们说，如果设备可以逻辑构建的，那么有没有多重抽象实现的逻辑设备呢？事实上，Linux好一些子系统就这么干的，一个例子是它的TTY设备驱动子系统。我们假设整个计算机系统分为多层设备，高层设备依赖低层设备，对低层设备进行逻辑抽象，提供更“友好”的接口，那么第N层的［设备驱动开发者］利用N-1层设备的接口实现N层设备的接口——N层设备的驱动程序。举例如，一个使用少量几个IO端口的字符设备（《<a href="http://book.douban.com/subject/1493443/" target="_blank">LDD3</a>》的字符驱动一章有实例代码），N-1层设备的接口就是IO端口，N层设备的接口就是file_operations。</p>
<h2>设备用户</h2>
<p>设备驱动开发者并不是设备的用户，开发者是作为一个位创造者的身份，分析设计，并创造设备。设备可构建为逻辑类型后，设备的用户变得复杂和让人困惑。我们假设提供硬件接口的设备为硬件层设备，提供文件接口（file_operation）的设备为文件层设备，文件层设备实现了设备驱动程序（如下图中的file device driver），那么，硬件层设备的［用户］是实现文件层设备的［设备驱动］，文件层设备的［用户］是用户空间的［程序］。低层设备的用户不是人，而是更抽象的程序代码，人是位于抽象最顶层的用户。</p>
<p>human usr<br />
-kb-mouse-window&#8212;&#8212;&#8212;&#8212;<br />
usr space process<br />
-fop&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
file device(build file device driver in)<br />
-reg&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;&#8212;-<br />
hw device</p>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/device-driver-developer.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>TTY设备驱动结构</title>
		<link>http://arttech.us/y-2011/tty-device-driver.html</link>
		<comments>http://arttech.us/y-2011/tty-device-driver.html#comments</comments>
		<pubDate>Sat, 16 Jul 2011 09:53:20 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[驱动程序]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=496</guid>
		<description><![CDATA[译自：《Serial Drivers》 原作：Alessandro Rubini 刘建文略译 本文为你展示基于“串口通信设备”的TTY设备驱动程序内部结构，并讲述这些驱动是如何实现链路层通信协议（包括ppp和slip）的应用。本文的代码基于2.4内核，大部分适用于2.2和2.0[注]。 注：本文的代码虽然基于2.4内核，但是结构原理在现在内核版本仍然存在。 串口驱动误解 当我们说【串口驱动】的时候，我们第一个想到的东西是/dev/ttyS0，因为它是大家所熟知的串口通信的设备文件（至少在PC系统上如此）。由于/dev/ttyS0是一个字符型的设备文件，大家都以为串口驱动是字符设备驱动，这种推断不能叫错，只是不够准确。【串口驱动】不是一般的字符驱动，有两点可以证明： 第一，串行驱动不被拿来作字符驱动范例； 第二，不同的串行设备驱动使用相同的【主设备号】。当你为你的系统扩展一个新的串口，那这个串口的驱动程序不会被分配一个新的主设备号。 串口驱动的“串口”从分类就看出来它是一种特殊的驱动类型，归于字符驱动大类下。 查看 /proc/devices ，你会发现ttyS驱动的主设备号是4，这是一个善意的谎言：文本模式控制台设备的主设备号也是4。事实上，2.0版内核用了一个通用的名字“ttyp”代表主设备号4。 串口设备之所以与一般的字符设备（比如并口打印机或磁带机）存在不同是因为串口被用来实现高一级的抽象——tty，串口设备为tty设备提供了串行通信信道。tty的名字来源早期的一种串口应用设备——电传打字机（tele-type）。 tty设备驱动子系统 tty设备是什么样的设备呢？tty的概念其实已经被泛化，不只是指处理数字文字（alphanumeric）字符的终端设备，例如基于VGA和帧缓冲式的文本控制台、xterm虚拟终端等。［tty设备驱动子系统］以下图那样的一种结构实现了对tty设备的泛化。某特定tty设备驱动的特殊部分以注册的方式注册入通用的子系统部分，从而实现一支特定tty设备驱动。注册方式的好处是设备驱动的特殊部分可以以内核模块形式（kernel module）实现。 Figure 1 图中展示出有三部分是可注册的，串行通信设备驱动（serial.c）、线路规则（n_tty.c）和［tty设备驱动子系统］本身（tty_io.c）。可以这样理解，tty设备是一种特殊的字符设备，所以它需通过［字符设备驱动子系统］（fs/devices.c）注册自身。［串行通信设备驱动］是不能被用户直接使用的，它必须抽象为一个tty设备，然后配置使用默认的线路规则——n_tty.c，经［tty设备驱动子系统］在系统中注册为字符设备。 由此可见，［tty设备驱动子系统］被划分为三块，数据流从用户空间到串行设备间夹着一层tty。虽然如此，tty_io.c本身只是桥梁，实际的tty操作是线路规则完成，线路规则（line discipline）是一支定义串口线如何使用的软件模块。Linux默认是线路规则是N_TTY，N_TTY是标准字符终端IO处理规则。 为何如此复杂？ 这种分层设计看上去就很复杂，有必要吗？复杂的回报是灵活性。当tty设备应用种类纷繁时，很有必要。为新串行设备编写驱动比一般字符设备要复杂，有了这种通用抽象模型，为编写串口设备驱动省下很多功夫。 更重一点是，［tty设备驱动子系统］把线路规则也卸下，进一步的抽象后，线路规则也可替换。这样，串行设备驱动根本不知道数据是从哪来的，它只管收发就行了。 PPP和slip 如果你使用modem（使用PPP链路协议）拨号上网，或者用SLIP互联PC和你的掌上PDA，你就体会到上面提到的复杂性。ppp和SLIP实现了各自的线路规则，当它们中任何一个应用要运行，tty设备必须在它们的线路规则模块间切换，来构造不同的tty设备。 下图展示了SLIP应用的一个概念图。 Figure 2 有趣的是，SLIP驱动向［tty设备驱动子系统］和［网络子系统］同时注册，前者是线路规则N_SLIP，后者网络设备slip0。当tty设备切换为N_SLIP后，串行通信数据经TCP/IP协议栈与用户空间通信。当tty设备被切为TCP/IP网络设备后，其它用户进程没法读写 基于/dev/ttyS的tty设备。这也说明了为什么网络通道建立后，slattach 和 pppd 都不能退出的原因。 关键数据结构 ［tty设备驱动子系统］由三个主要的数据结构构建： struct tty_struct：这是［tty设备］的核心表征数据结构，结构体内内嵌（通过指针）余下的两个关键数据结构。每当有新的tty设备被打开使用时，tty_struct都被创建一个新实例，直到设备被关闭。注意实际代码里（tty_io.c）有很多复杂的操作，例如在打开和关闭tty设备的过程中对的termios设定的保存与恢复（至少基于串口的tty设备如此）。 struct tty_driver：这是驱动本身的表征，定义设备号，定义并实现设备的使用接口等，在设备打开的时，get_tty_driver函数负责关联设备与设备驱动。tty_driver结构成员大略如下： struct tty_driver { /* the driver states which range of devices it [...]]]></description>
			<content:encoded><![CDATA[<div class="postmeta">
<dl>
<dd>
<div class="summary">
<ul>
<li>译自：《<a href="http://www.linux.it/~rubini/docs/serial/serial.html" target="_blank">Serial Drivers</a>》</li>
<li>原作：Alessandro Rubini</li>
<li>刘建文略译</li>
</ul>
</div>
</dd>
</dl>
</div>
<p>本文为你展示基于“串口通信设备”的TTY设备驱动程序内部结构，并讲述这些驱动是如何实现链路层通信协议（包括ppp和slip）的应用。本文的代码基于2.4内核，大部分适用于2.2和2.0[注]。</p>
<p class="comment">注：本文的代码虽然基于2.4内核，但是结构原理在现在内核版本仍然存在。</p>
<h2>串口驱动误解</h2>
<p>当我们说【串口驱动】的时候，我们第一个想到的东西是/dev/ttyS0，因为它是大家所熟知的串口通信的设备文件（至少在PC系统上如此）。由于/dev/ttyS0是一个字符型的设备文件，大家都以为串口驱动是字符设备驱动，这种推断不能叫错，只是不够准确。<span id="more-496"></span>【串口驱动】不是一般的字符驱动，有两点可以证明：</p>
<ul>
<li>第一，串行驱动不被拿来作字符驱动范例；</li>
<li>第二，不同的串行设备驱动使用相同的【主设备号】。当你为你的系统扩展一个新的串口，那这个串口的驱动程序不会被分配一个新的主设备号。</li>
</ul>
<p>串口驱动的“串口”从分类就看出来它是一种特殊的驱动类型，归于字符驱动大类下。</p>
<p>查看 /proc/devices ，你会发现ttyS驱动的主设备号是4，这是一个善意的谎言：文本模式控制台设备的主设备号也是4。事实上，2.0版内核用了一个通用的名字“ttyp”代表主设备号4。</p>
<p>串口设备之所以与一般的字符设备（比如并口打印机或磁带机）存在不同是因为串口被用来实现高一级的抽象——tty，串口设备为tty设备提供了串行通信信道。tty的名字来源早期的一种串口应用设备——电传打字机（tele-type）。</p>
<h2>tty设备驱动子系统</h2>
<p>tty设备是什么样的设备呢？tty的概念其实已经被泛化，不只是指处理数字文字（alphanumeric）字符的终端设备，例如基于VGA和帧缓冲式的文本控制台、xterm虚拟终端等。［tty设备驱动子系统］以下图那样的一种结构实现了对tty设备的泛化。某特定tty设备驱动的特殊部分以注册的方式注册入通用的子系统部分，从而实现一支特定tty设备驱动。注册方式的好处是设备驱动的特殊部分可以以内核模块形式（kernel module）实现。<br />
<a href="http://arttech.us/wp-content/uploads/2011/07/tty01.jpg"><img src="http://arttech.us/wp-content/uploads/2011/07/tty01.jpg" alt="" title="tty01" width="437" height="472" class="aligncenter size-full wp-image-497" /></a><br />
Figure 1</p>
<p>图中展示出有三部分是可注册的，串行通信设备驱动（serial.c）、线路规则（n_tty.c）和［tty设备驱动子系统］本身（tty_io.c）。可以这样理解，tty设备是一种特殊的字符设备，所以它需通过［字符设备驱动子系统］（fs/devices.c）注册自身。［串行通信设备驱动］是不能被用户直接使用的，它必须抽象为一个tty设备，然后配置使用默认的线路规则——n_tty.c，经［tty设备驱动子系统］在系统中注册为字符设备。</p>
<p>由此可见，［tty设备驱动子系统］被划分为三块，数据流从用户空间到串行设备间夹着一层tty。虽然如此，tty_io.c本身只是桥梁，实际的tty操作是线路规则完成，线路规则（line discipline）是一支定义串口线如何使用的软件模块。Linux默认是线路规则是N_TTY，N_TTY是标准字符终端IO处理规则。</p>
<h2>为何如此复杂？</h2>
<p>这种分层设计看上去就很复杂，有必要吗？复杂的回报是灵活性。当tty设备应用种类纷繁时，很有必要。为新串行设备编写驱动比一般字符设备要复杂，有了这种通用抽象模型，为编写串口设备驱动省下很多功夫。</p>
<p>更重一点是，［tty设备驱动子系统］把线路规则也卸下，进一步的抽象后，线路规则也可替换。这样，串行设备驱动根本不知道数据是从哪来的，它只管收发就行了。</p>
<h2>PPP和slip</h2>
<p>如果你使用modem（使用PPP链路协议）拨号上网，或者用SLIP互联PC和你的掌上PDA，你就体会到上面提到的复杂性。ppp和SLIP实现了各自的线路规则，当它们中任何一个应用要运行，tty设备必须在它们的线路规则模块间切换，来构造不同的tty设备。</p>
<p>下图展示了SLIP应用的一个概念图。<br />
<a href="http://arttech.us/wp-content/uploads/2011/07/tty02.jpg"><img src="http://arttech.us/wp-content/uploads/2011/07/tty02.jpg" alt="" title="tty02" width="595" height="490" class="aligncenter size-full wp-image-498" /></a><br />
Figure 2</p>
<p>有趣的是，SLIP驱动向［tty设备驱动子系统］和［网络子系统］同时注册，前者是线路规则N_SLIP，后者网络设备slip0。当tty设备切换为N_SLIP后，串行通信数据经TCP/IP协议栈与用户空间通信。当tty设备被切为TCP/IP网络设备后，其它用户进程没法读写 基于/dev/ttyS的tty设备。这也说明了为什么网络通道建立后，slattach 和 pppd 都不能退出的原因。</p>
<h2>关键数据结构</h2>
<p>［tty设备驱动子系统］由三个主要的数据结构构建：</p>
<p>struct tty_struct：这是［tty设备］的核心表征数据结构，结构体内内嵌（通过指针）余下的两个关键数据结构。每当有新的tty设备被打开使用时，tty_struct都被创建一个新实例，直到设备被关闭。注意实际代码里（tty_io.c）有很多复杂的操作，例如在打开和关闭tty设备的过程中对的termios设定的保存与恢复（至少基于串口的tty设备如此）。</p>
<p>struct tty_driver：这是驱动本身的表征，定义设备号，定义并实现设备的使用接口等，在设备打开的时，get_tty_driver函数负责关联设备与设备驱动。tty_driver结构成员大略如下：</p>
<div class="codeblock">
<pre>
struct tty_driver {

/* the driver states which range of devices it supports */
short major;         /* major device number */
short minor_start;   /* start of minor device number*/
short   num;         /* number of devices */

/* and has its own operations */
int  (*open)();
void (*close)();
int  (*write)();
int  (*ioctl)();  /* device-specific control */

/* return information on buffer state */
int  (*write_room)();      /* how much can be written */
int  (*chars_in_buffer)(); /* how much is there to read */

/* flow control, input and output */
void (*throttle)();
void (*unthrottle)();
void (*stop)();
void (*start)();

/* and callbacks for /proc/tty/driver/ */
int (*read_proc)();
int (*write_proc)();
};</pre>
</div>
<p>struct tty_ldisc：线路规则模块由tty_struct的ldisc域指定。在设备打开的时候，ldisc默认指向n_tty，用户空间程序可以通过ioctl切换tty设备的线路规则。tty_ldisc结构成员大略如下：</p>
<div class="codeblock">
<pre>
struct tty_ldisc {

/* routines called from above */
int     (*open)();
void    (*close)();
ssize_t (*read)();
ssize_t (*write)();
int     (*ioctl)();

/* routines called from below */
void    (*receive_buf)();
int     (*receive_room)();
void    (*write_wakeup)();
};</pre>
</div>
<p>作为一般的tty设备驱动程序开发者，可以不必关心tty_struct的实现，只管tty_driver 和 tty_ldisc就行了。因为当应用系统使用新串行通信设备，我们一般实现tty_driver就行了；如果应用系统有传统的串口，并且想开发新上层应用，那实现一支新线路规则模块就行了。例如，假设你手头有一块特殊的键盘，使用标准RS-232串口，那么需要实现一支新线路规则模块就可以了，因为整套的串口tty设备驱动有了，键盘驱动已经有了。新线路规则模块完成与input子系统和通用键盘驱动的通信。</p>
<h2>设备的读写数据流</h2>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/tty03.jpg"><img src="http://arttech.us/wp-content/uploads/2011/07/tty03.jpg" alt="" title="tty03" width="620" class="aligncenter" /></a><br />
Figure 3</p>
<p>图三展示了数据是如何从用户空间流向硬件接口并返回的。从图可知，“写入数据”是比较直观，“读出数据”需要解释一下。“读数据”稍微复杂，因为“读数据”时硬件和用户空间没有直接的调用关系。因为只有用户空间调用硬件，硬件不能调用用户空间代码，所以只能把收到的数据存放起来，等用户空间程序来读。你可能已经猜到解决办法——使用缓冲区：硬件收到的数据会存放到内核的缓冲区并保持着，直到有用户空间程序把它们读走。当某用户程序读取缓冲区，而缓冲区为空的时候，用户程序被置入睡眠状态，只有当缓冲区收到数据后才被唤醒。</p>
<p>注意，其实“写数据”的操作一样有缓冲区。只是“写操作”是一步接一步由上而下，控制流比较简单直接，不像读操作有数据传输的延迟，写操作的缓冲区只是为了“软化”硬件传输操作和软件控制操作的边界。为了精简图示，图中没有画出写操作缓冲区。</p>
<p>对tty设备而言，［读缓冲区］应该集成在tty的数据结构内，虽然这样设计会让tty_struct变得相当肥大，但是没有理由表明缓冲区可以放在其它地方。tty设备传输不能没有缓冲，而tty设备是动态创建的，所以即使缓冲占有空间，至少不会浪费空间。</p>
<p>tty设备被设计使用两种类型的缓冲区，内核开发者选择了在线路规则模块内使用普通的缓冲区（tty buffer），而低层硬件驱动使用翻转式缓冲区（flip_buffer），使用翻转式缓冲，硬件驱动可以尽可能快的接收传入的数据，不必与上层同步操作：flip_buffers由硬件独占使用，flip_buffers的数据被tty_flip_buffer_push函数转存入tty buffer。</p>
<p>所谓的翻转式缓冲区其实就是两块一样大小的可以来回写入数据的物理缓冲区块。在底层的中断处理函数返回前，函数flush_to_ldisc会被调用来翻转缓冲区的读写指针，翻转的具体实现可参考drivers/char/tty_io.c中的flush_to_ldisc()。</p>
<h2>如何切换自定的线路规则模块</h2>
<p>［tty设备驱动子系统］提供了线路规则模块的注册接口（tty_register_ldisc()），定义了线路规则驱动结构（tty_ldisc ），驱动开发者可以自定线程路规则。线路规则有一个数值标识，有一个符号名。例如，缺省的tty线路规则的标识值是N_TTY，PPP的标识值是N_PPP，这些值在include/linux/tty.h 内静态定义。值得注意的是，目前的设计没有保留可用的标识值给本地自定使用，因此你只能手动在系统中“偷”一个值来使用。例如到目前为止，N_MOUSE还没有被广泛使用，你可以用它来自定一个线路规则 。</p>
<p>要切换到N_MOUSE，在用户程序中使用以下代码：</p>
<div class="codeblock">
<pre>
#include ....

int i = N_MOUSE;
ioctl(fd, TIOCSETD, &amp;i);</pre>
</div>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/tty-device-driver.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>udev——设备文件管理的用户空间实现</title>
		<link>http://arttech.us/y-2011/udev-userspace-implementation-of-devfs.html</link>
		<comments>http://arttech.us/y-2011/udev-userspace-implementation-of-devfs.html#comments</comments>
		<pubDate>Thu, 14 Jul 2011 02:50:24 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[驱动程序]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=491</guid>
		<description><![CDATA[译自：《udev – A Userspace Implementation of devfs》 原作：Greg Kroah-Hartman 注意：原文写于2003年，文中udev实现的一些术语现今可能不复存在 刘建文略译 Abstract 自2.5版内核起，用户空间的进程可以通过sysfs文件系统访问按层次组织的所有系统的外部设备（包括物理设备和虚拟设备）的元信息。另外，“/sbin/hotplug” 会在系统设备热插拔时向用户空间发出提示。具备了这两项功能后，在用户空间内动态管理设备文件（/dev目录）成为现实，一直需求的更灵活的设备名分配策略成为现实。 本报告文分析udev，一支代替devfs——只是实现动态管理/dev目录条目的方案——的用户空间程序，解决由devfs不能单独解决的一些问题： 第一，无论设备何时何处插入都使用同一个设备名； 第二，发现并用户空间提示系统设备发生变动； 第三，灵活的设备命名方案； 第四，允许内核使用动态的主设备号和次设备号； 第五，将命名策略移出内核空间 本文将解释为什么在用户空间实现的udev会优于在内核实现的devfs，并且详述在实现udev上的一些设计考虑。另外，这里也讲解udve的工作原理和怎样给udev制作插件（如命名方案naming scheme），还有一些udev使用上需要考虑的事情。 1.Introduction /dev目录挂接了所有系统设备的设备文件，用户程序通过这些文件调用设备的驱动程序，从而使用设备。例如，/dev/hda 是系统第一个IDE磁盘的设备文件，/dev/hda 被绑定了一主设备号和次设备号，内核根据主设备号找到IDE磁盘的驱动程序。目前，/dev下的很多文件名（和相应的设备号）已经被分配，分配任务由 The Linux Assigned Names And Numbers Authority (LANANA their web site at http://www.lanana.org/docs/device-list/devices.txt）负责。 当Linux支持一种新设备类型时，它必须被分配一个主设备号和数个次设备号。在2.4版及以前，可用的主设备号和次设备号都只是8位（1-255）。由于设备号有限，在2.3版的开发期设备号已经告急。2.6版起，主设备号提升为12位，次设备号20位。 2.前sysfs的问题 2.1 设备与设备文件的映射不固定 当内核发现一支已知类型的新硬件时，它一般会按顺序为这新硬件分配硬件所属类型的主设备号和次设备号。例如，在devices.txt分配中，USB设备号是180，USB打印机的设备号是0-15；当系统启动时，第一支被发现的USB打印机会分配（180,0），设备名是/dev/usb/lp0；第二支被发现的USB打印机会分配（180,1），设备名是/dev/usb/lp1。当用户改动USB的拓扑结构，例如添加一个USB hub，那么上述的USB打印机的检测次序有可能改变，也就是说次设备号有可能交换。在sysfs出现以前，这种变动，用户程序很难发现。 同样的情况出现在具有热插拔功能的总线设备上——PCI、IEEE1394、 USB和CardBus。 有了sysfs文件系统，用户程序想知道物理设备被分配了哪个次设备号变得容易。像上面的系统有两支不同的USB打印机的情况，/sys/class/usb的目录树结构像下面的： 在/sys下总是有某一设备的物理信息，例如生产厂商和序列号，而这些信息可以通过一个具有丰富语义的层次目录内找到。例如次设备号为0 的USB打印机在/sys/class/usb/lp0/device内。由上图可知设备文件/dev/usb/lp0所关联的设备是序列号为HXOLL0012202323480的USB打印机，而设备文件/dev/usb/lp1所关联的设备是序列号尾号330的USB打印机。 sysfs现在可以让用户查对哪个设备关联了哪个设备文件了。如果设备关联变动，那么设备重配置可以在用户空间进行，不必麻烦内核[注]。 注：虽然如此，普通用户一般并不关心/dev/usb/lp0 和 /dev/usb/lp1是否变动过，他们只想原封不动使用原有功能，不想做任何的配置工作，哪怕在用户空间。 2.2 [...]]]></description>
			<content:encoded><![CDATA[<div class="postmeta">
<dl>
<dd>
<div class="summary">
<ul>
<li>译自：《udev – A Userspace Implementation of devfs》</li>
<li>原作：Greg Kroah-Hartman</li>
<li>注意：原文写于2003年，文中udev实现的一些术语现今可能不复存在</li>
<li>刘建文略译</li>
</ul>
</div>
</dd>
</dl>
</div>
<h2>Abstract</h2>
<p>自2.5版内核起，用户空间的进程可以通过sysfs文件系统访问按层次组织的所有系统的外部设备（包括物理设备和虚拟设备）的元信息。另外，“/sbin/hotplug” 会在系统设备热插拔时向用户空间发出提示。具备了这两项功能后，在用户空间内动态管理设备文件（/dev目录）成为现实，一直需求的更灵活的设备名分配策略成为现实。</p>
<p>本报告文分析udev，一支代替devfs——只是实现动态管理/dev目录条目的方案——的用户空间程序，解决由devfs不能单独解决的一些问题：</p>
<ul>
<li>第一，无论设备何时何处插入都使用同一个设备名；</li>
<li>第二，发现并用户空间提示系统设备发生变动；</li>
<li>第三，灵活的设备命名方案；</li>
<li>第四，允许内核使用动态的主设备号和次设备号；</li>
<li>第五，将命名策略移出内核空间</li>
</ul>
<p>本文将解释为什么在用户空间实现的udev会优于在内核实现的devfs，并且详述在实现udev上的一些设计考虑。另外，这里也讲解udve的工作原理和怎样给udev制作插件（如命名方案naming scheme），还有一些udev使用上需要考虑的事情。<span id="more-491"></span></p>
<h2>1.Introduction</h2>
<p>/dev目录挂接了所有系统设备的设备文件，用户程序通过这些文件调用设备的驱动程序，从而使用设备。例如，/dev/hda 是系统第一个IDE磁盘的设备文件，/dev/hda 被绑定了一主设备号和次设备号，内核根据主设备号找到IDE磁盘的驱动程序。目前，/dev下的很多文件名（和相应的设备号）已经被分配，分配任务由 The Linux Assigned Names And Numbers Authority (LANANA their web site at http://www.lanana.org/docs/device-list/devices.txt）负责。</p>
<p>当Linux支持一种新设备类型时，它必须被分配一个主设备号和数个次设备号。在2.4版及以前，可用的主设备号和次设备号都只是8位（1-255）。由于设备号有限，在2.3版的开发期设备号已经告急。2.6版起，主设备号提升为12位，次设备号20位。</p>
<h2>2.前sysfs的问题</h2>
<h3>2.1 设备与设备文件的映射不固定</h3>
<p>当内核发现一支已知类型的新硬件时，它一般会按顺序为这新硬件分配硬件所属类型的主设备号和次设备号。例如，在devices.txt分配中，USB设备号是180，USB打印机的设备号是0-15；当系统启动时，第一支被发现的USB打印机会分配（180,0），设备名是/dev/usb/lp0；第二支被发现的USB打印机会分配（180,1），设备名是/dev/usb/lp1。当用户改动USB的拓扑结构，例如添加一个USB hub，那么上述的USB打印机的检测次序有可能改变，也就是说次设备号有可能交换。在sysfs出现以前，这种变动，用户程序很难发现。</p>
<p>同样的情况出现在具有热插拔功能的总线设备上——PCI、IEEE1394、 USB和CardBus。</p>
<p>有了sysfs文件系统，用户程序想知道物理设备被分配了哪个次设备号变得容易。像上面的系统有两支不同的USB打印机的情况，/sys/class/usb的目录树结构像下面的：</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/udev-devfs01.jpg"><img src="http://arttech.us/wp-content/uploads/2011/07/udev-devfs01.jpg" alt="" title="udev-devfs01" width="585" height="320" class="aligncenter size-full wp-image-492" /></a></p>
<p>在/sys下总是有某一设备的物理信息，例如生产厂商和序列号，而这些信息可以通过一个具有丰富语义的层次目录内找到。例如次设备号为0 的USB打印机在/sys/class/usb/lp0/device内。由上图可知设备文件/dev/usb/lp0所关联的设备是序列号为HXOLL0012202323480的USB打印机，而设备文件/dev/usb/lp1所关联的设备是序列号尾号330的USB打印机。</p>
<p>sysfs现在可以让用户查对哪个设备关联了哪个设备文件了。如果设备关联变动，那么设备重配置可以在用户空间进行，不必麻烦内核[注]。</p>
<p class="comment">注：虽然如此，普通用户一般并不关心/dev/usb/lp0 和 /dev/usb/lp1是否变动过，他们只想原封不动使用原有功能，不想做任何的配置工作，哪怕在用户空间。</p>
<h3>2.2 没有足够的设备号</h3>
<p>前sysfs的设备号分配是静态的。设备名和设备号的静态分配管理方案中，设备号由16位提升到32位暂缓了设备号短缺的问题，但是也保不齐有一天也不够用；而且静态管理方案还需要一个额外的名字授权管理。如果将管理方法改用动态方式，那么设备名与设备号只有单机冲突问题，没有社区冲突问题，这样名字授权管理可以省去，设备号也不可能用完。但是，要实现动态分配的最大问题是，用户空间无法知道设备的设备号（包括主设备和次设备号）。</p>
<p>其实，早在2.2版本内核里，动态分配次设备号已经在USB设备驱动子系统和串口设备驱动子系统实现，并取得成功。但是还是那个问题，用户还是没法知道哪个设备分配了哪个设备号，只能通过查对系统日志来确定设备号。有了sysfs，这个问题被解决。</p>
<h3>2.3 /dev is too big</h3>
<p>很多发行版系统中， /dev下的设备文件并不是完全与实际设备配对的，有很多是空的。在系统启动的时候，/dev被填入可能使用的节点，并不根据是否存在实际设备。例如，在Red Hat 9，/dev有18,000个设备文件。</p>
<p>为系统创建一大堆暂时甚至一直无用的设备文件，肯定产生性能和管理问题。因此很多操作系统把设备文件管理的任务移入内核，因为内核总是确切知道有多少实际设备的。设备文件管理的内核方案就是基于ram的文件系统——devfs。Linux也使用了这一方案，并且在数个流行的发行版上得到实现。</p>
<h2>2.4 devfs</h2>
<p>很多UNIX类操作系统都实现了devfs，用以解决前面提到的设备文件管理上的一些问题。Linux也实现了devfs，解决了不少人的燃眉之急。但是，基于Linux的devfs实现只解决部分需求，如devfs只是解决了“/dev太大了”的问题，仍然存在一些没有解决的问题。另外，devfs 还带入新问题。引起新问题包括：</p>
<p>第一，devfs 的命名方案与LANANA授权命名不兼容。由于这种不兼容性，在devfs系统与静态/dev系统间切换需额外的配置，devfs的作者们必须实现某种与静态/dev命名的兼容模式。</p>
<p>第二，命名策略（ naming policy ）被移进内核。</p>
<p>没解决的问题包括，第一，设备号依然能静态分配；第二，设备与设备文件的映射依然不固定。</p>
<h2>3 udev’s goals</h2>
<p>在出现前面提到的所有问题的同时，udev项目已启动。其目标如下：</p>
<ul>
<li>运行在用户空间</li>
<li>创建一个动态的/dev</li>
<li>提供一致的设备命名方法</li>
<li>提供一个用户空间的API来访问关于当前系统设备的信息</li>
</ul>
<p>基于两个事实，第一个目标——“在用户空间中运行”——已经很容易做到。首先，/sbin/hotplug 实现了当系统添加或删除设备向用户空间发送事件消息；其次，结合在sysfs中能展示所有设备的元信息。其余的目标可把udev的项目拆分成三个独立的子系统：</p>
<ul>
<li>namedev：处理设备的命名</li>
<li>libsysfs：一个用于访问系统上的设备信息的标准库</li>
<li>udev：动态管理 /dev</li>
</ul>
<h3>3.1 namedev</h3>
<p>鉴于不同的设备命名方案的需要，udev的设备命名部分已被独立出来，成为独立的子系统——namedev。命名策略移出udev的二进制后，不同需求的用户可根据需要自定设备命名方案。namedev与udev之间实现了一个标准的接口，udev可以通过标准接口透明地调用命名一个特定的设备。</p>
<p>udev的初始版本，namedev逻辑仍然是有几个源文件链接进入udev的二进制。目前只有一个命名方案实施，就是由LANANA指定的。这个方案很简单，设备名命名基本上与sysfs使用相同的名称，这个方案适用于目前大多数Linux用户。</p>
<p>udev项目目标之一是为用户提供一种基于一套策略来命名设备的方法。 namedev目前版本为用户提供了一个五个步骤序列来确定一个给定的设备的名称。这些步骤按指定顺序征询，如果设备的名称可以在任何一步确定，就使用步确定的名称。现有的步骤如下：</p>
<ol>
<li>标签或序号</li>
<li>总线设备号</li>
<li>总线拓扑</li>
<li>名称简单替换</li>
<li>内核指定的名称</li>
</ol>
<p>第一步，添加到系统的设备会被检查它是否有一个某种类型的设备的唯一的标识符。例如，USB设备检查USB序列号，SCSI设备的检查UUID，块设备检查文件系统的标签。如果匹配用户提供的标识符（在配置文件中），匹配结果被接受。</p>
<p>第二步，检查设备的总线号。对于很多总线，这个数字一般不随时间变化，并保证所有的总线号要在系统中的任何一个点在时间上是唯一的。一个很好的例子是PCI总线的号很少改变。再次，如果总线号匹配由用户提供的，匹配结果名称用于该设备。</p>
<p>第三步，检查设备在总线上的位置。例如，一个USB设备位于根集线器第一端口连接的次集线口的第三个端口上。这种拓扑结构不会改变，即便机器重新启动总线号改变，除非用户物理上移动了设备。如果总线上的拓扑位置和用户提供的位置相匹配，请求的名称被指定给设备。</p>
<p>第四步是一个简单的字符串替换。如果设备的内核的名字在这里指定的名称相匹配，请求的新名称将用于设备。如果用户总是知道将具有相同的内核名称，但希望名称不同的东西，这是有用的。</p>
<p>第五步是捕捉所有落下的步骤。如果前面的步骤都未能提供此设备的名称，默认的内核的名称将被用于此设备。对于大多数系统中的设备，这是将要使用的规则，因为它匹配在Linux系统上没有devfs的或udev的命名的设备。</p>
<p>下图表显示了一个namedev配置文件例子。这个配置文件说明了如何替换默认的内核命名方案的四种不同的替换方式。</p>
<ul>
<li>LABEL: 前两个条目显示如何根据设备标识（序列号）指定设备名；</li>
<li>NUMBER: 第三和第四个条目显示如何避开（override）总线探测顺序的影响，根据总线ID来命名设备；</li>
<li>TOPOLOGY: 第五和第六项显示如何根据USB的拓扑结构指定的设备名称；</li>
<li>REPLACE: 第七项演示了如何做一个简单的名称替换。</li>
</ul>
<blockquote>
<p># USB Epson printer to be called lp_epson<br />
LABEL, BUS=&#8221;usb&#8221;, serial=&#8221;HXOLL0012202323480&#8243;, NAME=&#8221;lp_epson&#8221;<br />
# USB HP printer to be called lp_hp,<br />
LABEL, BUS=&#8221;usb&#8221;, serial=&#8221;W09090207101241330&#8243;, NAME=&#8221;lp_hp&#8221;<br />
# sound card with PCI bus id 00:0b.0 to be the first sound card<br />
NUMBER, BUS=&#8221;pci&#8221;, id=&#8221;00:0b.0&#8243;, NAME=&#8221;dsp&#8221;<br />
# sound card with PCI bus id 00:07.1 to be the second sound card<br />
NUMBER, BUS=&#8221;pci&#8221;, id=&#8221;00:07.1&#8243;, NAME=&#8221;dsp1&#8243;<br />
# USB mouse plugged into the third port of the first hub to be<br />
# called mouse0<br />
TOPOLOGY, BUS=&#8221;usb&#8221;, place=&#8221;1.3&#8243;, NAME=&#8221;mouse0&#8243;<br />
# USB tablet plugged into the second port of the second hub to be<br />
# called mouse1<br />
TOPOLOGY, BUS=&#8221;usb&#8221;, place=&#8221;2.2&#8243;, NAME=&#8221;mouse1&#8243;<br />
# ttyUSB1 should always be called visor<br />
REPLACE, KERNEL=&#8221;ttyUSB1&#8243;, NAME=&#8221;visor&#8221;<br />
<strong>Figure 3: Example namedev configuration file</strong></p>
</blockquote>
<h3>3.2 libsysfs</h3>
<p>为了让更多的用户空间程序访问sysfs，而不仅仅是udev的项目，有必要有一个通用的API来访问sysfs中的设备信息。与将查询逻辑重复写入不同的项目内不同，将查询逻辑在sysfs之上分割出来为一个单独的库，将更有意义。另外，sysfs表征不同类型设备的方式没有标准，PCI设备与USB有不同的属性项，这也是创建一个用于查询设备信息的标准库接口的另一个原因。</p>
<p>目前版本的udev使用的最初版本的libsysfs，libsysfs代码库的开发正处于活跃阶段。</p>
<h3>3.3 udev</h3>
<p>udev负责沟通namedev和libsysfs库以完成设备的命名。当内核调用/sbin/hotplug时，udev被触发运行，udev是通过在/etc/hotplug.d/default目录中添加一个链接自身的符号链接来实现这种触发关联的。/sbin/hotplug通过搜索/etc/hotplug.d/default目录间接调用udev。</p>
<p>/sbin/hotplug 被内核调用的时候，“热插事件名”会被传给/sbin/hotplug ，另外，内核还通过环境变量传递更多的事件信息，例如事件类型（插入是拔出）、发生事件的设备类型（USB, PCI等），还有具体（在sysfs目录树的）哪个设备发生了事件。udev根据这些信息，调用namedev取得设备名，如果是添加新设备，udev调用libsysfs取得设备号，然后创建相应的设备文件；如果是移除设备，udev移除相应用设备文件。</p>
<h2>4 Enhancements</h2>
<p>有不少用户要求对udev进一步优化增强，这些优化可以添加到现有的udev实现。</p>
<p>很多用户空间程序希望能检测到新设备被添加或从系统中删除的事件。Gnome和KDE都想在系统添加新磁盘的时候添加一个新的图标，或者在USB Palm设备插入后启动一个同步程序。D-BUS项目的目标就是为应用程序使用消息实现彼此通信提供一种简单的方法。例如，udev在创建或移除设备文件后向系统广播一条D-BUS消息，所有侦听消息应用程序可以作出相应的动作。</p>
<p>目前，namedev使用一个非常简单的配置文件，创建一个简单的基于RAM的数据库，用于存储当前所有的设备信息和设备的命名规则。有人建议，将这个“数据库”转移到一个真实的，备份式存储类型的数据库，以持久化系统的状态，或者提供一个更复杂的命名方案给udev使用。</p>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/udev-userspace-implementation-of-devfs.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>关于I²C和SPI总线协议</title>
		<link>http://arttech.us/y-2011/introduction-to-i%c2%b2c-and-spi-protocols.html</link>
		<comments>http://arttech.us/y-2011/introduction-to-i%c2%b2c-and-spi-protocols.html#comments</comments>
		<pubDate>Sat, 02 Jul 2011 14:44:07 +0000</pubDate>
		<dc:creator>刘 建文</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[8051]]></category>
		<category><![CDATA[数字电子]]></category>
		<category><![CDATA[数据通信]]></category>

		<guid isPermaLink="false">http://arttech.us/?p=478</guid>
		<description><![CDATA[译自：《Introduction to I²C and SPI protocols》 刘建文略译 I²C vs SPI 现今，在低端数字通信应用领域，我们随处可见I²C (Inter-Integrated Circuit) 和 SPI (Serial Peripheral Interface)的身影。原因是这两种通信协议非常适合近距离低速芯片间通信。Philips（for I²C）和Motorola（for SPI） 出于不同背景和市场需求制定了这两种标准通信协议。 I²C 开发于1982年，当时是为了给电视机内的CPU和外围芯片提供更简易的互联方式。电视机是最早的嵌入式系统之一，而最初的嵌入系统是使用内存映射（memory-mapped I/O）的方式来互联微控制器和外围设备的。要实现内存映射，设备必须并联入微控制器的数据线和地址线，这种方式在连接多个外设时需大量线路和额外地址解码芯片，很不方便并且成本高。 为了节省微控制器的引脚和和额外的逻辑芯片，使印刷电路板更简单，成本更低，位于荷兰的Philips实验室开发了 ‘Inter-Integrated Circuit’，IIC 或 I²C ，一种只使用二根线接连所有外围芯片的总线协议。最初的标准定义总线速度为100kbps。经历几次修订，主要是1995年的400kbps，1998的3.4Mbps。 有迹象表明，SPI总线首次推出是在1979年，Motorola公司将SPI总线集成在他们第一支改自68000微处理器的微控制器芯片上。SPI总线是微控制器四线的外部总线（相对于内部总线）。与I²C不同，SPI没有明文标准，只是一种事实标准，对通信操作的实现只作一般的抽象描述，芯片厂商与驱动开发者通过data sheets和application notes沟通实现上的细节。 SPI 对于有经验的数字电子工程师来说，用SPI互联两支数字设备是相当直观的。SPI是种四根信号线协议（如图）： SCLK: Serial Clock (output from master); MOSI; SIMO: Master Output, Slave Input (output from master); MISO; SOMI: Master Input, Slave [...]]]></description>
			<content:encoded><![CDATA[<div class="postmeta">
<dl>
<dd>
<div class="summary">
<ul>
<li>译自：<a href="http://www.byteparadigm.com/kb/article/AA-00255/22/Introduction-to-SPI-and-IC-protocols.html" target="_blank">《Introduction to I²C and SPI protocols》</a></li>
<li>刘建文略译</li>
</ul>
</div>
</dd>
</dl>
</div>
<h2>I²C vs SPI</h2>
<p>现今，在低端数字通信应用领域，我们随处可见I²C (Inter-Integrated Circuit) 和 SPI (Serial Peripheral Interface)的身影。原因是这两种通信协议非常适合近距离低速芯片间通信。Philips（for I²C）和Motorola（for SPI） 出于不同背景和市场需求制定了这两种标准通信协议。</p>
<p>I²C 开发于1982年，当时是为了给电视机内的CPU和外围芯片提供更简易的互联方式。电视机是最早的嵌入式系统之一，而最初的嵌入系统是使用内存映射（memory-mapped I/O）的方式来互联微控制器和外围设备的。要实现内存映射，设备必须并联入微控制器的数据线和地址线，这种方式在连接多个外设时需大量线路和额外地址解码芯片，很不方便并且成本高。</p>
<p>为了节省微控制器的引脚和和额外的逻辑芯片，使印刷电路板更简单，成本更低，位于荷兰的Philips实验室开发了 ‘Inter-Integrated Circuit’，IIC 或 I²C ，一种只使用二根线接连所有外围芯片的总线协议。最初的标准定义总线速度为100kbps。经历几次修订，主要是1995年的400kbps，1998的3.4Mbps。<span id="more-478"></span></p>
<p>有迹象表明，SPI总线首次推出是在1979年，Motorola公司将SPI总线集成在他们第一支改自68000微处理器的微控制器芯片上。SPI总线是微控制器四线的外部总线（相对于内部总线）。与I²C不同，SPI没有明文标准，只是一种事实标准，对通信操作的实现只作一般的抽象描述，芯片厂商与驱动开发者通过data sheets和application notes沟通实现上的细节。</p>
<h2>SPI</h2>
<p>对于有经验的数字电子工程师来说，用SPI互联两支数字设备是相当直观的。SPI是种四根信号线协议（如图）：</p>
<ul>
<li>SCLK: Serial Clock (output from master);</li>
<li>MOSI; SIMO: Master Output, Slave Input (output from master);</li>
<li>MISO; SOMI: Master Input, Slave Output (output from slave);</li>
<li>SS: Slave Select (active low, output from master).</li>
</ul>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-01.jpg"><img class="aligncenter size-full wp-image-479" title="Intro-to-SPI-and-IC-01" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-01.jpg" alt="" width="557" height="520" /></a></p>
<p>SPI是［单主设备（ single-master ）］通信协议，这意味着总线中的只有一支中心设备能发起通信。当SPI主设备想读/写［从设备］时，它首先拉低［从设备］对应的SS线（SS是低电平有效），接着开始发送工作脉冲到时钟线上，在相应的脉冲时间上，［主设备］把信号发到MOSI实现“写”，同时可对MISO采样而实现“读”，如下图：</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-02.jpg"><img class="aligncenter size-full wp-image-480" title="Intro-to-SPI-and-IC-02" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-02.jpg" alt="" width="597" height="487" /></a></p>
<p>SPI有四种操作模式——模式0、模式1、模式2和模式3，它们的区别是定义了在时钟脉冲的哪条边沿转换（toggles）输出信号，哪条边沿采样输入信号，还有时钟脉冲的稳定电平值（就是时钟信号无效时是高还是低）。每种模式由一对参数刻画，它们称为时钟极（clock polarity）CPOL与时钟期（clock phase）CPHA。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-03.jpg"><img class="aligncenter size-full wp-image-481" title="Intro-to-SPI-and-IC-03" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-03.jpg" alt="" width="793" height="536" /></a></p>
<p>［主从设备］必须使用相同的工作参数——SCLK、CPOL 和 CPHA，才能正常工作。如果有多个［从设备］，并且它们使用了不同的工作参数，那么［主设备］必须在读写不同［从设备］间重新配置这些参数。</p>
<p>以上SPI总线协议的主要内容。SPI不规定最大传输速率，没有地址方案；SPI也没规定通信应答机制，没有规定流控制规则。事实上，SPI［主设备］甚至并不知道指定的［从设备］是否存在。这些通信控制都得通过SPI协议以外自行实现。例如，要用SPI连接一支［命令-响应控制型］解码芯片，则必须在SPI的基础上实现更高级的通信协议。</p>
<p>SPI并不关心物理接口的电气特性，例如信号的标准电压。在最初，大多数SPI应用都是使用间断性时钟脉冲和以字节为单位传输数据的，但现在有很多变种实现了连续性时间脉冲和任意长度的数据帧。</p>
<h2>I²C</h2>
<p>与SPI的单主设备不同，I²C 是多主设备的总线，I²C没有物理的芯片选择信号线，没有仲裁逻辑电路，只使用两条信号线—— ‘serial data’ (SDA) 和 ‘serial clock’ (SCL)。I²C协议规定：</p>
<ul>
<li>第一，每一支IIC设备都有一个唯一的七位设备地址；</li>
<li>第二，数据帧大小为8位的字节；</li>
<li>第三，数据（帧）中的某些数据位用于控制通信的开始、停止、方向（读写）和应答机制。</li>
</ul>
<p>I²C 数据传输速率有标准模式（100 kbps）、快速模式（400 kbps）和高速模式（3.4 Mbps），另外一些变种实现了低速模式（10 kbps）和快速+模式（1 Mbps）。</p>
<p>物理实现上，I²C 总线由两根信号线和一根地线组成。两根信号线都是双向传输的，参考下图。I²C协议标准规定发起通信的设备称为主设备，主设备发起一次通信后，其它设备均为从设备。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-04.jpg"><img class="aligncenter size-full wp-image-482" title="Intro-to-SPI-and-IC-04" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-04.jpg" alt="" width="578" height="418" /></a></p>
<p>I²C 通信过程大概如下。首先，主设备发一个START信号，这个信号就像对所有其它设备喊：请大家注意！然后其它设备开始监听总线以准备接收数据。接着，主设备发送一个7位设备地址加一位的读写操作的数据帧。当所设备接收数据后，比对地址自己是否目标设备。如果比对不符，设备进入等待状态，等待STOP信号的来临；如果比对相符，设备会发送一个应答信号——ACKNOWLEDGE作回应。</p>
<p>当主设备收到应答后便开始传送或接收数据。数据帧大小为8位，尾随一位的应答信号。主设备发送数据，从设备应答；相反主设备接数据，主设备应答。当数据传送完毕，主设备发送一个STOP信号，向其它设备宣告释放总线，其它设备回到初始状态。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-05.jpg"><img class="aligncenter size-full wp-image-483" title="Intro-to-SPI-and-IC-05" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-05.jpg" alt="" width="693" height="456" /></a></p>
<p>基于I²C总线的物理结构，总线上的START和STOP信号必定是唯一的。另外，I²C总线标准规定SDA线的数据转换必须在SCL线的低电平期，在SCL线的高电平期，SDA线的上数据是稳定的。</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-06.jpg"><img class="aligncenter size-full wp-image-484" title="Intro-to-SPI-and-IC-06" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-06.jpg" alt="" width="569" height="453" /></a></p>
<p>在物理实现上，SCL线和SDA线都是漏极开路（open-drain），通过上拉电阻外加一个电压源。当把线路接地时，线路为逻辑0，当释放线路，线路空闲时，线路为逻辑1。基于这些特性，IIC设备对总线的操作仅有“把线路接地”——输出逻辑0。</p>
<p>I²C总线设计只使用了两条线，但相当优雅地实现任意数目设备间无缝通信，堪称完美。我们设想一下，如果有两支设备同时向SCL线和SDA线发送信息会出现什么情况。</p>
<p>基于I²C总线的设计，线路上不可能出现电平冲突现象。如果一支设备发送逻辑0，其它发送逻辑1，那么线路看到的只有逻辑0。也就是说，如果出现电平冲突，发送逻辑0的始终是“赢家”。</p>
<p>总线的物理结构亦允许主设备在往总线写数据的同时读取数据。这样，任何设备都可以检测冲突的发生。当两支主设备竞争总线的时候，“赢家”并不知道竞争的发生，只有“输家”发现了冲突——当它写一个逻辑1，却读到0时——而退出竞争。</p>
<h3>10位设备地址</h3>
<p>任何IIC设备都有一个7位地址，理论上，现实中只能有127种不同的IIC设备。实际上，已有IIC的设备种类远远多于这个限制，在一条总线上出现相同的地址的IIC设备的概率相当高。为了突破这个限制，很多设备使用了双重地址——7位地址加引脚地址（external configuration pins）。 I²C 标准也预知了这种限制，提出10位的地址方案。</p>
<p>10位的地址方案对 I²C协议的影响有两点：</p>
<ul>
<li>第一，地址帧为两个字节长，原来的是一个字节；</li>
<li>第二，第一个字节前五位最高有效位用作10位地址标识，约定是“11110”。</li>
</ul>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-07.jpg"><img class="aligncenter size-full wp-image-485" title="Intro-to-SPI-and-IC-07" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-07.jpg" alt="" width="628" height="316" /></a></p>
<p>除了10位地址标识，标准还预留了一些地址码用作其它用途，如下表：</p>
<p><a href="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-08.jpg"><img class="aligncenter size-full wp-image-486" title="Intro-to-SPI-and-IC-08" src="http://arttech.us/wp-content/uploads/2011/07/Intro-to-SPI-and-IC-08.jpg" alt="" width="428" height="361" /></a></p>
<h3>时钟拉伸</h3>
<p>在 I²C 通信中，主设备决定了时钟速度。因为时钟脉冲信号是由主设备显式发出的。但是，当从设备没办法跟上主设备的速度时，从设备需要一种机制来请求主设备慢一点。这种机制称为时钟拉伸，而基于I²C结构的特殊性，这种机制得到实现。当从设备需要降低传输的速度的时候，它可以按下时钟线，逼迫主设备进入等待状态，直到从设备释放时钟线，通信才继续。</p>
<h3>高速模式</h3>
<p>原理上讲，使用上拉电阻来设置逻辑1会限制总线的最大传输速度。而速度是限制总线应用的因素之一。这也说明为什么要引入高速模式（3.4 Mbps）。在发起一次高速模式传输前，主设备必须先在低速的模式下（例如快速模式）发出特定的“High Speed Master”信号。为缩短信号的周期和提高总线速度，高速模式必须使用额外的I/O缓冲区。另外，总线仲裁在高速模式下可屏蔽掉。更多的信息请参与总线标准文档。</p>
<h2>I²C vs SPI: 哪位是赢家？</h2>
<p>我们来对比一下I²C 和 SPI的一些关键点：</p>
<h3>第一，总线拓扑结构/信号路由/硬件资源耗费</h3>
<p>I²C 只需两根信号线，而标准SPI至少四根信号，如果有多个从设备，信号需要更多。一些SPI变种虽然只使用三根线——SCLK, SS和双向的MISO/MOSI，但SS线还是要和从设备一对一根。另外，如果SPI要实现多主设备结构，总线系统需额外的逻辑和线路。用I²C 构建系统总线唯一的问题是有限的7位地址空间，但这个问题新标准已经解决——使用10位地址。从第一点上看，I²C是明显的大赢家。</p>
<h3>第二，数据吞吐/传输速度</h3>
<p>如果应用中必须使用高速数据传输，那么SPI是必然的选择。因为SPI是全双工，I²C 的不是。SPI没有定义速度限制，一般的实现通常能达到甚至超过10 Mbps。I²C 最高的速度也就快速+模式（1 Mbps）和高速模式（3.4 Mbps），后面的模式还需要额外的I/O缓冲区，还并不是总是容易实现的。</p>
<h3>第三，优雅性</h3>
<p>I²C 常被称更优雅于SPI。公正的说，我们更倾向于认为两者同等优雅和健壮。</p>
<p>I²C的优雅在于它的特色——用很轻盈的架构实现了多主设备仲裁和设备路由。但是对使用的工程师来讲，理解总线结构更费劲，而且总线的性能不高。</p>
<p>SPI的优点在于它的结构相当的直观简单，容易实现，并且有很好扩展性。SPI的简单性不足称其优雅，因为要用SPI搭建一个有用的通信平台，还需要在SPI之上构建特定的通信协议软件。也就是说要想获得SPI特有而IIC没有的特性——高速性能，工程师们需要付出更多的劳动。另外，这种自定的工作是完全自由的，这也说明为什么SPI没有官方标准。</p>
<p>I²C和SPI都对低速设备通信提供了很好的支持，不过，SPI适合数据流应用，而I²C更适合“字节设备”的多主设备应用。</p>
<h2>小结</h2>
<p>在数字通信协议簇中，I²C和SPI常称为“小”协议，相对Ethernet, USB, SATA, PCI-Express等传输速度达数百上千兆字节每秒的总线。但是，我们不能忘记的是各种总线的用途是什么。“大”协议是用于系统外的整个系统之间通信的，“小”协议是用于系统内各芯片间的通信，没有迹象表明“大”协议有必要取代“小”协议。I²C和SPI的存在和流行体现了“够用就好”的哲学。回应文首，I²C和SPI如此的流行，它是任何一位嵌入式工程师必备的工具。</p>
]]></content:encoded>
			<wfw:commentRss>http://arttech.us/y-2011/introduction-to-i%c2%b2c-and-spi-protocols.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

