E100活动 数据分析师考试科目如何轻松搞定E100

后使用快捷导航没有帐号?
查看: 236488|回复: 122
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
2楼:7-10级所有车辆
3楼:8-10级MT
4楼:8-10级HT
5楼:8-10级TD
6楼:8-10级SPG
7楼:7-8级LT
概述:由于游戏内车辆性能并非开放商所说的完全平衡,造成了不同车辆的明显差异(事实上依毛子数值策划的功力根本就做不到)
而并不怎么精明的游戏主策又在各等级车辆中加入了新手难以驾驭的车辆,比如M系T57线中的T71,结合游戏中坑爹的分房规则,
其结果就是高玩手里和坑货手里,同一辆车的表现差距非常大(OP点较高,详见另一篇帖子《什么车最适合你,数据分析高级房常见坦克的战斗表现》)
附件已加入excel文件供大家查阅
涉及到比赛用车和“屠幼”车,都将不予点评
(56.08 KB, 下载次数: 2909)
16:14:45 上传
下载次数: 2909
下载积分: 金钱 -5
(左键点击下载)
本帖最后由 蹲坑有经验 于
18:47 编辑
&看了第一段就不想看了,因为目前9.3的50B完胜T57。&
:尼玛!这游戏是毛子搞得!你玩任何车都是支持毛子。和小鬼子没一毛钱关系!哥就喜欢STB,玩的多,认为他性能强,并不代表现实中支持鬼子!&
&日系再好大爷我也不玩&
&看玩了,我走的线比较少,121线到34-2,4202线到百夫长7,地狱猫线到 T25/2.113线到110停止,只有一条线貌似还好,就是A-43刚买没多久,还没升级。唉.&
&还有,我明白为什么95E6是活动送的了~~果断当模型了~~&
&你平均战力和最高战力应该放一起,因为前面的车特别是十级车,战斗力第一的水平差不多,才能说明车的好坏~XVM上排版本来就不合理~&
总评分:&金钱 + 66&
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
2楼:<font color="#ff级所有车辆纵向排名
14:20:20 上传
本帖最后由 蹲坑有经验 于
16:11 编辑
&贴出你的10级车场均,胜率,再来发这种帖子!貌似你发的很多车你都没玩过吧?&
&贴出你的10级车场均,胜率,再来发这种帖子!貌似你发的很多车你都没玩过吧?&
&我只想说,请贴出你自己的10级车的xvm&
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
3楼:8-10级MT
STB-1:目前性能非常优异,加之整体水平较为出色,让其平均战力值排名所有车辆首位。
25t:因其车身小、隐蔽好、MT里最高速度,以及夸张的爆发能力,不仅是各类比赛中MT的第一选择,更然是最高战力值保持者。整体排名没拿到第1,要好好“感谢”国服的一大票25t玩家,不足9000官方战斗等级的玩家难以理解其60%的功力。
140:相对于62A而言,头皮脆和车身易起火是缺点。但速度和关键的俯角为其增分不少,两个优点能被高玩放大到极致。
62A:因其急速和加速性能落后于140,并且少了非常关键的1度俯角(62A是5度,140是6度),既生瑜何生亮用在它身上最为合适不过了。
50M:此车的出色发挥更多依赖的是其靠谱的首上装甲,联队比赛里更是替代了110E5而扮演了HT的角色。MT的速度和HT的装甲相结合,在野队里常常能如鱼得水。
豹1:各项性能均不差,唯一缺点是99%的玩家因其打哪哪穿的装甲,而难以驾驭该车。
4202、M48巴顿、M60、121:都是非主流车,在隐蔽、装甲(包括正面击穿坏零件死成员)、火控、仰俯角、急速、加速、DPM中的某一项或几项有较大硬伤,如果开发商不对其进行改进或替换,无论是高玩还是坑货,都无法出色发挥,因为其本身就是坑车。
是不是很奇怪的漏掉了T95E6?我都懒得说它了。。。。
六一式:作为过渡车辆的六一式竟然超越了传统三强T54、E50和M46巴顿排在了榜首非常让人意外。初以为是R国MT玩家整体水平较高,待我查了相关资料后却发现,开发商为了吸引玩家点亮并购买该车,竟然给了其10度俯角和银币弹258穿深的APCR,2800的DPM也足以让众多10级车汗颜,太不要脸了简直。
百夫长Mk.VII:整体排名第二的百夫长Mk.VII虽拥有令人艳羡的268穿深APCR银币弹,但因其单发装填时间足足比六一式慢了1.65秒(DPM少了接近500,几乎是9级车和10级车的差距),而急速上更比其少了关键的8点,稳居9级单发MT里最差DPM的身份,货比货得扔就是这么个道理。
E50: 因其靠谱的装甲和不俗的火力排名第三属于情理之中,九级房里能随意连打带撞、搓圆捏扁吊打59,想排名靠后都不容易。
430V2:没其它特点,就是扁,长得扁。冷兵器打架要靠人高马大,但在WOT世界里身板矮却成为了最大的优势,此车如果要说明显缺点,只有4度俯角和219穿深的AP弹单发伤害仅320。
T54:作为传统三强,T54依旧具备了优秀的综合性能,让其拥有9级MT里第二战力保持者的身份吸引了众多慕名者,但同时吸引的大票坑货玩家拉低了其平均战力值。
T54E1:弹夹车的爆发力毋须多言,你可能敢于和600血的其它车换血,但不一定敢和同样血量的T54E1较劲,谁比谁疼还不一定呢。
M46巴顿:此车也是老三强,作为加强后的车让其拥有了9级MT里最高DPM和最高视野,但脆弱的装甲和218穿深的AP弹是硬伤,急速上也比T54差了一大截。
WZ120、豹1原型、40t:就不想多评价了,不加强就是非主流坑车,实际上百夫长Mk.VII也在此列。
因特殊性不做评价
过渡车辆没有特别评价,都会在国服59房里挨个被吊打
14:24:30 上传
本帖最后由 蹲坑有经验 于
15:55 编辑
:百7太吃队友了。遇到不靠普的队友。。。不说了,说多了都是泪~~~~~~带不起节奏,抗不起伤害,输出还要靠队友,唉~~~~老大一把泪。&
:继续加油吧,这车我只能说永远在侧翼,真要正面硬刚你不一定绕得过59和轻坦,但是侧面支援(偷袭),10机车都没你强(会走位队友不坑的情况下)&
:我还在爬白板的坑,好可怜&
:对新手太残忍,就是1w场的老鸟也不一定能驾驭,车是非主流,玩法更非主流。性格如果不合绝对玩不来,有时赢了赚钱了还是感觉不爽&
&T95E到手之后6盘出M...场均3100的路过......表示好像没你说的那么悲惨...120的首上和较靠谱的炮台加上俯角已经可以恶心到很多人了。&
&问下,百夫长Mk.VII整体 第二,为什么又说他 是坑车呢&
:430不知是不是因为玩家太少的关系,如果你稍微注意一点,会发现430在顶级玩家手里明显弱于同级MT&
&感觉7级黑豹面对59还有的拼。。。毕竟面对有穿身的速射炮,59的弹药架和油箱也难保不出问题。。。&
:430遇到的太少,基友们全都没那破车,以前装填时间太长,现在加强后也没强多少,点评显得不怎么中肯&
&T95E6至少还被提到,430工程表示早被忘到十八里铺去了。&
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
<font color="#楼:<font color="#ff级HT
T57:毫不意外因其弹夹车的高爆发力,M103的装甲和机动能力(大部分地图甚至优于IS-7)位居平均战力值榜首,坑货能玩,高玩更能以其优异性能扭转整体战局。
215B:拥有2500的血量和MT级别的装弹速度,火力方面明显超越了110E5。这台战场绞肉机如果不是因后置炮塔的硬伤,那简直就是个BUG。
50B:排名第三同样得益于弹夹车的身份,MT级的机动能力能让它在战场里发挥奇效。
E100:能超过110E5排名HT第4我也是醉了,只能分析此车依靠不俗的单发火力和靠谱的装甲更适合新手玩家。
110E5:真的是辆强势车,待你官方战斗力8500以上时方能理解。
VK7201、IS-4、鼠式、IS-7:较为适合新手玩家,高玩手里无法用其扭转战局
113:坑车。
屈服者:在M103被一次次锉刀后,终于坐上了9级HT第一位置,不夸张的说,如果M103火力是9级HT,那屈服者则是9.5级。
ST-I:高单发火力和非常靠谱的装甲,让它更适合新手玩家来对抗新手玩家。
E75:凭借较为靠谱的装甲能力,可以在很多地形上诱发不了解角度和等效装甲关系的玩家开炮,哪怕面对10级车火力,350米外的E75还真不是那么容易击穿,至于在9级房里吊打59、IS-6和WZ111,那是日常好吗,此车综合性能非常适合新手玩家。
111 I-IV:高机动、高单发伤害能让其在某些地图的某些位置发挥奇效,装甲在9级房里还行,10级房里是纸。
M103:增加装填时间,砍。改变装甲模型,砍。降火控,再砍。曾经此车电信区排名前20的,不想多说什么了,全是泪。
4502P:最近版本真的加强不少,和E75一样,首下能忽悠不少人向其开炮,适合新手玩家。
IS-8:性能在理论上和111 I-IV非常接近的车,或许是众多打算迈向IS-7的新手大军拉低了其平均水平。
AMX50 120:长装填时间能达到32秒左右,但问题短装填是万恶的3.33秒,和大哥50B有明显差距外,和M系一水的2秒短装填更是不能比。开炮后扩圈也比50B大了很多,过渡车。
因特殊性不做评价
因特殊性不做评价
16:00:39 上传
本帖最后由 蹲坑有经验 于
13:44 编辑
&kv-5胜率最高,但是我打这个车真的不行,看样子我要好好玩玩kv5了&
&屈服者9级第一,我就呵呵,ST-I胜率50.6,小老鼠50.9,屈服者48.6,也就中间水平,重坦还是多看看胜率吧,火力还没强到可以弥补其他缺陷的程度。&
&LZ确定是E75吊打三棍?我怎么感觉是反过来。。。&
:感谢提醒,本来想留到最后说的,过渡车&
&为啥9级里少了50 120&
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
5楼:8-10级TD
E100 WT:个人仍把其排在第一,即便是砍了血量又砍了一发弹夹备弹后。被点亮后火炮首选轰炸目标,所有车第一攻击目标,仇恨值就能说明一切。坑货手里都能打出2000+的场均输出,不知道开发商在想什么。
263:好车,但用好的人真不多。装甲对于新手比较靠谱,其实它的弱点有xx、xx、xx和xx。
AMX50 福煦 155:独创名词福三炮,WT之前的火炮首选轰炸目标(现在也至少排第2),砍了性能后坑货玩不转,高玩手里同样逆天。经过49楼提醒并查阅数据后更正,此车被砍后,已不再BUG。
T110E3:前脸装甲相当靠谱,头上大包301-449的等效装甲,却能吸引不少新手玩家的炮弹,这车如果有268的机动,会是另一个BUG,除了速度,隐蔽是它最大缺点。
268:机动、隐蔽、高穿深依旧强势,和福三炮一样,nerf后的268剥离了坑货继续玩它的欲望,高玩手里的268依旧出彩。
183:转角遇到WT和183,那都是遇到真爱。此车应该把装备配件和学习技能后的装填时间砍到28-29秒,目前过强,给了坑货太高的容错率。
T110E4:侧面0俯角,大车身很好穿,头上大包更容易穿。和E100 TD相比,唯一优势可能就是其180度转得奇慢的炮塔。
E100 TD:百突目前和T110E4并倒数第一没什么悬念的,好几个8-9级车完爆这两货。
IV WT:就一句话,和莱茵同属BUG车。
704:老牌TD,内测时期的正面装甲甚至超越老鼠,在无数版本更新后的今天依然拥有诡异的跳蛋,不俗的机动配合高穿深高单发伤害,此车适合新手同时也适合保留。
T95:装甲太靠谱了,哪怕在10级房都是令人头疼的存在,它不会是任何人第一个想要攻击的目标。但龟速让它顺风局吃不了肉,逆风局又捡不完的肥皂,请允许我赐它“肥皂王”的美号。想体验明星被围观的节奏,玩它5000场。
T30:好车,有炮塔、有穿深、有伤害、有俯角,综合来看超越了T110E4。
A39土龟:注意是所有车里最高的4000+DPM,任何10级车都不敢在无遮蔽的平原和其对炮,缺点如其名是龟速,隐藏缺点是此车所有方向都能被火炮的AP弹很好击穿(你对游戏理解不够的情况下,火炮真的不要随便打AP)。
猎虎:藏住首下的猎虎才是合格的好猎虎,高大上的车身和孱弱的机动,向来是火炮最爱,因为侧面经常能被火炮HE一发秒掉。有了D系新TD莱茵线后,更是不值得保留。
Su-122-54:前脸装甲还行,超越大部分10级车的DPM和9级MT的机动,加上优秀的隐蔽,真的是台好车。
AMX50 福煦:D系莱茵线之前的神车,在被砍了装甲后和火控后,沦落到连某些8级TD都不如的地步了,这车就是如此,buff一下过强,nerf一下太弱。
RhB WT:莱茵不排第一没天理,伤害高、装填少、隐蔽好、车体小、有炮塔,上砍10级怪兽房,下斩8级幼稚园,如果连它都玩不好,建议回5级房补课或去医院开补脑片,残酷的数据事实,大部分人需要吃药。。。
猎豹II:10级车的DPM、10级MT的机动,这车一直是被我低估了。要说缺点,炮管都能被一发击穿。
ISU-152:外号季老师的传统强势车,286穿750单发伤害教你做人,缺点是装甲不靠谱,远距离炮弹经常不科学的下坠造成0伤断带。
斐迪南:废铁男曾经是内测时期的名车,综合性能甚至超越顶头上司猎虎,在开发商的锉刀下已沦为过渡车。
AT-15、T28原型、T28、SU-101:都是过渡车辆,不值得保留。
AMX1948:最差8级TD,没有之一。
猎虎88:金币车,顺风局打钱神器,逆风局挨打神器。
BTW:理解不了游戏数值策划在想什么,有了莱茵RhB WT,其它8级TD都没有保留必要了,差距太大,莱茵是9级房里的10级车,自己去理解。
因特殊性不做评价
17:29:28 上传
本帖最后由 蹲坑有经验 于
21:15 编辑
&莱茵隐蔽 火力……除了装甲能让一些人诟病之外,几乎找不出任何缺点。。。&
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
6楼:8-10级SPG
屈服者GC:外号高潮,炸的人高潮,被炸的也高潮。有点像羞羞的R国电影台词,大家一起“去了”。实际这台车因为装填时间和机动能力,并不是一辆十分合格的SPG,无法满足高强度对抗的比赛节奏,野队娱乐玩玩还行,毕竟游戏内最强的落弹抛物线,和仅次于T92的炮弹爆炸半径,并非摆设。
查狄伦 155 58:能把这台烂车玩到SPG总排名第2,也能从侧面说明国服火炮平均水平到底有多坑。优点是360度炮塔和优异的缩圈速度,缺点是高级火炮里最差的炮弹爆炸半径和炮弹穿深。75秒的装填速度更是让它无法在开局时支援MT和LT,顶级房里宁愿给8级SPG点灯,也不愿意给查火炮点灯,个人对此车印象极差。
261:最强火炮,和25t一样是联队比赛的唯一选择,高装填速度+高炮弹飞行速度+不俗的机动能力,能把神炮玩到SPG总排名第3,国服火炮平均水平到底有多坑?这些人不光意识和技术有问题,甚至连智商都有些不及格,老想着打AP中大奖。
T92、E炮:本着既生瑜何生亮的原则,机动和装填速度,没有出现在游戏里的必要。
3805:略微超越M53/55的装填速度和火控,但机动、射界又明显弱于M53/55,九级神炮排名里第2。
M53/55:曾经的M40/43换了名字还增加了射界,神炮。
查狄伦 155 55:装填速度上属于小号261,能以机动达到很多关键位置进行支援,唯一缺点是炮弹爆炸半径过小,未击穿情况下均伤过低,可长期保留。
212、虎炮:机动太弱,属于不合格火炮,没有出现在游戏里的必要。
M40/43:从次顶级火炮退位下来的火炮,为了适应8级火炮身份,砍了机动和装填速度,但保留了M系高穿深、高爆炸半径的特点,综合性能甚至不输某些10级火炮。
洛林155 51:优异的机动性能和装填时间满足所有战局需要,缺点是法系火炮的爆炸半径,可娱乐保留。
FV207:机动能力很一般,火力、穿深、爆炸半径和F系9-10级火炮看齐,过渡车。
SU-14-2和Gw 虎(P):机动、射界太差,没有出现在游戏里的必要。
因特殊性不做评价
18:22:25 上传
本帖最后由 蹲坑有经验 于
15:57 编辑
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
7楼:7-8级LT(因特殊性只做简短说明)
Ru 251:目前版本的比赛用车,不砍就是BUG。
T49:目前版本的比赛第二用车,90炮和152炮各有特点,90炮金币弹需要加强飞行速度。
T-54 LT:过渡车,不值得保留。
AMX 1390:目前版本的比赛用车,特殊用途。
WZ132:已被251和T49取代,野队可以娱乐可保留。
M41沃克猛犬:BUG。
T71:BUG。
LTTB、62、WZ131、1375:和WZ132情况类似。&&
黑豹侦查车:坑车。
18:33:10 上传
本帖最后由 蹲坑有经验 于
18:33 编辑
&……62相当好用。&
&62是好车。。嗯。。。&
新人欢迎积分1 阅读权限80积分17289精华0UID帖子金钱54277 威望1
Lv.8, 积分 17289, 距离下一级还需 711 积分
UID帖子威望1 多玩草1506 草
8楼顶一个吗??
新人欢迎积分1 阅读权限125积分20238精华3UID帖子金钱54304 威望16
口嫌体正直
UID帖子威望16 多玩草8735 草
这个算是沙发?
你太傻,就不要抱怨别人不笨。
新人欢迎积分0 阅读权限40积分752精华0UID帖子金钱24880 威望0
Lv.4, 积分 752, 距离下一级还需 248 积分
UID帖子威望0 多玩草0 草
还要占楼吗???
我的菊花没了!
新人欢迎积分0 阅读权限70积分6032精华0UID帖子金钱35628 威望3
Lv.7, 积分 6032, 距离下一级还需 3968 积分
UID帖子威望3 多玩草769 草
还是我先占楼,向楼主学习
:别吓我,我是等楼主编辑帖子出来学习的&
&学习7连吧,坐等版主查水表。。。&
新人欢迎积分1 阅读权限50积分1125精华0UID帖子金钱2639 威望0
Lv.5, 积分 1125, 距离下一级还需 1375 积分
UID帖子威望0 多玩草0 草
蹲坑有经验 发表于
6楼 ccccccc
7连了&&哈哈&&等着版主来找你吧。。
:我在写资料&
&可是我木有看到帖。。&
&人家七连是占楼发帖~&
新人欢迎积分0 阅读权限50积分2010精华0UID帖子金钱7465 威望1
Lv.5, 积分 2010, 距离下一级还需 490 积分
UID帖子威望1 多玩草658 草
更新完毕,写了4个小时。
新人欢迎积分0 阅读权限60积分3098精华0UID4958997帖子金钱46318 威望0
Lv.6, 积分 3098, 距离下一级还需 1902 积分
UID4958997帖子威望0 多玩草265 草
不错,楼主辛苦了
新人欢迎积分0 阅读权限30积分246精华0UID帖子金钱460 威望0
Lv.3, 积分 246, 距离下一级还需 4 积分
UID帖子威望0 多玩草0 草
这个帖子有营养啊。。。
CPU:Intel E3-1230 V2
散热:利民HR02 Macho
SSD:三星840
内存:金士顿HyperX Genesisi,4GX2
显卡:华硕Strix GTX970
主板:华硕P8Z77-M M-ATX
电源:安钛克BP430
机箱:恩杰NZXT H2
显示器:Delll UltraSharp U2
365天!天天有你
连续签到1年即可获得
狗年新春勋章
参加2018狗年论坛相关APP活动有机会获得
2015论坛年度勋章
放肆青春,就要多玩
坦克世界功勋勋章
对坦克世界论坛做出卓越贡献的玩家
优秀写手勋章
优秀写手勋章
原创先锋勋章
原创先锋勋章
一路有你,感恩相伴
风雨同舟勋章
一路有你,多玩更精彩
原创视频勋章
原创视频勋章
马年新春勋章
手机APP马年迎春,马上有钱!
元宝专属一阶勋章。已绝版
需要金钱:1100
手机盒子客户端点击或扫描下载
Powered by&>&&>&&>&正文
战争雷霆D系重坦E100数据性能分析 E100打法战术详解
10:49:22 来源:官方论坛 作者:元首 编辑:苍梧君 浏览:loading
  侧身装甲75毫米较比与老鼠的180毫米/100毫米(上下两段)也是较为薄弱,但是在这里值得提一下的是E100侧身采用的裙甲有一定弧度增加了一定的跳弹几率,而除了这75毫米的裙甲之外,在它的车身内部还有一块120毫米的装甲这样的设计较比之D系其它车相对安全许多。
  它的内部成员方面,它是一个6人制的车和老鼠一样、2个装填手、车长、炮手、机枪手、驾驶员。
  E100炮弹方面因为和老鼠采用的是一样的炮弹所以他们炮弹也是相同。
  这里总结一样吧,E100在打法上基本上还是与老鼠差不多,也是需要摆姿势、炮塔保护好自己的两张“脸”,还需要注意自己地盘、地盘装甲较为薄弱以免被敌方打中失去动力,还有就是注意轰炸机了,和老鼠一样机动性不是很好所以E100和老鼠一样都是敌方轰炸机的首要攻击对象。
更多相关资讯请关注:
友情提示:支持键盘左右键“← →”翻页
用手机访问
扫一扫,手机浏览
相关新闻:
游戏制作:Gaijin Entertainment
游戏发行:腾讯游戏
游戏平台:PC/PS4
上市时间:
游戏特色:
《战争雷霆》1.55版本新飞机:飞燕丁与钟馗乙历史模式飞行性能点评。
战争雷霆德系坦克怎么样?战争雷霆德系轻型坦克35T在标准战斗下战力爆表,不愧是德系坦克,就算在游戏内也这么厉害。下面由小编带来的精彩击杀视频。
《战争雷霆》2015TGC还原空地联动战场,至强空战战斗,陆地决战,钢铁同盟,为战争集结火力全开!
战争雷霆为我们带来了真实的二战体验,真实的载具,高度还原的战场。下面就请大家一起来欣赏战争雷霆高清截图吧。
战争雷霆为我们带来了真实的二战体验,真实的载具,高度还原的战场。下面就请大家一起来欣赏战争雷霆高清截图吧。
由腾讯代理运营的《战争雷霆》25日开启集结内测,游戏新推出了火箭炮供玩家体验,小编也在第一时间为大家带来了一手截图。
《战争雷霆》“集结内测”是这款战争网游大作的国服首轮内测。此次测试以二战胜利终结,以载具全面进入“冷战”时代为主题。
战争雷霆增加了可开关的飞机驾驶舱盖、驾驶舱照明灯和更多可损坏坦克外挂部件在增加了画面的表现力,质量得到提升配置却没增加。
战争雷霆纪念815日本无条件投降70周年。日,日本政府正式无条件投降。战争雷霆》以和为镜,以战为鉴,向抗战英雄致敬!
战争雷霆都有什么飞机?战争雷霆是现阶段拥有目前最全面的战争载具网游,喜欢飞机或坦克的朋友不可错过的游戏,下面小编给大家带来战争雷霆飞机小合集。
战争雷霆类似的战争题材的游戏在产品形态上变得空前丰富,产品品质上的提升幅度也始终与动作游戏、角色扮演游戏一样处于第一梯队。
综合热点资讯
单机游戏下载宝骏E100新能源全国最低5.0折,最高优惠5.00万
新浪汽车大数据中心
摘要:新浪汽车通过近12个月销量价格的分析,用数据告诉大家宝骏E100新能源近期是否适合入手。
车型名称指导价优惠幅度折扣成交价优惠购车
车市信息变化频繁,具体售价请与当地经销商商谈
填写以下信息,轻松获取优惠价格
(您的个人信息我们将严格保密)
我已阅读并接受个人信息保护
2016款奥迪A3 1.4T自动Sportback 35TFSI进取型指导价:18.49万
&提交成功,请注意查收短信
查看该车系的用户还看了:
宝骏E100新能源口碑
来自33个用户
图表加载中...
评论车型:2017款宝骏E100智享版
性价比这个东西的话,我觉得也是按每个人自己的感觉来的,当然我自己是觉得这款车的性价比是非常高的,既买到我自己喜欢的东西了,而且价格也不是不能接受反而还很实惠,那这不是性价比高,又算是什么!
评论车型:2017款宝骏E100智享版
车子实际操控上手的时候感觉还是比较轻松的,感觉车子整体的反应速度也比较快,我感觉稍微有所欠缺的地方就是方向盘没有什么实感吧,感觉方向盘重量太轻了,但是车子小巧操控肯定还是不错的,像停车啊,倒车啊之类的这些方面都掌控的比较好。
评论车型:2017款宝骏E100智享版
1、深红色车身比较时尚,整体造型比较可爱前卫,我老婆机械盲,纯外观控,能让她动心的车,样子一般差不了。1、配置很高,有ESP心里踏实。车尺寸小,盲区比一般车小很多,功能按键都是傻瓜式操作,很容易上手,很适合女性开。3、补贴后的价格和其它同类车相比,还算合理。用车成本低,很爽
宝骏E100新能源走势图
图表加载中..
图表加载中..
5月销量(单位:辆)
6月成交价(单位:万)
竞品车型月销量
图表加载中..
注:以上车型资料供参考,欢迎报名垂询
宝骏E100新能源
9.39-10.99万元
底价将以短信的形式发送到您的手机
个人信息不会泄露给第三方
一个月不再弹出新车推荐!宝骏E100新能源全国新车4.68万起
新浪汽车大数据中心
摘要:新浪汽车通过近12个月销量价格的分析,用数据告诉大家宝骏E100新能源近期是否适合入手。
车型名称指导价优惠幅度折扣成交价优惠购车
车市信息变化频繁,具体售价请与当地经销商商谈
填写以下信息,轻松获取优惠价格
(您的个人信息我们将严格保密)
我已阅读并接受个人信息保护
2016款奥迪A3 1.4T自动Sportback 35TFSI进取型指导价:18.49万
&提交成功,请注意查收短信
查看该车系的用户还看了:
宝骏E100新能源口碑
来自33个用户
图表加载中...
评论车型:2017款宝骏E100智行版
性价比这个问题,我想不需要赘述了,持续的销量已经证明了,在这个价位好像也没有对手了吧。标配配置:刚开始也纠结高配,和我一起提车的朋友,经济实力比我强,他选的也是低配,作为一个代步车,该有的都有,对一个18年驾龄的老司机来说足够了。
评论车型:2017款宝骏E100智享版
按钮式操控,我觉得还挺有意思的,而且也非常容易上手,基本上没几天就挺顺手习惯的了。而且这车停车啊或者倒车啊都非常流畅。
评论车型:2017款宝骏E100智享版
我还挺喜欢这车的外观的,挺适合年轻人的,开在马路上也也挺回头率的。重点是用来吸引妹子,这车绝对是一把好手。
宝骏E100新能源走势图
图表加载中..
图表加载中..
6月销量(单位:辆)
6月成交价(单位:万)
竞品车型月销量
图表加载中..
注:以上车型资料供参考,欢迎报名垂询
宝骏E100新能源
9.39-10.99万元
底价将以短信的形式发送到您的手机
个人信息不会泄露给第三方
一个月不再弹出一、从网卡说起
这并非是一个网卡驱动分析的专门文档,只是对网卡处理数据包的流程进行一个重点的分析。这里以Intel的e100驱动为例进行分析。
大多数网卡都是一个PCI设备,PCI设备都包含了一个标准的配置寄存器,寄存器中,包含了PCI设备的厂商ID、设备ID等等信息,驱动
程序使用来描述这些寄存器的标识符。如下:
struct pci_device_id {
__u32 vendor,
/* Vendor and device ID or PCI_ANY_ID*/
__u32 subvendor,
/* Subsystem ID's or PCI_ANY_ID */
__u32 class, class_
/* (class,subclass,prog-if) triplet */
kernel_ulong_t driver_
/* Data private to the driver */
这样,在驱动程序中,常常就可以看到定义一个struct pci_device_id 类型的数组,告诉内核支持不同类型的
PCI设备的列表,以e100驱动为例:
#define INTEL_8255X_ETHERNET_DEVICE(device_id, ich) {\
PCI_VENDOR_ID_INTEL, device_id, PCI_ANY_ID, PCI_ANY_ID, \
PCI_CLASS_NETWORK_ETHERNET && 8, 0xFFFF00, ich }
static struct pci_device_id e100_id_table[] = {
INTEL_8255X_ETHERNET_DEVICE(0x1029, 0),
INTEL_8255X_ETHERNET_DEVICE(0x1030, 0),
INTEL_8255X_ETHERNET_DEVICE(0x1031, 3),
……/*略过一大堆支持的设备*/
在内核中,一个PCI设备,使用struct pci_driver结构来描述,
struct pci_driver {
struct list_
struct module *
const struct pci_device_id *id_
/* must be non-NULL for probe to be called */
(struct pci_dev *dev, const struct pci_device_id *id);
/* New device inserted */
void (*remove) (struct pci_dev *dev);
/* Device removed (NULL if not a hot-plug capable driver) */
(*suspend) (struct pci_dev *dev, pm_message_t state);
/* Device suspended */
(*resume) (struct pci_dev *dev);
/* Device woken up */
(*enable_wake) (struct pci_dev *dev, pci_power_t state, int enable);
/* Enable wake event */
void (*shutdown) (struct pci_dev *dev);
struct device_
struct pci_
因为在系统引导的时候,PCI设备已经被识别,当内核发现一个已经检测到的设备同驱动注册的id_table中的信息相匹配时,
它就会触发驱动的probe函数,以e100为例:
* 定义一个名为e100_driver的PCI设备
* 1、设备的探测函数为e100_
* 2、设备的id_table表为e100_id_table
static struct pci_driver e100_driver = {
.id_table =
e100_id_table,
e100_probe,
__devexit_p(e100_remove),
#ifdef CONFIG_PM
.suspend =
e100_suspend,
e100_resume,
.driver = {
.shutdown = e100_shutdown,
这样,如果系统检测到有与id_table中对应的设备时,就调用驱动的probe函数。
驱动设备在init函数中,调用pci_module_init函数初始化PCI设备e100_driver:
static int __init e100_init_module(void)
if(((1 && debug) - 1) & NETIF_MSG_DRV) {
printk(KERN_INFO PFX "%s, %s\n", DRV_DESCRIPTION, DRV_VERSION);
printk(KERN_INFO PFX "%s\n", DRV_COPYRIGHT);
return pci_module_init(&e100_driver);
一切顺利的话,注册的e100_probe函数将被内核调用,这个函数完成两个重要的工作:
1、分配/初始化/注册网络设备;
2、完成PCI设备的I/O区域的分配和映射,以及完成硬件的其它初始化工作;
网络设备使用struct net_device结构来描述,这个结构非常之大,许多重要的参考书籍对它都有较为深入的描述,可以参考《Linux设备驱动程序》中网卡驱动设计的相关章节。我会在后面的内容中,对其重要的成员进行注释;
当probe函数被调用,证明已经发现了我们所支持的网卡,这样,就可以调用register_netdev函数向内核注册网络设备了,注册之前,一般会调用alloc_etherdev为以太网分析一个net_device,然后初始化它的重要成员。
除了向内核注册网络设备之外,探测函数另一项重要的工作就是需要对硬件进行初始化,比如,要访问其I/O区域,需要为I/O区域分配内存区域,然后进行映射,这一步一般的流程是:
1、request_mem_region()
2、ioremap()
对于一般的PCI设备而言,可以调用:
1、pci_request_regions()
2、ioremap()
pci_request_regions函数对PCI的6个寄存器都会调用资源分配函数进行申请(需要判断是I/O端口还是I/O内存),例如:
int pci_request_regions(struct pci_dev *pdev, char *res_name)
for (i = 0; i & 6; i++)
if(pci_request_region(pdev, i, res_name))
int pci_request_region(struct pci_dev *pdev, int bar, char *res_name)
if (pci_resource_len(pdev, bar) == 0)
if (pci_resource_flags(pdev, bar) & IORESOURCE_IO) {
if (!request_region(pci_resource_start(pdev, bar),
pci_resource_len(pdev, bar), res_name))
else if (pci_resource_flags(pdev, bar) & IORESOURCE_MEM) {
if (!request_mem_region(pci_resource_start(pdev, bar),
pci_resource_len(pdev, bar), res_name))
有了这些基础,我们来看设备的探测函数:
static int __devinit e100_probe(struct pci_dev *pdev,
const struct pci_device_id *ent)
struct net_device *
struct nic *
/*分配网络设备*/
if(!(netdev = alloc_etherdev(sizeof(struct nic)))) {
if(((1 && debug) - 1) & NETIF_MSG_PROBE)
printk(KERN_ERR PFX "Etherdev alloc failed, abort.\n");
return -ENOMEM;
/*设置各成员指针函数*/
netdev-&open = e100_
netdev-&stop = e100_
netdev-&hard_start_xmit = e100_xmit_
netdev-&get_stats = e100_get_
netdev-&set_multicast_list = e100_set_multicast_
netdev-&set_mac_address = e100_set_mac_
netdev-&change_mtu = e100_change_
netdev-&do_ioctl = e100_do_
SET_ETHTOOL_OPS(netdev, &e100_ethtool_ops);
netdev-&tx_timeout = e100_tx_
netdev-&watchdog_timeo = E100_WATCHDOG_PERIOD;
netdev-&poll = e100_
netdev-&weight = E100_NAPI_WEIGHT;
#ifdef CONFIG_NET_POLL_CONTROLLER
netdev-&poll_controller = e100_
/*设置网络设备名称*/
strcpy(netdev-&name, pci_name(pdev));
/*取得设备私有数据结构*/
nic = netdev_priv(netdev);
/*网络设备指针,指向自己*/
nic-&netdev =
/*PCIy设备指针,指向自己*/
nic-&pdev =
nic-&msg_enable = (1 && debug) - 1;
/*将PCI设备的私有数据区指向网络设备*/
pci_set_drvdata(pdev, netdev);
/*激活PCI设备*/
if((err = pci_enable_device(pdev))) {
DPRINTK(PROBE, ERR, "Cannot enable PCI device, aborting.\n");
goto err_out_free_
/*判断I/O区域是否是I/O内存,如果不是,则报错退出*/
if(!(pci_resource_flags(pdev, 0) & IORESOURCE_MEM)) {
DPRINTK(PROBE, ERR, "Cannot find proper PCI device "
"base address, aborting.\n");
err = -ENODEV;
goto err_out_disable_
/*分配I/O内存区域*/
if((err = pci_request_regions(pdev, DRV_NAME))) {
DPRINTK(PROBE, ERR, "Cannot obtain PCI resources, aborting.\n");
goto err_out_disable_
* 告之内核自己的DMA寻址能力,这里不是很明白,因为从0xFFFFFFFF来看,本来就是内核默认的32了
* 为什么还要调用pci_set_dma_mask来重复设置呢?可能是对ULL而非UL不是很了解吧。
if((err = pci_set_dma_mask(pdev, 0xFFFFFFFFULL))) {
DPRINTK(PROBE, ERR, "No usable DMA configuration, aborting.\n");
goto err_out_free_
SET_MODULE_OWNER(netdev);
SET_NETDEV_DEV(netdev, &pdev-&dev);
/*分配完成后,映射I/O内存*/
nic-&csr = ioremap(pci_resource_start(pdev, 0), sizeof(struct csr));
if(!nic-&csr) {
DPRINTK(PROBE, ERR, "Cannot map device registers, aborting.\n");
err = -ENOMEM;
goto err_out_free_
if(ent-&driver_data)
nic-&flags |=
nic-&flags &= ~
/*设置设备私有数据结构的大部份默认参数*/
e100_get_defaults(nic);
/* 初始化自旋锁,锅的初始化必须在调用 hw_reset 之前执行*/
spin_lock_init(&nic-&cb_lock);
spin_lock_init(&nic-&cmd_lock);
/* 硬件复位,通过向指定I/O端口设置复位指令实现. */
e100_hw_reset(nic);
* PCI网卡被BIOS配置后,某些特性可能会被屏蔽掉。比如,多数BIOS都会清掉“master”位,
* 这导致板卡不能随意向主存中拷贝数据。pci_set_master函数数会检查是否需要设置标志位,
* 如果需要,则会将“master”位置位。
* PS:什么是PCI master?
* 不同于ISA总线,PCI总线的地址总线与数据总线是分时复用的。这样做的好处是,一方面
* 可以节省接插件的管脚数,另一方面便于实现突发数据传输。在做数据传输时,由一个PCI
* 设备做发起者(主控,Initiator或Master),而另一个PCI设备做目标(从设备,Target或Slave)。
* 总线上的所有时序的产生与控制,都由Master来发起。PCI总线在同一时刻只能供一对设备完成传输。
pci_set_master(pdev);
/*添加两个内核定时器,watchdog和blink_timer*/
init_timer(&nic-&watchdog);
nic-&watchdog.function = e100_
nic-&watchdog.data = (unsigned long)
init_timer(&nic-&blink_timer);
nic-&blink_timer.function = e100_blink_
nic-&blink_timer.data = (unsigned long)
INIT_WORK(&nic-&tx_timeout_task,
(void (*)(void *))e100_tx_timeout_task, netdev);
if((err = e100_alloc(nic))) {
DPRINTK(PROBE, ERR, "Cannot alloc driver memory, aborting.\n");
goto err_out_
/*phy寄存器初始化*/
e100_phy_init(nic);
if((err = e100_eeprom_load(nic)))
goto err_out_
memcpy(netdev-&dev_addr, nic-&eeprom, ETH_ALEN);
if(!is_valid_ether_addr(netdev-&dev_addr)) {
DPRINTK(PROBE, ERR, "Invalid MAC address from "
"EEPROM, aborting.\n");
err = -EAGAIN;
goto err_out_
/* Wol magic packet can be enabled from eeprom */
if((nic-&mac &= mac_8_A4) &&
(nic-&eeprom[eeprom_id] & eeprom_id_wol))
nic-&flags |= wol_
/* ack any pending wake events, disable PME */
pci_enable_wake(pdev, 0, 0);
/*注册网络设备*/
strcpy(netdev-&name, "eth%d");
if((err = register_netdev(netdev))) {
DPRINTK(PROBE, ERR, "Cannot register net device, aborting.\n");
goto err_out_
DPRINTK(PROBE, INFO, "addr 0x%lx, irq %d, "
"MAC addr %02X:%02X:%02X:%02X:%02X:%02X\n",
pci_resource_start(pdev, 0), pdev-&irq,
netdev-&dev_addr[0], netdev-&dev_addr[1], netdev-&dev_addr[2],
netdev-&dev_addr[3], netdev-&dev_addr[4], netdev-&dev_addr[5]);
err_out_free:
e100_free(nic);
err_out_iounmap:
iounmap(nic-&csr);
err_out_free_res:
pci_release_regions(pdev);
err_out_disable_pdev:
pci_disable_device(pdev);
err_out_free_dev:
pci_set_drvdata(pdev, NULL);
free_netdev(netdev);
执行到这里,探测函数的使命就完成了,在对网络设备重要成员初始化时,有:
netdev-&open = e100_
指定了设备的open函数为e100_open,这样,当第一次使用设备,比如使用ifconfig工具的时候,open函数将被调用。
二、打开设备
在探测函数中,设置了netdev-&open = e100_ 指定了设备的open函数为e100_open:
static int e100_open(struct net_device *netdev)
struct nic *nic = netdev_priv(netdev);
int err = 0;
netif_carrier_off(netdev);
if((err = e100_up(nic)))
DPRINTK(IFUP, ERR, "Cannot open interface, aborting.\n");
大多数涉及物理设备可以感知信号载波(carrier)的存在,载波的存在意味着设备可以工作
据个例子来讲:当一个用户拔掉了网线,也就意味着信号载波的消失。
netif_carrier_off:关闭载波信号;
netif_carrier_on:打开载波信号;
netif_carrier_ok:检测载波信号;
对于探测网卡网线是否连接,这一组函数被使用得较多;
接着,调用e100_up函数启动网卡,这个“启动”的过程,最重要的步骤有:
1、调用request_irq向内核注册中断;
2、调用netif_wake_queue函数来重新启动传输队例;
static int e100_up(struct nic *nic)
if((err = e100_rx_alloc_list(nic)))
if((err = e100_alloc_cbs(nic)))
goto err_rx_clean_
if((err = e100_hw_init(nic)))
goto err_clean_
e100_set_multicast_list(nic-&netdev);
e100_start_receiver(nic, 0);
mod_timer(&nic-&watchdog, jiffies);
if((err = request_irq(nic-&pdev-&irq, e100_intr, SA_SHIRQ,
nic-&netdev-&name, nic-&netdev)))
goto err_no_
netif_wake_queue(nic-&netdev);
netif_poll_enable(nic-&netdev);
/* enable ints _after_ enabling poll, preventing a race between
* disable ints+schedule */
e100_enable_irq(nic);
err_no_irq:
del_timer_sync(&nic-&watchdog);
err_clean_cbs:
e100_clean_cbs(nic);
err_rx_clean_list:
e100_rx_clean_list(nic);
这样,中断函数e100_intr将被调用;
三、网卡中断
从本质上来讲,中断,是一种电信号,当设备有某种事件发生的时候,它就会产生中断,通过总线把电信号发送给中断控制器,如果中断的线是激活的,中断控制器就把电信号发送给处理器的某个特定引脚。处理器于是立即停止自己正在做的事,跳到内存中内核设置的中断处理程序的入口点,进行中断处理。
在内核中断处理中,会检测中断与我们刚才注册的中断号匹配,于是,注册的中断处理函数就被调用了。
当需要发/收数据,出现错误,连接状态变化等,网卡的中断信号会被触发。当接收到中断后,中断函数读取中断状态位,进行合法性判断,如判断中断信号是否是自己的等,然后,应答设备中断——OK,我已经知道了,你回去继续工作吧……
接着,它就屏蔽此中断,然后netif_rx_schedule函数接收,接收函数 会在未来某一时刻调用设备的poll函数(对这里而言,注册的是e100_poll)实现设备的轮询:
static irqreturn_t e100_intr(int irq, void *dev_id, struct pt_regs *regs)
struct net_device *netdev = dev_
struct nic *nic = netdev_priv(netdev);
u8 stat_ack = readb(&nic-&csr-&scb.stat_ack);
DPRINTK(INTR, DEBUG, "stat_ack = 0x%02X\n", stat_ack);
if(stat_ack == stat_ack_not_ours ||
/* Not our interrupt */
stat_ack == stat_ack_not_present)
/* Hardware is ejected */
return IRQ_NONE;
/* Ack interrupt(s) */
writeb(stat_ack, &nic-&csr-&scb.stat_ack);
/* We hit Receive No Resource (RNR); restart RU after cleaning */
if(stat_ack & stat_ack_rnr)
nic-&ru_running = RU_SUSPENDED;
e100_disable_irq(nic);
netif_rx_schedule(netdev);
return IRQ_HANDLED;
对于数据包的接收而言,我们关注的是poll函数中,调用e100_rx_clean进行数据的接收:
static int e100_poll(struct net_device *netdev, int *budget)
struct nic *nic = netdev_priv(netdev);
* netdev-&quota是当前CPU能够从所有接口中接收数据包的最大数目,budget是在
* 初始化阶段分配给接口的weight值,轮询函数必须接受二者之间的最小值。表示
* 轮询函数本次要处理的数据包个数。
unsigned int work_to_do = min(netdev-&quota, *budget);
unsigned int work_done = 0;
/*进行数据包的接收和传输*/
e100_rx_clean(nic, &work_done, work_to_do);
tx_cleaned = e100_tx_clean(nic);
/*接收和传输完成后,就退出poll模块,重启中断*/
/* If no Rx and Tx cleanup work was done, exit polling mode. */
if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {
netif_rx_complete(netdev);
e100_enable_irq(nic);
*budget -= work_
netdev-&quota -= work_
static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,
unsigned int work_to_do)
struct rx *
int restart_required = 0;
struct rx *rx_to_start = NULL;
/* are we already rnr? then pay attention!!! this ensures that
* the state machine progression never allows a start with a
* partially cleaned list, avoiding a race between hardware
* and rx_to_clean when in NAPI mode */
if(RU_SUSPENDED == nic-&ru_running)
restart_required = 1;
/* Indicate newly arrived packets */
for(rx = nic-&rx_to_ rx-& rx = nic-&rx_to_clean = rx-&next) {
int err = e100_rx_indicate(nic, rx, work_done, work_to_do);
if(-EAGAIN == err) {
/* hit quota so have more work to do, restart once
* cleanup is complete */
restart_required = 0;
} else if(-ENODATA == err)
/* No more to clean */
/* save our starting point as the place we'll restart the receiver */
if(restart_required)
rx_to_start = nic-&rx_to_
/* Alloc new skbs to refill list */
for(rx = nic-&rx_to_ !rx-& rx = nic-&rx_to_use = rx-&next) {
if(unlikely(e100_rx_alloc_skb(nic, rx)))
/* Better luck next time (see watchdog) */
if(restart_required) {
// ack the rnr?
writeb(stat_ack_rnr, &nic-&csr-&scb.stat_ack);
e100_start_receiver(nic, rx_to_start);
if(work_done)
(*work_done)++;
四、网卡的数据接收
内核如何从网卡接受数据,传统的经典过程:1、数据到达网卡;
2、网卡产生一个中断给内核;
3、内核使用I/O指令,从网卡I/O区域中去读取数据;
我们在许多网卡驱动中,都可以在网卡的中断函数中见到这一过程。
但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,一个问题是,“可不可以不使用中断”,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,“你有没有数据啊?”……
从这个描述本身可以看到,哪果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧,呵呵……
OK,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPU从I/O区域读,然后把它放到内存(这个内存指的是系统本身的物理内存,跟外设的内存不相干,也叫主内存)中”。于是自然地,就想到了DMA技术——让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:1、首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。
2、内核将这个缓冲区通过DMA映射,把这个队列交给网卡;
3、网卡收到数据,就直接放进这个环形缓冲区了——也就是直接放进主内存了;然后,向系统产生一个中断;
4、内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;
——呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?
对应以上4步,来看它的具体实现:
1、分配环形DMA缓冲区
Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后把它们组织成一个双向链表;
2、建立DMA映射
内核通过调用
dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)
建立映射关系。
struct device *dev,描述一个设备;
buffer:把哪个地址映射给设备;也就是某一个skb——要映射全部,当然是做一个双向链表的循环即可;
size:缓存大小;
direction:映射方向——谁传给谁:一般来说,是“双向”映射,数据在设备和内存之间双向流动;
对于PCI设备而言(网卡一般是PCI的),通过另一个包裹函数pci_map_single,这样,就把buffer交给设备了!设备可以直接从里边读/取数据。
3、这一步由硬件完成;
4、取消映射
dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里——因为我们已经接收到数据了,应该由CPU把数据交给上层网络栈;
当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用
dma_sync_single_for_cpu()
让CPU在取消映射前,就可以访问DMA缓冲区中的内容。
关于DMA映射的更多内容,可以参考《Linux设备驱动程序》“内存映射和DMA”章节相关内容!
OK,有了这些知识,我们就可以来看e100的代码了,它跟上面讲的步骤基本上一样的——绕了这么多圈子,就是想绕到e100上面了,呵呵!
在e100_open函数中,调用e100_up,我们前面分析它时,略过了一个重要的东东,就是环形缓冲区的建立,这一步,是通过
e100_rx_alloc_list函数调用完成的:
static int e100_rx_alloc_list(struct nic *nic)
struct rx *
unsigned int i, count = nic-&params.rfds.
nic-&rx_to_use = nic-&rx_to_clean = NULL;
nic-&ru_running = RU_UNINITIALIZED;
/*结构struct rx用来描述一个缓冲区节点,这里分配了count个*/
if(!(nic-&rxs = kmalloc(sizeof(struct rx) * count, GFP_ATOMIC)))
return -ENOMEM;
memset(nic-&rxs, 0, sizeof(struct rx) * count);
/*虽然是连续分配的,不过还是遍历它,建立双向链表,然后为每一个rx的skb指针分员分配空间
skb用来描述内核中的一个数据包,呵呵,说到重点了*/
for(rx = nic-&rxs, i = 0; i & rx++, i++) {
rx-&next = (i + 1 & count) ? rx + 1 : nic-&
rx-&prev = (i == 0) ? nic-&rxs + count - 1 : rx - 1;
if(e100_rx_alloc_skb(nic, rx)) {
/*分配缓存*/
e100_rx_clean_list(nic);
return -ENOMEM;
nic-&rx_to_use = nic-&rx_to_clean = nic-&
nic-&ru_running = RU_SUSPENDED;
#define RFD_BUF_LEN (sizeof(struct rfd) + VLAN_ETH_FRAME_LEN)
static inline int e100_rx_alloc_skb(struct nic *nic, struct rx *rx)
/*skb缓存的分配,是通过调用系统函数dev_alloc_skb来完成的,它同内核栈中通常调用alloc_skb的区别在于,
它是原子的,所以,通常在中断上下文中使用*/
if(!(rx-&skb = dev_alloc_skb(RFD_BUF_LEN + NET_IP_ALIGN)))
return -ENOMEM;
/*初始化必要的成员 */
rx-&skb-&dev = nic-&
skb_reserve(rx-&skb, NET_IP_ALIGN);
/*这里在数据区之前,留了一块sizeof(struct rfd) 这么大的空间,该结构的
一个重要作用,用来保存一些状态信息,比如,在接收数据之前,可以先通过
它,来判断是否真有数据到达等,诸如此类*/
memcpy(rx-&skb-&data, &nic-&blank_rfd, sizeof(struct rfd));
/*这是最关键的一步,建立DMA映射,把每一个缓冲区rx-&skb-&data都映射给了设备,缓存区节点
rx利用dma_addr保存了每一次映射的地址,这个地址后面会被用到*/
rx-&dma_addr = pci_map_single(nic-&pdev, rx-&skb-&data,
RFD_BUF_LEN, PCI_DMA_BIDIRECTIONAL);
if(pci_dma_mapping_error(rx-&dma_addr)) {
dev_kfree_skb_any(rx-&skb);
rx-&skb = 0;
rx-&dma_addr = 0;
return -ENOMEM;
/* Link the RFD to end of RFA by linking previous RFD to
* this one, and clearing EL bit of previous.
if(rx-&prev-&skb) {
struct rfd *prev_rfd = (struct rfd *)rx-&prev-&skb-&
/*put_unaligned(val,ptr);用到把var放到ptr指针的地方,它能处理处理内存对齐的问题
prev_rfd是在缓冲区开始处保存的一点空间,它的link成员,也保存了映射后的地址*/
put_unaligned(cpu_to_le32(rx-&dma_addr),
(u32 *)&prev_rfd-&link);
prev_rfd-&command &= ~cpu_to_le16(cb_el);
pci_dma_sync_single_for_device(nic-&pdev, rx-&prev-&dma_addr,
sizeof(struct rfd), PCI_DMA_TODEVICE);
e100_rx_alloc_list函数在一个循环中,建立了环形缓冲区,并调用e100_rx_alloc_skb为每个缓冲区分配了空间,并做了
DMA映射。这样,我们就可以来看接收数据的过程了。
前面我们讲过,中断函数中,调用netif_rx_schedule,表明使用轮询技术,系统会在未来某一时刻,调用设备的poll函数:
static int e100_poll(struct net_device *netdev, int *budget)
struct nic *nic = netdev_priv(netdev);
unsigned int work_to_do = min(netdev-&quota, *budget);
unsigned int work_done = 0;
e100_rx_clean(nic, &work_done, work_to_do);
tx_cleaned = e100_tx_clean(nic);
/* If no Rx and Tx cleanup work was done, exit polling mode. */
if((!tx_cleaned && (work_done == 0)) || !netif_running(netdev)) {
netif_rx_complete(netdev);
e100_enable_irq(nic);
*budget -= work_
netdev-&quota -= work_
目前,我们只关心rx,所以,e100_rx_clean函数就成了我们关注的对像,它用来从缓冲队列中接收全部数据(这或许是取名为clean的原因吧!):
static inline void e100_rx_clean(struct nic *nic, unsigned int *work_done,
unsigned int work_to_do)
struct rx *
int restart_required = 0;
struct rx *rx_to_start = NULL;
/* are we already rnr? then pay attention!!! this ensures that
* the state machine progression never allows a start with a
* partially cleaned list, avoiding a race between hardware
* and rx_to_clean when in NAPI mode */
if(RU_SUSPENDED == nic-&ru_running)
restart_required = 1;
/* 函数最重要的工作,就是遍历环形缓冲区,接收数据*/
for(rx = nic-&rx_to_ rx-& rx = nic-&rx_to_clean = rx-&next) {
int err = e100_rx_indicate(nic, rx, work_done, work_to_do);
if(-EAGAIN == err) {
/* hit quota so have more work to do, restart once
* cleanup is complete */
restart_required = 0;
} else if(-ENODATA == err)
/* No more to clean */
/* save our starting point as the place we'll restart the receiver */
if(restart_required)
rx_to_start = nic-&rx_to_
/* Alloc new skbs to refill list */
for(rx = nic-&rx_to_ !rx-& rx = nic-&rx_to_use = rx-&next) {
if(unlikely(e100_rx_alloc_skb(nic, rx)))
/* Better luck next time (see watchdog) */
if(restart_required) {
// ack the rnr?
writeb(stat_ack_rnr, &nic-&csr-&scb.stat_ack);
e100_start_receiver(nic, rx_to_start);
if(work_done)
(*work_done)++;
static inline int e100_rx_indicate(struct nic *nic, struct rx *rx,
unsigned int *work_done, unsigned int work_to_do)
struct sk_buff *skb = rx-&
struct rfd *rfd = (struct rfd *)skb-&
u16 rfd_status, actual_
if(unlikely(work_done && *work_done &= work_to_do))
return -EAGAIN;
/* 读取数据之前,也就是取消DMA映射之前,需要先读取cb_complete 状态位,
以确定数据是否真的准备好了,并且,rfd的actual_size中,也包含了真实的数据大小
pci_dma_sync_single_for_cpu函数前面已经介绍过,它让CPU在取消DMA映射之前,具备
访问DMA缓存的能力*/
pci_dma_sync_single_for_cpu(nic-&pdev, rx-&dma_addr,
sizeof(struct rfd), PCI_DMA_FROMDEVICE);
rfd_status = le16_to_cpu(rfd-&status);
DPRINTK(RX_STATUS, DEBUG, "status=0x%04X\n", rfd_status);
/* If data isn't ready, nothing to indicate */
if(unlikely(!(rfd_status & cb_complete)))
return -ENODATA;
/* Get actual data size */
actual_size = le16_to_cpu(rfd-&actual_size) & 0x3FFF;
if(unlikely(actual_size & RFD_BUF_LEN - sizeof(struct rfd)))
actual_size = RFD_BUF_LEN - sizeof(struct rfd);
/* 取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也就意味着,
CPU可以处理主内存中的数据了 */
pci_unmap_single(nic-&pdev, rx-&dma_addr,
RFD_BUF_LEN, PCI_DMA_FROMDEVICE);
/* this allows for a fast restart without re-enabling interrupts */
if(le16_to_cpu(rfd-&command) & cb_el)
nic-&ru_running = RU_SUSPENDED;
/*正确地设置data指针,因为最前面有一个sizeof(struct rfd)大小区域,跳过它*/
skb_reserve(skb, sizeof(struct rfd));
/*更新skb的tail和len指针,也是就更新接收到这么多数据的长度*/
skb_put(skb, actual_size);
/*设置协议位*/
skb-&protocol = eth_type_trans(skb, nic-&netdev);
if(unlikely(!(rfd_status & cb_ok))) {
/* Don't indicate if hardware indicates errors */
nic-&net_stats.rx_dropped++;
dev_kfree_skb_any(skb);
} else if(actual_size & nic-&netdev-&mtu + VLAN_ETH_HLEN) {
/* Don't indicate oversized frames */
nic-&rx_over_length_errors++;
nic-&net_stats.rx_dropped++;
dev_kfree_skb_any(skb);
/*网卡驱动要做的最后一步,就是统计接收计数器,设置接收时间戳,然后调用netif_receive_skb,
把数据包交给上层协议栈,自己的光荣始命也就完成了*/
nic-&net_stats.rx_packets++;
nic-&net_stats.rx_bytes += actual_
nic-&netdev-&last_rx =
netif_receive_skb(skb);
if(work_done)
(*work_done)++;
rx-&skb = NULL;
网卡驱动执行到这里,数据接收的工作,也就处理完成了。但是,使用这一种方法的驱动,省去了网络栈中一个重要的内容,就是
“队列层”,让我们来看看,传统中断接收数据包模式下,使用netif_rx函数调用,又会发生什么。
五、队列层
1、软中断与下半部
当用中断处理的时候,为了减少中断处理的工作量,比如,一般中断处理时,需要屏蔽其它中断,如果中断处理时间过长,那么其它中断
有可能得不到及时处理,也以,有一种机制,就是把“不必马上处理”的工作,推迟一点,让它在中断处理后的某一个时刻得到处理。这就
是下半部。
下半部只是一个机制,它在Linux中,有多种实现方式,其中一种对时间要求最严格的实现方式,叫“软中断”,可以使用:
open_softirq()
来向内核注册一个软中断,
然后,在合适的时候,调用
raise_softirq_irqoff()
如果采用中断方式接收数据(这一节就是在说中断方式接收,后面,就不用这种假设了),同样也需要软中断,可以调用
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
向内核注册一个名为NET_RX_SOFTIR的软中断,net_rx_action是软中断的处理函数。
然后,在驱动中断处理完后的某一个时刻,调用
raise_softirq_irqoff(NET_RX_SOFTIRQ);
触发它,这样net_rx_action将得到执行。
什么是队列层?通常,在网卡收发数据的时候,需要维护一个缓冲区队列,来缓存可能存在的突发数据,类似于前面的DMA环形缓冲区。
队列层中,包含了一个叫做struct softnet_data:
struct softnet_data
/*throttle 用于拥塞控制,当拥塞发生时,throttle将被设置,后续进入的数据包将被丢弃*/
/*netif_rx函数返回的拥塞级别*/
/*softnet_data 结构包含一个指向接收和传输队列的指针,input_pkt_queue成员指向准备传送
给网络层的sk_buffs包链表的首部的指针,这个队列中的包是由netif_rx函数递交的*/
struct sk_buff_head
input_pkt_
struct list_head
struct net_device
struct sk_buff
*completion_
struct net_device
/* Sorry. 8) */
内核使用了一个同名的变量softnet_data,它是一个Per-CPU变量,每个CPU都有一个。
net/core/dev.c
DECLARE_PER_CPU(struct softnet_data,softnet_data);
网络模块的核心处理模块.
static int __init net_dev_init(void)
int i, rc = -ENOMEM;
BUG_ON(!dev_boot_phase);
net_random_init();
if (dev_proc_init())
/*初始化proc文件系统*/
if (netdev_sysfs_init())
/*初始化sysfs文件系统*/
/*ptype_all和ptype_base是重点,后面会详细分析,它们都是
struct list_head类型变量,这里初始化链表成员*/
INIT_LIST_HEAD(&ptype_all);
for (i = 0; i & 16; i++)
INIT_LIST_HEAD(&ptype_base[i]);
for (i = 0; i & ARRAY_SIZE(dev_name_head); i++)
INIT_HLIST_HEAD(&dev_name_head[i]);
for (i = 0; i & ARRAY_SIZE(dev_index_head); i++)
INIT_HLIST_HEAD(&dev_index_head[i]);
初始化包接收队列,这里我们的重点了.
/*遍历每一个CPU,取得它的softnet_data,我们说过,它是一个struct softnet_data的Per-CPU变量*/
for (i = 0; i & NR_CPUS; i++) {
struct softnet_data *
/*取得第i个CPU的softnet_data,因为队列是包含在它里边的,所以,我会直接说,“取得队列”*/
queue = &per_cpu(softnet_data, i);
/*初始化队列头*/
skb_queue_head_init(&queue-&input_pkt_queue);
queue-&throttle = 0;
queue-&cng_level = 0;
queue-&avg_blog = 10; /* arbitrary non-zero */
queue-&completion_queue = NULL;
INIT_LIST_HEAD(&queue-&poll_list);
set_bit(__LINK_STATE_START, &queue-&backlog_dev.state);
queue-&backlog_dev.weight = weight_p;
/*这里,队列中backlog_dev设备,它是一个伪网络设备,不对应任何物理设备,它的poll函数,指向了
process_backlog,后面我们会详细分析*/
queue-&backlog_dev.poll = process_
atomic_set(&queue-&backlog_dev.refcnt, 1);
#ifdef OFFLINE_SAMPLE
samp_timer.expires = jiffies + (10 * HZ);
add_timer(&samp_timer);
dev_boot_phase = 0;
/*注册收/发软中断*/
open_softirq(NET_TX_SOFTIRQ, net_tx_action, NULL);
open_softirq(NET_RX_SOFTIRQ, net_rx_action, NULL);
hotcpu_notifier(dev_cpu_callback, 0);
dst_init();
dev_mcast_init();
这样,初始化完成后,在驱动程序中,在中断处理函数中,会调用netif_rx将数据交上来,这与采用轮询技术,有本质的不同:
int netif_rx(struct sk_buff *skb)
struct softnet_data *
/* if netpoll wants it, pretend we never saw it */
if (netpoll_rx(skb))
return NET_RX_DROP;
/*接收时间戳未设置,设置之*/
if (!skb-&stamp.tv_sec)
net_timestamp(&skb-&stamp);
* 这里准备将数据包放入接收队列,需要禁止本地中断,在入队操作完成后,再打开中断.
local_irq_save(flags);
/*获取当前CPU对应的softnet_data变量*/
this_cpu = smp_processor_id();
queue = &__get_cpu_var(softnet_data);
/*接收计数器累加*/
__get_cpu_var(netdev_rx_stat).total++;
/*接收队列是否已满*/
if (queue-&input_pkt_queue.qlen &= netdev_max_backlog) {
if (queue-&input_pkt_queue.qlen) {
if (queue-&throttle)
/*拥塞发生了,丢弃数据包*/
/*数据包入队操作*/
dev_hold(skb-&dev);
/*累加设备引入计数器*/
__skb_queue_tail(&queue-&input_pkt_queue, skb);
/*将数据包加入接收队列*/
#ifndef OFFLINE_SAMPLE
get_sample_stats(this_cpu);
local_irq_restore(flags);
return queue-&cng_
* 驱动程序不断地调用net_rx函数,实现接收数据包的入队操作,当qlen == 0时,则进入这段代码,这里,如果已经被设置拥塞标志的话,则清除它,因为这里将要调用软中断,开始将数据包交给上层了,即上层协议的接收函数将执行出队操作,拥塞自然而然也就不存在了。 */
if (queue-&throttle)
queue-&throttle = 0;
* netif_rx_schedule函数完成两件重要的工作:
* 1、将bakclog_dev设备加入“处理数据包的设备”的链表当中;
* 2、触发软中断函数,进行数据包接收处理;
netif_rx_schedule(&queue-&backlog_dev);
/*前面判断了队列是否已满,如果已满而标志未设置,设置之,并累加拥塞计数器*/
if (!queue-&throttle) {
queue-&throttle = 1;
__get_cpu_var(netdev_rx_stat).throttled++;
/*拥塞发生,累加丢包计数器,释放数据包*/
__get_cpu_var(netdev_rx_stat).dropped++;
local_irq_restore(flags);
kfree_skb(skb);
return NET_RX_DROP;
从这段代码的分析中,我们可以看到,当第一个数据包被接收后,因为qlen==0,所以首先会调用netif_rx_schedule触发软中断,然后利用goto跳转至入队。因为软中断被触发后,将执行出队操作,把数据交往上层处理。而当这个时候,又有数据包进入,即网卡中断产生,因为它的优先级高过软中断,这样,出队操作即被中断,网卡中断程序再将被调用,netif_rx函数又再次被执行,如果队列未满,就入队返回。中断完成后,软中断的执行过程被恢复而继续执行出队——如此生产者/消费者循环不止,生生不息……
netif_rx调用netif_rx_schedule进一步处理数据包,我们注意到:
1、前面讨论过,采用轮询技术时,同样地,也是调用netif_rx_schedule,把设备自己传递了过去;
2、这里,采用中断方式,传递的是队列中的一个“伪设备”,并且,这个伪设备的poll函数指针,指向了一个叫做process_backlog的函数;
netif_rx_schedule函数完成两件重要的工作:
1、将bakclog_dev设备加入“处理数据包的设备”的链表当中;
2、触发软中断函数,进行数据包接收处理;
这样,我们可以猜想,在软中断函数中,不论是伪设备bakclog_dev,还是真实的设备(如前面讨论过的e100),都会被软中断函数以:
dev-&poll()
的形式调用,对于e100来说,poll函数的接收过程已经分析了,而对于其它所有没有采用轮询技术的网络设备来说,它们将统统调用
process_backlog函数(我觉得把它改名为pseudo-poll是否更合适一些^o^)。
OK,我想分析到这里,关于中断处理与轮询技术的差异,已经基本分析开了……
继续来看,netif_rx_schedule进一步调用__netif_rx_schedule:
/* Try to reschedule poll. Called by irq handler. */
static inline void netif_rx_schedule(struct net_device *dev)
if (netif_rx_schedule_prep(dev))
__netif_rx_schedule(dev);
/* Add interface to tail of rx poll list. This assumes that _prep has
* already been called and returned 1.
static inline void __netif_rx_schedule(struct net_device *dev)
local_irq_save(flags);
dev_hold(dev);
/*伪设备也好,真实的设备也罢,都被加入了队列层的设备列表*/
list_add_tail(&dev-&poll_list, &__get_cpu_var(softnet_data).poll_list);
if (dev-&quota & 0)
dev-&quota += dev-&
dev-&quota = dev-&
/*触发软中断*/
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
local_irq_restore(flags);
软中断被触发,注册的net_rx_action函数将被调用:
/*接收的软中断处理函数*/
static void net_rx_action(struct softirq_action *h)
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time =
int budget = netdev_max_
local_irq_disable();
* 遍历队列的设备链表,如前所述,__netif_rx_schedule已经执行了
* list_add_tail(&dev-&poll_list, &__get_cpu_var(softnet_data).poll_list);
* 设备bakclog_dev已经被添加进来了
while (!list_empty(&queue-&poll_list)) {
struct net_device *
if (budget &= 0 || jiffies - start_time & 1)
goto softnet_
local_irq_enable();
/*取得链表中的设备*/
dev = list_entry(queue-&poll_list.next,
struct net_device, poll_list);
netpoll_poll_lock(dev);
/*调用设备的poll函数,处理接收数据包,这样,采用轮询技术的网卡,它的真实的poll函数将被调用,
这就回到我们上一节讨论的e100_poll函数去了,而对于采用传统中断处理的设备,它们调用的,都将是
bakclog_dev的process_backlog函数*/
if (dev-&quota &= 0 || dev-&poll(dev, &budget)) {
netpoll_poll_unlock(dev);
/*处理完成后,把设备从设备链表中删除,又重置于末尾*/
local_irq_disable();
list_del(&dev-&poll_list);
list_add_tail(&dev-&poll_list, &queue-&poll_list);
if (dev-&quota & 0)
dev-&quota += dev-&
dev-&quota = dev-&
netpoll_poll_unlock(dev);
dev_put(dev);
local_irq_disable();
local_irq_enable();
softnet_break:
__get_cpu_var(netdev_rx_stat).time_squeeze++;
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
对于dev-&poll(dev, &budget)的调用,一个真实的poll函数的例子,我们已经分析过了,现在来看process_backlog,
static int process_backlog(struct net_device *backlog_dev, int *budget)
int work = 0;
int quota = min(backlog_dev-&quota, *budget);
struct softnet_data *queue = &__get_cpu_var(softnet_data);
unsigned long start_time =
backlog_dev-&weight = weight_p;
/*在这个循环中,执行出队操作,把数据从队列中取出来,交给netif_receive_skb,直至队列为空*/
for (;;) {
struct sk_buff *
struct net_device *
local_irq_disable();
skb = __skb_dequeue(&queue-&input_pkt_queue);
local_irq_enable();
dev = skb-&
netif_receive_skb(skb);
dev_put(dev);
if (work &= quota || jiffies - start_time & 1)
backlog_dev-&quota -=
*budget -=
return -1;
/*当队列中的数据包被全部处理后,将执行到这里*/
backlog_dev-&quota -=
*budget -=
list_del(&backlog_dev-&poll_list);
smp_mb__before_clear_bit();
netif_poll_enable(backlog_dev);
if (queue-&throttle)
queue-&throttle = 0;
local_irq_enable();
这个函数重要的工作,就是出队,然后调用netif_receive_skb()将数据包交给上层,这与上一节讨论的poll是一样的。这也是为什么,
在网卡驱动的编写中,采用中断技术,要调用netif_rx,而采用轮询技术,要调用netif_receive_skb啦!
如下图链路层包的传输过程:
在netif_rx中会调用napi_schedule,然后该函数又会去调用__napi_schedule()。在函数__napi_schedule()中会去调用设备的poll函数,它是设备自己注册的。在设备的poll函数中,会去调用netif_receive_skb函数,在该函数中有下面一条语句pt_prev-&func,此处的func为一个函数指针,在之前的注册中设置为ip_rcv。因此,就完成了从链路层上传到网络层的这一个过程了。
到了这里,就处理完数据包与设备相关的部分了,数据包将进入上层协议栈……
linux内核对网卡驱动多队列的支持
没有更多推荐了,}

我要回帖

更多关于 数据分析师是干什么的 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信