add lots file of APUE
@@ -1,12 +1,12 @@
|
||||
[History]
|
||||
list=[["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction",0,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811",0,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction",0,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction:Device Tree Usage",32107,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction:mpc8315erdb.dts",7935,null],["Research:Error Notes:\u4e0b\u8f7d\u9519\u8bef:chosen node create failed",426,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction:mpc8315erdb.dts",7935,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811",0,null],["Interview:AWK\u4e4b\u7236\u8bbf\u8c08\u5f55",0,null],["Interview",0,null],["Interview:The History of the Floppy Disk",0,null],["Programme:C++:size t vs size type:size type",3680,null],["Programme:C++:size t vs size type",53,null],["Programme:C++:size t vs size type:size type",0,null],["Programme:C++:size t vs size type",52,null],["Programme:C++:Refer VS Pointer",0,null],["Programme:C++:size t vs size type",52,null],["Programme:C++:Refer VS Pointer",0,null],["Programme:C++:size t vs size type",52,null],["Utils:cscope",0,null],["Research:Error Notes:\u4e0b\u8f7d\u9519\u8bef:chosen node create failed",426,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction",0,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction:Device Tree Usage",32107,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction:mpc8315erdb.dts",7935,null],["Research:Error Notes:\u4e0b\u8f7d\u9519\u8bef:chosen node create failed",null,null]]
|
||||
list=[["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:Page Cache",2874,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5757\u7f13\u51b2\u533a\u548c\u5757\u7f13\u51b2\u533a\u5934",356,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5757\u7f13\u51b2\u533a\u548c\u5757\u7f13\u51b2\u533a\u5934:buffer cache",2108,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:Virtual file system",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:The Virtual File System in Linux",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:Linux\u6587\u4ef6\u7cfb\u7edfFAQ",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:GIO",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:Filesystem in Userspace (FUSE)",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:The Virtual File System in Linux",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5757\u7f13\u51b2\u533a\u548c\u5757\u7f13\u51b2\u533a\u5934",356,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5757\u7f13\u51b2\u533a\u548c\u5757\u7f13\u51b2\u533a\u5934:buffer cache",2108,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5982\u4f55\u5b9e\u73b0\u4e00\u4e2a\u6587\u4ef6\u7cfb\u7edf",15137,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u89e3\u6790 Linux \u4e2d\u7684 VFS \u6587\u4ef6\u7cfb\u7edf\u673a\u5236",0,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76",1434,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:Page Cache",2874,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf",0,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:Page Cache",2874,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76",1434,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:Page Cache",2875,null],["\u5185\u6838\u5f00\u53d1:Linux2.6.16\u7684\u542f\u52a8\u8fc7\u7a0b",0,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:Page Cache",2493,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76",2075,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:Page Cache",2493,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76",5340,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:The Page Cache FAQ",null,null]]
|
||||
current=24
|
||||
recent=[["Interview",0,null],["Interview:The History of the Floppy Disk",null,null],["Programme:C++:size t vs size type:size type",0,null],["Programme:C++:Refer VS Pointer",0,null],["Programme:C++:size t vs size type",null,null],["Utils:cscope",null,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction",0,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction:Device Tree Usage",32107,null],["Research:\u6241\u5e73\u8bbe\u5907\u6811:Device Tree Introduction:mpc8315erdb.dts",7935,null],["Research:Error Notes:\u4e0b\u8f7d\u9519\u8bef:chosen node create failed",null,null]]
|
||||
recent=[["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:The Virtual File System in Linux",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5757\u7f13\u51b2\u533a\u548c\u5757\u7f13\u51b2\u533a\u5934",356,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5757\u7f13\u51b2\u533a\u548c\u5757\u7f13\u51b2\u533a\u5934:buffer cache",2108,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u5982\u4f55\u5b9e\u73b0\u4e00\u4e2a\u6587\u4ef6\u7cfb\u7edf",15137,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf:\u89e3\u6790 Linux \u4e2d\u7684 VFS \u6587\u4ef6\u7cfb\u7edf\u673a\u5236",0,null],["\u5185\u6838\u5f00\u53d1:linux\u6587\u4ef6\u7cfb\u7edf",0,null],["\u5185\u6838\u5f00\u53d1:Linux2.6.16\u7684\u542f\u52a8\u8fc7\u7a0b",0,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:Page Cache",2493,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76",5340,null],["\u5185\u6838\u5f00\u53d1:Linux Cache \u673a\u5236\u63a2\u7a76:The Page Cache FAQ",null,null]]
|
||||
|
||||
[MainWindow]
|
||||
windowsize=[1600,836]
|
||||
show_sidepane=True
|
||||
sidepane_pos=424
|
||||
sidepane_pos=316
|
||||
show_menubar=True
|
||||
show_menubar_fullscreen=True
|
||||
show_toolbar=True
|
||||
@@ -68,8 +68,8 @@ windowsize=[500,400]
|
||||
windowsize=[537,422]
|
||||
|
||||
[AttachmentBrowserPlugin]
|
||||
active=False
|
||||
bottompane_pos=513
|
||||
active=True
|
||||
bottompane_pos=516
|
||||
|
||||
[TaskListDialog]
|
||||
windowsize=[550,400]
|
||||
|
||||
102
Zim/Interview/stallman其人轶事.txt
Normal file
@@ -0,0 +1,102 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-14T15:56:12+08:00
|
||||
|
||||
====== stallman其人轶事 ======
|
||||
Created Friday 14 September 2012
|
||||
http://www.baidu-ops.com/2012/08/26/stallman-facts/
|
||||
|
||||
Richard Stallman 是程序员公认的顶级黑客。好事的程序员也总结了他的奇闻轶事--程序员的暗语和笑话
|
||||
|
||||
|
||||
原文地址,
|
||||
|
||||
top10
|
||||
Richard Stallman 的胡子是由括号组成的
|
||||
Richard Stallman’s beard is made of parentheses.
|
||||
Richard Stallman 自己就是GNU的标志
|
||||
Richard Stallman is the sole poster on /g/
|
||||
Richard Stallman 从不洗澡,他只执行’make clean’
|
||||
Richard Stallman never showers: he runs ‘make clean’.
|
||||
Richard Stallman 使用16进制编辑器编译了gcc的第一个版本
|
||||
Richard Stallman compiled the first version of gcc with an hexadecimal editor.
|
||||
Richard Stallman 能触摸MC Harmmer
|
||||
|
||||
zerd:双关MC Harmmer的成名曲是”U Can’t Touch This”
|
||||
|
||||
Richard Stallman can touch MC Hammer
|
||||
Richard Stallman 通过免费解决了旅行销售员的问题。
|
||||
Richard Stallman solved the travelling salesman problem by making everything free.
|
||||
‘RMS’中的’R’代表’RMS’
|
||||
|
||||
zerd: RMS是Richard Stallman的名字首字母缩写
|
||||
|
||||
The R in RMS stands for RMS.
|
||||
‘RMS’脸上的毛发是胡子中免费的。
|
||||
rms’ facial hair is “free as in beard”
|
||||
Richard Stallman 愤怒时他不仅诅咒,还是递归的诅咒.
|
||||
When Richard Stallman gets pissed off he doesn’t swear, he recurses.
|
||||
锁定供应商是指供应商把自己锁在建筑物中以远离Richard Stallman的愤怒。
|
||||
Vendor lock-in is when vendors lock themselves inside of a building out of fear of Richard Stallman’s wrath.
|
||||
其余的
|
||||
Richard Stallman 编写了 Chuck Norris
|
||||
Richard Stallman programmed Chuck Norris
|
||||
当Richard Stallman 注视windows时,它发生了段错误。当Richard Stallman未注视windows时,它也发生了段错误。
|
||||
|
||||
Whenever Richard Stallman looks at a Windows computer, it segfaults. Whenever Richard Stallman doesn’t look at a Windows computer, it segfaults.
|
||||
|
||||
Richard Stallman 用0和1记笔记
|
||||
|
||||
Richard Stallman takes notes in binary.
|
||||
Richard Stallman的左手叫做”(“,右手叫做”)”.
|
||||
|
||||
zerd: lisp语言书写出来有大量的”()”
|
||||
|
||||
Richard Stallman’s left and right hands are named “(” and ”)”
|
||||
Richard Stallman 能编写防病毒程序来治愈爱滋病。但他从不写防病毒程序。
|
||||
Richard Stallman can write an anti-virus program that cures HIV. Too bad he never writes anti-virus programs.
|
||||
Richard Stallman 用Emacs写了第一版的Emacs。
|
||||
Richard Stallman wrote the first version of Emacs using Emacs.
|
||||
Richard Stallman 写了一个非常强大的程序,他知道宇宙的终极秘密。
|
||||
|
||||
zerd:42是科幻小说中”Deep Thought”技术出的一个数字,解答了宇宙完事万物的秘密。
|
||||
|
||||
Richard Stallman wrote a program so powerful, it knows the question to 42.
|
||||
Richard Stallman发现了外星生命,但是他杀了他们因为他们使用商业软件。
|
||||
Richard Stallman discovered extra-terrestrial life but killed them because they used closed-source software.
|
||||
人们查杀病毒,病毒查杀Richard Stallman。
|
||||
Some people check their computers for viruses. Viruses check their computers for Richard Stallman.
|
||||
Richard Stallman 为上帝写了编译器。大爆炸就是宇宙第一次发生“段错误”
|
||||
Richard Stallman wrote the compiler God used. The Big Bang was the Universe’s first segfault.
|
||||
Richard Stallman 的理想还未实现
|
||||
|
||||
zerd:这句没看懂。wget是linux下的一个程序。w-get对应w-demand,Stallman的思想是free software,目前还没有实现。
|
||||
|
||||
Richard Stallman doesn’t wget, Richard Stallman wdemands!
|
||||
Richard Stallman 能用正则表达式解析HTML。
|
||||
Richard Stallman can parse HTML with regex.
|
||||
Richard Stallman 不需要sudo,我反正要给他做一个三明治
|
||||
|
||||
zerd: 真的没看懂
|
||||
|
||||
Richard Stallman doesn’t need sudo. I will make him a sandwich anyway.
|
||||
Stallman 家里没有windows,即使门上也没有
|
||||
|
||||
zerd:stallman家里没有窗户,呵呵
|
||||
|
||||
There is no Windows in Stallman’s house.. just DOORs…
|
||||
和正常出生的小孩不一样,Richard Stallman 是以多态的方式实例化了自己,不久他就长出了胡子。
|
||||
Rather than being birthed like a normal child, Richard Stallman instead instantiated himself polymorphically. Shortly thereafter he grew a beard.
|
||||
在起居室里有1242个对象(包括这间屋子)可供RMS编写OS
|
||||
In an average living room there are 1,242 objects RMS could use to write an OS, including the room itself.
|
||||
Richard Stallman 能够telnet上Mordor
|
||||
|
||||
zerd: Mordor 魔多
|
||||
|
||||
Richard Stallman can telnet into Mordor
|
||||
Richard Stallman 不用浏览器,他发送link给daemon,由它通过wget获取页面并传回给Stallman
|
||||
Richard Stallman doesn’t use web browsers, he sends a link to a demon that uses wget to fetch the page and sends it back to him.
|
||||
Richard Stallman 使用GNU FDL协议发布了自己的DNA
|
||||
Richard Stallman released his own DNA under GNU FDL.
|
||||
Richard Stallman 没有手机,因为它能对着粗糙的凸盘发出臭氧层的共振频率,抵达接受者的上空。
|
||||
Richard Stallman does not own a mobile phone because he can fashion a crude convex dish and shout into it at the exact resonant frequency of the ozone, causing a voice to seemingly come from the sky above his intended recipient.
|
||||
@@ -81,5 +81,6 @@ mendeley — a papers eg. pdf word organizer
|
||||
dia — a 'viso' like flow-chart drawing software.
|
||||
fcitx — a input method
|
||||
evince — a simple pdf viewer
|
||||
iftop --- interface top
|
||||
|
||||
###################################################################
|
||||
|
||||
191
Zim/Linux/Load和CPU利用率是如何算出来的.txt
Normal file
@@ -0,0 +1,191 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-23T10:30:02+08:00
|
||||
|
||||
====== Load和CPU利用率是如何算出来的 ======
|
||||
Created Tuesday 23 October 2012
|
||||
|
||||
本文内容遵从CC版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明
|
||||
网址: http://www.penglixun.com/tech/system/how_to_calc_load_cpu.html
|
||||
|
||||
相信很多人都对Linux中top命令里“load average”这一栏困惑过,到底什么是Load,Load代表了什么含义,Load高会有什么后果?“%CPU”这一栏为什么会超过100%,它是如何计算的?带着这些问题,我们通过一些测试,来探索下其中的不解之处。
|
||||
|
||||
首先,我们通过实验来大概确定其计算方式:
|
||||
测试服务器:4核Xeon处理器
|
||||
测试软件:MySQL 5.1.40
|
||||
服务器上除了MySQL没有运行其他任何非系统自带软件。因为__MySQL只能单线程运行单条SQL__,所以可以很好的通过**增加查询并发**来控制使用的CPU核数。
|
||||
|
||||
空载时,top的信息为:
|
||||
top – 14:51:47 up 35 days, 4:43, 1 user, load average: 0.00, 0.00, 0.00
|
||||
Tasks: 76 total, 1 running, 75 sleeping, 0 stopped, 0 zombie
|
||||
Cpu(s): 0.0%us, 0.0%sy, 0.0%ni, 99.5%id, 0.1%wa, 0.2%hi, 0.2%si, 0.0%st
|
||||
|
||||
在数据库中启动一个大查询:
|
||||
top – 15:28:09 up 35 days, 5:19, 3 users, load average: 0.99, 0.92, 0.67
|
||||
Tasks: 80 total, 1 running, 79 sleeping, 0 stopped, 0 zombie
|
||||
Cpu0 : 0.0%us, 0.0%sy, 0.0%ni, 96.3%id, 0.0%wa, 1.3%hi, 2.3%si, 0.0%st
|
||||
Cpu1 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
Cpu2 : 98.7%us, 1.3%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
Cpu3 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
|
||||
同时可以看到%CPU也是在100%
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
877 mysql 15 0 308m 137m 4644 S **99.9** 6.8 15:13.28 mysqld
|
||||
|
||||
然后开启第二个大查询,不久就可以看到top信息的变化,Load到了2:
|
||||
top – 15:36:44 up 35 days, 5:28, 3 users, load average: 1.99, 1.62, 1.08
|
||||
Tasks: 80 total, 1 running, 79 sleeping, 0 stopped, 0 zombie
|
||||
Cpu0 : 0.0%us, 0.0%sy, 0.0%ni, 97.7%id, 0.0%wa, 1.0%hi, 1.3%si, 0.0%st
|
||||
Cpu1 : **99.0%us**, 1.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
Cpu2 : 0.0%us, 0.0%sy, 0.0%ni,100.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
Cpu3 : **99.0%us**, 1.0%sy, 0.0%ni, 0.0%id, 0.0%wa, 0.0%hi, 0.0%si, 0.0%st
|
||||
|
||||
也可以观察到%CPU增加到了200%:
|
||||
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
|
||||
877 mysql 15 0 312m 141m 4644 S **199.8** 7.0 22:31.27 mysqld //__同一个PID下有两个线程,每个线程处理一条SQL语句__。
|
||||
|
||||
由此可以简单的做出如下临时结论:
|
||||
1. %CPU是由__每个核的CPU占用率之和__算出来的。
|
||||
2. load跟执行的任务数有关
|
||||
|
||||
不过要想准确的知道其含义,还是必须从源码入手。
|
||||
|
||||
===== CPU利用率的计算方法 =====
|
||||
下载busybox的源码,在procps目录下有top.c的源码,查看第293行附近(1.17.1版),可以看到
|
||||
if (prev_hist_count) do {
|
||||
if (prev_hist[i].pid == pid) {
|
||||
cur->pcpu = cur->ticks - prev_hist[i].ticks;
|
||||
total_pcpu += cur->pcpu;
|
||||
break;
|
||||
}
|
||||
i = (i+1) % prev_hist_count;
|
||||
/* hist_iterations++; */
|
||||
} while (i != last_i);
|
||||
这就是计算%CPU的代码,很明显total_pcpu就是__累加了每个线程对每个核的使用率__,所以%CPU的最大值就是 核数*100%。
|
||||
线程的CPU利用率是如何计算的呢?跟踪代码可以发现,是__从系统的/proc/stat这里读取__的,这个文件的格式可以参考:http://www.linuxhowtos.org/System/procstat.htm,下面是我笔记本上读出来的内容。
|
||||
|
||||
plx@plinux-Laptop:~/busybox-1.17.1$ cat **/proc/stat**
|
||||
__cpu__ 520529 3525 658608 3500749 210662 6650 29698 0 0
|
||||
cpu0 249045 1936 466387 1624486 136381 308 17051 0 0
|
||||
cpu1 271483 1588 192221 1876263 74281 6342 12646 0 0
|
||||
intr 84067574 42497789 41743 0 0 0 0 0 0 1 57928 0 0 7175 0 0 0 477092 24693 0 5 0 183 0 20 0 0 0 12455 821851 745906 10192555 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
|
||||
ctxt 142313984
|
||||
btime 1281403521
|
||||
processes 6707
|
||||
procs_running 2
|
||||
procs_blocked 0
|
||||
softirq 56932805 0 20168080 9440286 238191 821787 0 10621375 4052209 13257 11577620
|
||||
|
||||
cpuN的含义从左到右分别是:user、system、nice、idle、iowait、irq、softirq,具体含义可以看文档。
|
||||
在下面几行的含义是:
|
||||
“intr”这行给出中断的信息,第一个为自系统启动以来,发生的**所有的中断的次数**;然后每个数对应一个特定的中断自系统启动以来所发生的次数。
|
||||
“ctxt”给出了自系统启动以来CPU发生的**上下文交换的次数**。
|
||||
“btime”给出了从系统启动到现在为止的时间,单位为秒。
|
||||
“processes (total_forks) **自系统启动以来所创建的任务的总数目**。
|
||||
“procs_running”:当前运行队列的任务的数目。
|
||||
“procs_blocked”:当前被阻塞的任务的数目。
|
||||
|
||||
那么CPU利用率可以使用以下方法,先取**两个采样点**,然后计算其差值:
|
||||
cpu usage=(idle2-idle1)/(cpu2-cpu1)*100
|
||||
cpu usage=[(user_2 +sys_2+nice_2) - (user_1 + sys_1+nice_1)]/(total_2 - total_1)*100;
|
||||
|
||||
这是一段Bash代码采集利用率的,摘自网络:
|
||||
#!/bin/sh
|
||||
##echo **user nice system **__idle__** iowait irq softirq**
|
||||
CPULOG_1=$(cat /proc/stat | grep 'cpu ' | awk '{print $2" "$3" "$4" "$5" "$6" "$7" "$8}')
|
||||
SYS_IDLE_1=$(echo $CPULOG_1 | awk '{print $4}')
|
||||
Total_1=$(echo $CPULOG_1 | awk '{print $1+$2+$3+$4+$5+$6+$7}')
|
||||
|
||||
sleep 5
|
||||
|
||||
CPULOG_2=$(cat /proc/stat | grep 'cpu ' | awk '{print $2" "$3" "$4" "$5" "$6" "$7" "$8}')
|
||||
SYS_IDLE_2=$(echo $CPULOG_2 | awk '{print $4}')
|
||||
Total_2=$(echo $CPULOG_2 | awk '{print $1+$2+$3+$4+$5+$6+$7}')
|
||||
|
||||
SYS_IDLE=`expr $SYS_IDLE_2 - $SYS_IDLE_1`
|
||||
|
||||
Total=`expr $Total_2 - $Total_1`
|
||||
SYS_USAGE=`expr $SYS_IDLE/$Total*100 |bc -l`
|
||||
SYS_Rate=`expr 100-$SYS_USAGE |bc -l`
|
||||
|
||||
Disp_SYS_Rate=`expr "scale=3; $SYS_Rate/1" |bc`
|
||||
echo $Disp_SYS_Rate%
|
||||
|
||||
===== Load的计算方法 =====
|
||||
跟踪busybox的代码可以知道,__load是从/proc/loadavg中读取的__。
|
||||
我本机的一次抓取内容如下:
|
||||
plx@plinux-Laptop:~/busybox-1.17.1$ cat /proc/loadavg
|
||||
0.64 0.81 0.86 3/364 6930
|
||||
|
||||
每个值的含义依次为:
|
||||
lavg_1 (0.64) 1-分钟平均负载
|
||||
lavg_5 (0.81) 5-分钟平均负载
|
||||
lavg_15(0.86) 15-分钟平均负载
|
||||
nr_running (3) 在采样时刻,运行队列的任务的数目,与/proc/stat的procs_running表示相同意思即正在运行和可立即投入运行的线程数目
|
||||
nr_threads (364) 在采样时刻,系统中**活跃的任务**的个数(包括__可运行和阻塞的__线程,但不包括运行已经结束的任务)
|
||||
last_pid(6930) 最大的pid值,包括轻量级进程,即线程。
|
||||
|
||||
假设当前有两个CPU,则每个CPU的当前任务数为0.64/2=0.32
|
||||
|
||||
我们可以在Linux内核中找到loadavg文件的源码:
|
||||
tatic int loadavg___read_proc__(char *page, char **start, off_t off,
|
||||
int count, int *eof, void *data)
|
||||
{
|
||||
int a, b, c;
|
||||
int len;
|
||||
#
|
||||
|
||||
a = avenrun[0] + (FIXED_1/200);
|
||||
b = avenrun[1] + (FIXED_1/200);
|
||||
c = avenrun[2] + (FIXED_1/200);
|
||||
len = sprintf(page,"%d.%02d %d.%02d %d.%02d %ld/%d %d\n",
|
||||
LOAD_INT(a), LOAD_FRAC(a),
|
||||
LOAD_INT(b), LOAD_FRAC(b),
|
||||
LOAD_INT(c), LOAD_FRAC(c),
|
||||
nr_running(), nr_threads, last_pid);
|
||||
return proc_calc_metrics(page, start, off, count, eof, len);
|
||||
}
|
||||
以及计算load的代码:
|
||||
|
||||
#define FSHIFT 11 /* nr of bits of precision */
|
||||
#define FIXED_1 (1<<FSHIFT) /* 1.0 as fixed-point(定点) */
|
||||
#define LOAD_FREQ (5*HZ) /* 5 sec intervals,每隔5秒计算一次平均负载值 */
|
||||
#define CALC_LOAD(load, exp, n) \
|
||||
load *= exp; \
|
||||
load += n*(FIXED_1 - exp); \
|
||||
load >>= FSHIFT;
|
||||
|
||||
unsigned long avenrun[3];
|
||||
|
||||
EXPORT_SYMBOL(avenrun);
|
||||
|
||||
/*
|
||||
* calc_load - given tick count, update the avenrun load estimates.
|
||||
* This is called while holding a write_lock on xtime_lock.
|
||||
*/
|
||||
static inline void calc_load(unsigned long ticks)
|
||||
{
|
||||
unsigned long active_tasks; /* fixed-point */
|
||||
static int count = LOAD_FREQ;
|
||||
count -= ticks;
|
||||
if (count < 0) {
|
||||
count += LOAD_FREQ;
|
||||
active_tasks = count_active_tasks();
|
||||
CALC_LOAD(avenrun[0], EXP_1, active_tasks);
|
||||
CALC_LOAD(avenrun[1], EXP_5, active_tasks);
|
||||
CALC_LOAD(avenrun[2], EXP_15, active_tasks);
|
||||
}
|
||||
}
|
||||
看了大师的文章,理解了这些代码。
|
||||
|
||||
所以可以明白:
|
||||
__Linux的系统负载指运行队列的平均长度,也就是等待CPU的平均进程数。__因为Linux内禁止浮点运算,因此系统的负载只能通过计算变化的次数这一修正值来计算。Linux内核定义一个长度为3的双字数组avenrun,双字的低11位用于存放负载的小数部分,高21位用于存放整数部分。当进程所耗的 CPU时间片数超过CPU在5秒内能够提供的时间片数时,内核计算上述的三个负载。负载初始化为0,假设最近1、5、15分钟内的平均负载分别为 load1、load5和load15,那么下一个计算时刻到来时,内核通过下面的算式计算负载:
|
||||
load1 -= load1 -* exp(-5 / 60) -+ n * (1 – exp(-5 / 60 ))
|
||||
load5 -= load5 -* exp(-5 / 300) + n * (1 – exp(-5 / 300))
|
||||
load15 = load15 * exp(-5 / 900) + n * (1 – exp(-5 / 900))
|
||||
其中,exp(x)为e的x次幂,n为当前运行队列的长度。Linux内核认为进程的生存时间服从参数为1的指数分布,指数分布的概率密度为:以内核计算负载load1为例,设相邻两个计算时刻之间系统活动的进程集合为S0。从1分钟前到当前计算时刻这段时间里面活动的load1个进程,设他们的集合是 S1,内核认为的概率密度是:λe-λx,而在当前时刻活动的n个进程,设他们的集合是Sn内核认为的概率密度是1-λe-λx。其中x = 5 / 60,因为相邻两个计算时刻之间进程所耗的CPU时间为5秒,而考虑的时间段是1分钟(60秒)。那么可以求出最近1分钟系统运行队列的长度:
|
||||
load1 = |S1| -* λe-λx + |Sn| * (1-λe-λx) = load1 * λe-λx + n * (1-λe-λx)
|
||||
其中λ = 1, x = 5 / 60, |S1|和|Sn|是集合元素的个数,这就是Linux内核源文件shed.c的函数calc_load()计算负载的数学依据。
|
||||
|
||||
所以__“Load值=CPU核数”__,这是最理想的状态,没有任何竞争,一个任务分配一个核。
|
||||
由于数据是每隔5秒钟检查一次活跃的进程数,然后根据这个数值算出来的。如果这个数除以CPU的核数,结果高于5的时候就表明系统在超负荷运转了。
|
||||
346
Zim/Programme/APUE/Linux_Epoll介绍和程序实例.txt
Normal file
@@ -0,0 +1,346 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-15T20:39:55+08:00
|
||||
|
||||
====== Linux Epoll介绍和程序实例 ======
|
||||
Created Monday 15 October 2012
|
||||
http://blog.csdn.net/sparkliang/article/details/4770655
|
||||
|
||||
|
||||
===== Epoll 是何方神圣? =====
|
||||
|
||||
|
||||
Epoll 可是当前在 Linux 下开发**大规模并发网络程序**的热门人选, Epoll 在 Linux2.6 内核中正式引入,和 select 相似,其实都 **I/O 多路复用**技术而已 ,并没有什么神秘的。其实在 Linux 下设计并发网络程序,向来不缺少方法,比如典型的 Apache 模型( Process Per Connection ,简称 __PPC__ ), __TPC__ ( Thread Per Connection )模型,以及 select 模型和 poll 模型,那为何还要再引入 Epoll 这个东东呢?那还是有得说说的 …
|
||||
|
||||
|
||||
===== 常用模型的缺点 =====
|
||||
|
||||
|
||||
如果不摆出来其他模型的缺点,怎么能对比出 Epoll 的优点呢。
|
||||
|
||||
==== 2.1 PPC/TPC 模型 ====
|
||||
这两种模型思想类似,就是让每一个到来的连接一边自己做事去,别再来烦我 。只是 PPC 是为它开了一个进程,而 TPC 开了一个线程。可是别烦我是有代价的,它要**时间和空间**啊,连接多了之后,那么多的__进程 / 线程切换开销__就上来了;因此这类模型能接受的最大连接数都不会高,一般在几百个左右。
|
||||
|
||||
==== 2.2 select 模型 ====
|
||||
1. 最大并发数限制,因为一个进程所打开的 FD (文件描述符)是有限制的,由 FD_SETSIZE 设置,默认值是 1024/2048 ,因此 Select 模型的最大并发数就被相应限制了。自己改改这个 FD_SETSIZE ?想法虽好,可是先看看下面吧 …
|
||||
2. 效率问题, select 每次调用都会**线性扫描全部**的 FD 集合,这样效率就会呈现线性下降,把 FD_SETSIZE 改大的后果就是,大家都慢慢来,什么?都超时了??!!
|
||||
3. 内核 / 用户空间 **内存拷贝问题**,如何让内核把 FD 消息通知给用户空间呢?在这个问题上 select 采取了**内存拷贝**方法。
|
||||
|
||||
==== 2.3 poll 模型 ====
|
||||
基本上效率和 select 是相同的, select 缺点的 2 和 3 它都没有改掉。
|
||||
|
||||
===== 3. Epoll 的提升 =====
|
||||
把其他模型逐个批判了一下,再来看看 Epoll 的改进之处吧,其实把 select 的缺点反过来那就是 Epoll 的优点了。
|
||||
|
||||
3.1. Epoll 没有最大并发连接的限制,上限是最大可以打开文件的数目,这个数字一般远大于 2048, 一般来说这个数目和系统内存关系很大 ,具体数目可以 cat /proc/sys/fs/file-max 察看。
|
||||
3.2. 效率提升, Epoll 最大的优点就在于它只管你__“活跃”__的连接 ,而跟连接总数无关,因此在实际的网络环境中, Epoll 的效率就会远远高于 select 和 poll 。
|
||||
3.3. 内存拷贝, Epoll 在这点上使用了“共享内存 ”,这个内存拷贝也省略了。
|
||||
|
||||
===== 4. Epoll 为什么高效 =====
|
||||
Epoll 的高效和其数据结构的设计是密不可分的,这个下面就会提到。
|
||||
|
||||
首先回忆一下 select 模型,当有 I/O 事件到来时, select 通知应用程序有事件到了快去处理,而应用程序必须__轮询所有的 FD 集合__,测试每个 FD 是否有事件发生,并处理事件;代码像下面这样:
|
||||
|
||||
int res = select(maxfd+1, &readfds, NULL, NULL, 120);
|
||||
|
||||
if (res > 0)
|
||||
|
||||
{
|
||||
|
||||
for (int i = 0; i < MAX_CONNECTION; i++)
|
||||
|
||||
{
|
||||
|
||||
if (FD_ISSET(allConnection[i], &readfds))
|
||||
|
||||
{
|
||||
|
||||
handleEvent(allConnection[i]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// if(res == 0) handle timeout, res < 0 handle error
|
||||
|
||||
Epoll 不仅会告诉应用程序有I/0 事件到来,还会告诉应用程序相关的信息,这些信息是应用程序填充的,因此根据这些信息应用程序就能直接定位到事件,而__不必遍历整个FD 集合__。
|
||||
|
||||
int res = epoll_wait(epfd, events, 20, 120);
|
||||
for (int i = 0; i < res;i++)
|
||||
{
|
||||
handleEvent(events[n]);
|
||||
}
|
||||
|
||||
===== 5. Epoll 关键数据结构 =====
|
||||
前面提到 Epoll 速度快和其数据结构密不可分,其关键数据结构就是:
|
||||
|
||||
struct epoll_event {
|
||||
__uint32_t events; // Epoll events
|
||||
epoll_data_t data; // User data variable
|
||||
};
|
||||
|
||||
typedef union epoll_data {
|
||||
void *ptr;
|
||||
int fd;
|
||||
__uint32_t u32;
|
||||
__uint64_t u64;
|
||||
} epoll_data_t;
|
||||
|
||||
可见 epoll_data 是一个 union 结构体 , 借助于它应用程序可以__保存很多类型的信息__ :fd 、指针等等。有了它,应用程序就可以直接定位目标了。
|
||||
|
||||
6. ===== 使用 Epoll =====
|
||||
|
||||
既然 Epoll 相比 select 这么好,那么用起来如何呢?会不会很繁琐啊 … 先看看下面的三个函数吧,就知道 Epoll 的易用了。
|
||||
|
||||
int epoll_create(int size);
|
||||
生成一个 Epoll 专用的文件描述符,其实是**申请一个内核空间**,用来存放你想关注的 socket fd 上是否发生以及发生了什么事件。 size 就是你在这个 Epoll fd 上能关注的最大 socket fd 数,大小自定,只要内存足够。
|
||||
|
||||
int epoll_ctl(int epfd, int __op__, int fd, struct epoll_event *event );
|
||||
控制某个 Epoll 文件描述符上的事件:注册、修改、删除。其中参数 epfd 是 epoll_create() 创建 Epoll 专用的文件描述符。相对于 select 模型中的 FD_SET 和 FD_CLR 宏。
|
||||
|
||||
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout);
|
||||
等待 I/O 事件的发生;参数说明:
|
||||
epfd: 由 epoll_create() 生成的 Epoll 专用的文件描述符;
|
||||
epoll_event: 用于__回传__代处理事件的数组;
|
||||
maxevents: 每次能处理的事件数;
|
||||
timeout: 等待 I/O 事件发生的超时值;
|
||||
返回发生事件数。相对于 select 模型中的 select 函数。
|
||||
|
||||
7. 例子程序
|
||||
下面是一个简单 Echo Server 的例子程序,麻雀虽小,五脏俱全,还包含了一个简单的超时检查机制,简洁起见没有做错误处理。
|
||||
//
|
||||
// a simple echo server using epoll in linux
|
||||
//
|
||||
// 2009-11-05
|
||||
// by sparkling
|
||||
//
|
||||
#include <sys/socket.h>
|
||||
#include <sys/epoll.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <stdio.h>
|
||||
#include <errno.h>
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
#define MAX_EVENTS 500
|
||||
|
||||
struct myevent_s
|
||||
{
|
||||
int fd;
|
||||
int events;
|
||||
void *arg;
|
||||
void (*call_back)(int fd, int events, void *arg); //call_back函数的参数都从结构体成员中得到。
|
||||
int status; // __1: in epoll wait list, 0 not in__
|
||||
char buff[128]; // recv data buffer
|
||||
int len;
|
||||
long last_active; // last active time
|
||||
};
|
||||
|
||||
// set event
|
||||
void EventSet(myevent_s *ev, int fd, void (*call_back)(int, int, void*), void *arg)
|
||||
{
|
||||
ev->fd = fd;
|
||||
ev->call_back = call_back;
|
||||
**ev->events = 0;**
|
||||
**ev->arg = arg; //arg其实就是struct myevent_s的指针。**
|
||||
ev->status = 0;
|
||||
ev->last_active = time(NULL);
|
||||
}
|
||||
|
||||
// add/mod an event to epoll
|
||||
void EventAdd(int epollFd, int events, myevent_s *ev)
|
||||
{
|
||||
__struct epoll_event epv = {0, {0}};__
|
||||
int op;
|
||||
__epv.data.ptr = ev; //myevent_s被封装到epoll_event的ptr成员中。__
|
||||
__epv.events = ev->events = events;__
|
||||
if(ev->status == 1){ //ev->fd已经处于epoll wait list中
|
||||
op = EPOLL_CTL_MOD;
|
||||
}
|
||||
else{
|
||||
op = EPOLL_CTL_ADD;
|
||||
ev->status = 1;
|
||||
}
|
||||
if(__epoll_ctl__(epollFd, op, ev->fd, **&epv**) < 0)
|
||||
printf("Event Add failed[fd=%d]/n", ev->fd);
|
||||
else
|
||||
printf("Event Add OK[fd=%d]/n", ev->fd);
|
||||
}
|
||||
|
||||
// delete an event from epoll
|
||||
void EventDel(int epollFd, myevent_s *ev)
|
||||
{
|
||||
struct epoll_event epv = {0, {0}};
|
||||
if(ev->status != 1) return;
|
||||
epv.data.ptr = ev;
|
||||
ev->status = 0;
|
||||
__epoll_ctl__(epollFd, EPOLL_CTL_DEL, ev->fd, &epv);
|
||||
}
|
||||
|
||||
int g_epollFd;
|
||||
myevent_s g_Events[MAX_EVENTS+1]; // g_Events[MAX_EVENTS] is used by __socket listen fd__
|
||||
void RecvData(int fd, int events, void *arg);
|
||||
void SendData(int fd, int events, void *arg);
|
||||
|
||||
// accept new connections from clients
|
||||
void AcceptConn(int fd, int events, void *arg) //arg参数在该函数中并没有被使用。
|
||||
{
|
||||
__struct sockaddr_in__ sin;
|
||||
socklen_t len = sizeof(struct sockaddr_in);
|
||||
int nfd, i;
|
||||
// accept
|
||||
if((__nfd__ = accept(fd, (struct sockaddr*)&sin, &len)) == -1)
|
||||
{
|
||||
if(errno != EAGAIN && errno != EINTR)
|
||||
{
|
||||
printf("%s: bad accept", __func__);
|
||||
}
|
||||
return;
|
||||
}
|
||||
do
|
||||
{
|
||||
for(i = 0; i < MAX_EVENTS; i++)
|
||||
{
|
||||
if(g_Events[i].status == 0)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i == MAX_EVENTS)
|
||||
{
|
||||
printf("%s:max connection limit[%d].", __func__, MAX_EVENTS);
|
||||
break;
|
||||
}
|
||||
__// set nonblocking__
|
||||
if(fcntl(nfd, F_SETFL, O_NONBLOCK) < 0) break;
|
||||
// add a read event for receive data
|
||||
EventSet(&g_Events[i], nfd, __RecvData__, &g_Events[i]); //注册一个**回调函数**
|
||||
EventAdd(g_epollFd, **EPOLLIN|EPOLL**__ET__, &g_Events[i]);
|
||||
printf("new conn[%s:%d][time:%d]/n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port), g_Events[i].last_active);
|
||||
}while(0);
|
||||
}
|
||||
|
||||
// receive data
|
||||
void RecvData(int fd, int events, void *arg) //当fd可读时,envents和arg来源于__struct epoll_event 的 epv.data.ptr和epv.events 成员。__
|
||||
{
|
||||
struct myevent_s *ev = __(struct myevent_s*)arg__;
|
||||
int len;
|
||||
// receive data
|
||||
len = recv(fd, ev->buff, sizeof(ev->buff)-1, 0);
|
||||
EventDel(g_epollFd, ev);
|
||||
if(len > 0)
|
||||
{
|
||||
ev->len = len;
|
||||
ev->buff[len] = '/0';
|
||||
printf("C[%d]:%s/n", fd, ev->buff);
|
||||
// change to **send** event
|
||||
EventSet(ev, fd, __SendData__, ev);
|
||||
EventAdd(g_epollFd, __EPOLLOUT|EPOLLET,__ ev);
|
||||
}
|
||||
else if(len == 0)
|
||||
{
|
||||
close(ev->fd);
|
||||
printf("[fd=%d] closed gracefully./n", fd);
|
||||
}
|
||||
else
|
||||
{
|
||||
close(ev->fd);
|
||||
printf("recv[fd=%d] error[%d]:%s/n", fd, errno, strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// send data
|
||||
void SendData(int fd, int events, void *arg)
|
||||
{
|
||||
struct myevent_s *ev = (struct myevent_s*)arg;
|
||||
int len;
|
||||
// send data
|
||||
len = send(fd, ev->buff, ev->len, 0);
|
||||
ev->len = 0;
|
||||
EventDel(g_epollFd, ev);
|
||||
if(len > 0)
|
||||
{
|
||||
// change to **receive** event
|
||||
EventSet(ev, fd, __RecvData__, ev);
|
||||
EventAdd(g_epollFd, EPOLLIN|EPOLLET, ev);
|
||||
}
|
||||
else
|
||||
{
|
||||
close(ev->fd);
|
||||
printf("recv[fd=%d] error[%d]/n", fd, errno);
|
||||
}
|
||||
}
|
||||
|
||||
void InitListenSocket(int epollFd, short port)
|
||||
{
|
||||
int listenFd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
fcntl(listenFd, F_SETFL, O_NONBLOCK); // set non-blocking
|
||||
printf("server listen fd=%d/n", listenFd);
|
||||
EventSet(&g_Events[MAX_EVENTS], __listenFd, AcceptConn__, &g_Events[MAX_EVENTS]); //注册一个**回调函数**
|
||||
// add listen socket
|
||||
EventAdd(epollFd, __EPOLLIN|EPOLLET__, &g_Events[MAX_EVENTS]);
|
||||
// bind & listen
|
||||
sockaddr_in sin;
|
||||
bzero(&sin, sizeof(sin));
|
||||
sin.sin_family = AF_INET;
|
||||
sin.sin_addr.s_addr = INADDR_ANY;
|
||||
sin.sin_port = htons(port);
|
||||
bind(listenFd, (const sockaddr*)&sin, sizeof(sin));
|
||||
listen(listenFd, 5);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
short port = 12345; // default port
|
||||
if(argc == 2){
|
||||
port = atoi(argv[1]);
|
||||
}
|
||||
**// create epoll**
|
||||
g_epollFd = epoll_create(MAX_EVENTS);
|
||||
if(g_epollFd <= 0) printf("create epoll failed.%d/n", g_epollFd);
|
||||
// create & bind listen socket, and add to epoll, set non-blocking
|
||||
InitListenSocket(**g_epollFd**, port);
|
||||
__// event loop__
|
||||
struct epoll_event events[MAX_EVENTS];
|
||||
printf("server running:port[%d]/n", port);
|
||||
int checkPos = 0;
|
||||
while(1){
|
||||
// a simple timeout check here, every time 100, better to use a mini-heap, and add timer event
|
||||
long now = time(NULL);
|
||||
for(int i = 0; i < 100; i++, checkPos++) // __doesn't check listen fd__
|
||||
{
|
||||
if(checkPos == MAX_EVENTS) checkPos = 0; // recycle
|
||||
if(g_Events[checkPos].status != 1) continue;
|
||||
long duration = now - g_Events[checkPos].last_active;
|
||||
if(duration >= 60) // 60s timeout
|
||||
{
|
||||
close(g_Events[checkPos].fd);
|
||||
printf("[fd=%d] timeout[%d--%d]./n", g_Events[checkPos].fd, g_Events[checkPos].last_active, now);
|
||||
EventDel(g_epollFd, &g_Events[checkPos]);
|
||||
}
|
||||
}
|
||||
// wait for events to happen
|
||||
int fds = __epoll_wait__(g_epollFd, __events__, MAX_EVENTS, 1000);
|
||||
if(fds < 0){
|
||||
printf("epoll_wait error, exit/n");
|
||||
break;
|
||||
}
|
||||
for(int i = 0; i < fds; i++){
|
||||
**myevent_s *ev = (struct myevent_s*)events[i].data.ptr ;**
|
||||
if((events[i].events&**EPOLLIN**)&&(ev->events&EPOLLIN)) // read event
|
||||
{
|
||||
ev->call_back(ev->fd, events[i].events, ev->arg);
|
||||
}
|
||||
if((events[i].events&**EPOLLOUT**)&&(ev->events&EPOLLOUT)) // write event
|
||||
{
|
||||
ev->call_back(ev->fd, events[i].events, ev->arg);
|
||||
}
|
||||
}
|
||||
}
|
||||
// free resource
|
||||
return 0;
|
||||
}
|
||||
138
Zim/Programme/APUE/Linux_Epoll介绍和程序实例/epoll精髓.txt
Normal file
@@ -0,0 +1,138 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-15T21:09:07+08:00
|
||||
|
||||
====== epoll精髓 ======
|
||||
Created Monday 15 October 2012
|
||||
http://www.cnblogs.com/OnlyXP/archive/2007/08/10/851222.html
|
||||
|
||||
在linux的网络编程中,很长的时间都在使用select来做**事件触发**。在linux新的内核中,有了一种替换它的机制,就是epoll。相比于select,epoll最大的好处在于它**不会随着监听fd数目的增长而降低效率**。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
|
||||
#define __FD_SETSIZE 1024
|
||||
表示select最多同时监听1024个fd,当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
|
||||
|
||||
epoll的接口非常简单,一共就三个函数:
|
||||
|
||||
1. int epoll_create(int size);
|
||||
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会__占用一个fd值__,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
|
||||
|
||||
2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
|
||||
epoll的**事件注册函数**,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里__先注册要监听的事件类型__。
|
||||
第一个参数是epoll_create()的返回值
|
||||
第二个参数表示动作,用三个宏来表示:
|
||||
EPOLL_CTL_ADD:注册新的fd到epfd中;
|
||||
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
|
||||
EPOLL_CTL_DEL:从epfd中删除一个fd;
|
||||
第三个参数是需要监听的fd
|
||||
第四个参数是告诉内核需要**监听什么事**,struct epoll_event结构如下:
|
||||
struct epoll_event {
|
||||
__uint32_t events; /* Epoll events */
|
||||
epoll_data_t data; /* User data variable */
|
||||
};
|
||||
events可以是以下几个**宏的位与**:
|
||||
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
|
||||
EPOLLOUT:表示对应的文件描述符可以写;
|
||||
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
|
||||
EPOLLERR:表示对应的文件描述符发生错误;
|
||||
EPOLLHUP:表示对应的文件描述符被挂断;
|
||||
EPOLLET: 将EPOLL设为__边缘触发__(Edge Triggered)模式,这是相对于__水平触发__(Level Triggered)来说的。
|
||||
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
|
||||
|
||||
typedef union epoll_data {
|
||||
void *ptr;
|
||||
int fd;
|
||||
__uint32_t u32;
|
||||
__uint64_t u64;
|
||||
} epoll_data_t;
|
||||
|
||||
3. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
|
||||
等待事件的产生,类似于select()调用。参数events用来__从内核得到事件的集合__,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
|
||||
|
||||
--------------------------------------------------------------------------------------------
|
||||
|
||||
从man手册中,得到ET和LT的具体描述如下:
|
||||
EPOLL事件有两种模型:
|
||||
Edge Triggered (ET)
|
||||
Level Triggered (LT)
|
||||
|
||||
假如有这样一个例子:
|
||||
1. 我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到epoll描述符
|
||||
2. 这个时候从管道的另一端被写入了2KB的数据
|
||||
3. 调用epoll_wait(2),并且它会返回RFD,说明它已经准备好读取操作
|
||||
4. 然后我们读取了1KB的数据
|
||||
5. 调用epoll_wait(2)......
|
||||
|
||||
=== Edge Triggered 工作模式: ===
|
||||
如果我们在第1步将RFD添加到epoll描述符的时候使用了__EPOLLET__标志,那么在第5步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且**数据发出端**还在等待一个针对已经发出数据的反馈信息。__只有在监视的文件句柄上发生了某个事件的时候 ET 工作模式才会汇报事件__。因此在第5步的时候,调用者可能会放弃等待仍存在于文件输入缓冲区内的剩余数据。在上面的例子中,会有一个事件产生在RFD句柄上,因为在第2步执行了一个写操作,然后,__事件将会在第3步被销毁__。因为第4步的读取操作没有读空文件输入缓冲区内的数据,因此我们在第5步调用 epoll_wait(2)完成后,是否挂起是不确定的。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好以下面的方式调用ET模式的epoll接口,在后面会介绍避免可能的缺陷。
|
||||
i 基于非阻塞文件句柄
|
||||
ii 只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到产生一个EAGAIN才认为此次事件处理完成,当read()返回的__读到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了__,也就可以认为此事读事件已处理完成。
|
||||
|
||||
=== Level Triggered 工作模式 ===
|
||||
相反的,以LT方式调用epoll接口的时候,它就相当于一个速度比较快的poll(2),并且无论后面的数据是否被使用,因此他们具有同样的职能(**如果epoll_wait返回的fd数据没有读完,下次再调用epoll_wait时继续返回该fd可读事件。**)。因为即使使用ET模式的epoll,在收到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在 epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止掉。因此当EPOLLONESHOT设定后,使用带有 EPOLL_CTL_MOD标志的epoll_ctl(2)处理文件句柄就成为调用者必须作的事情。
|
||||
|
||||
|
||||
然后详细解释ET, LT:
|
||||
LT(level triggered)是__缺省的工作方式__,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,__内核还是会继续通知你的(下一次调用epoll_wait会继续立即返回)__,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
|
||||
|
||||
ET(edge-triggered)是高速工作方式,__只支持no-block socket__。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知(__即使下一次调用epoll_wait时该fd仍然就绪),直到你做了某些操作导致那个文件描述符不再为就绪状态了(__比如**读该fd直到将缓冲区中的数据读完,或写该fd直到将缓冲区填满。**)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
|
||||
|
||||
在许多测试中我们会看到如果没有大量的idle -connection或者dead-connection,epoll的效率并不会比select/poll高很多,但是当我们遇到大量的idle- connection(例如WAN环境中存在大量的慢速连接),就会发现epoll的效率大大高于select/poll。
|
||||
|
||||
另外,当使用epoll的ET模型来工作时,当产生了一个EPOLLIN事件后,读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以__还需要再次读取(直到对到的数据量小于请求的数据量,或返回EAGAIN或EWOULDBLOCK错误)__:
|
||||
while(rs)
|
||||
{
|
||||
buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
|
||||
if(buflen < 0)
|
||||
{
|
||||
// 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
|
||||
// 在这里就当作是该次事件已处理处.
|
||||
if(errno == EAGAIN)
|
||||
break;
|
||||
else
|
||||
return;
|
||||
}
|
||||
else if(buflen == 0)
|
||||
{
|
||||
// 这里表示对端的socket已正常关闭.
|
||||
}
|
||||
if(buflen == sizeof(buf)
|
||||
rs = 1; // **需要再次读取**
|
||||
else
|
||||
rs = 0;
|
||||
}
|
||||
|
||||
还有,假如发送端流量大于接收端的流量(意思是epoll所在的程序读比转发的socket要快), 由于是非阻塞的socket,那么send()函数虽然返回,但实际缓冲区的数据并未真正发给接收端,这样不断的读和发,当缓冲区满后会产生EAGAIN错误(参考man send),同时,不理会这次请求发送的数据.所以,需要封装socket_send()的函数用来处理这种情况,该函数会尽量将数据写完再返回,返回-1表示出错。在socket_send()内部,当写缓冲已满(send()返回-1,且errno为EAGAIN),那么会等待后再重试.这种方式并不很完美,在理论上可能会长时间的阻塞在socket_send()内部,但暂没有更好的办法.
|
||||
ssize_t socket_send(int sockfd, const char* buffer, size_t buflen)
|
||||
{
|
||||
ssize_t tmp;
|
||||
size_t total = buflen;
|
||||
const char *p = buffer;
|
||||
|
||||
while(1)
|
||||
{
|
||||
tmp = send(sockfd, p, total, 0);
|
||||
if(tmp < 0)
|
||||
{
|
||||
// 当send收到信号时,可以继续写,但这里返回-1.
|
||||
if(errno == EINTR)
|
||||
return -1;
|
||||
|
||||
// 当socket是非阻塞时,如返回EAGAIN错误,表示写缓冲队列已满,
|
||||
// 在这里做延时后再重试.
|
||||
if(errno == EAGAIN)
|
||||
{
|
||||
usleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
if((size_t)tmp == total)
|
||||
return buflen;
|
||||
|
||||
total -= tmp;
|
||||
p += tmp;
|
||||
}
|
||||
|
||||
return tmp;
|
||||
}
|
||||
@@ -5,7 +5,9 @@ Creation-Date: 2011-06-03T21:32:39+08:00
|
||||
====== Linux 网络编程之TIME WAIT状态 ======
|
||||
Created 星期五 03 六月 2011
|
||||
http://blog.csdn.net/feiyinzilgd/archive/2010/09/19/5894446.aspx
|
||||
|
||||
{{./TIME_WAIT.jpg}}
|
||||
|
||||
刚刚开始看TCP socket的4次握手终止流程图的时候,对于最后的TIME_WAIT状态不是很理解.现在在回过头来研究,发现TIME_WAIT状态是一个很微妙状态.之所以设计TIME_WAIT状态的原因有2个原因:
|
||||
|
||||
* 使得TCP的全双工连接能够可靠的终止.
|
||||
@@ -13,14 +15,14 @@ http://blog.csdn.net/feiyinzilgd/archive/2010/09/19/5894446.aspx
|
||||
|
||||
在具体详解这两个原因之前,我们需要理解MSL(maxinum segment lifetime)这个概念.
|
||||
|
||||
每一个TCP 都必须有一个MSL值.这个值一般是2分钟,但也不是固定的,不同的系统不一样.无论是否出错或者连接被断开,总之,一个数据包在网路上能停留的最大时间是MSL.也就是说MSL是数据包的生命周期时间.操作这个时间,该数据包将会被丢弃而不被发送.而TIME_WAIT状态持续的时间是MSL的两倍,也就是2MSL时间.
|
||||
每一个TCP 都必须有一个MSL值.这个值一般是2分钟,但也不是固定的,不同的系统不一样.无论是否出错或者连接被断开,总之,一个数据包在网路上能停留的最大时间是MSL.也就是说MSL是**数据包的生命周期时间.**操作这个时间,该数据包将会被丢弃而不被发送.而TIME_WAIT状态持续的时间是MSL的两倍,也就是2MSL时间.
|
||||
|
||||
TCP的全双工连接能够被可靠终止
|
||||
|
||||
TCP的可靠终止需要经过4次握手终止.如上图所示:首先,client 主动close,导致FIN发送给server,server接收到FIN后,给client回复一个ACK,之后,server会关闭和client的连接,即向client发送一个FIN,client接收到FIN之后,会发送一个ACK给server.此时client就进入TIME_WAIT状态.如果server没有收到ACK,server会重新发送一个FIN信息给client,client会重发ACK,server然后继续等待client发送一个ACK.这样保证了双方的可靠终止.2端都知道对方已经终止了.那么,在这个TIME_WAIT时间中,可以重发ACK,如果client没有收到FIN信息,则TCP会向server发送一个RST信息,这个信息会被server解释成error.
|
||||
TCP的可靠终止需要经过4次握手终止.如上图所示:首先,client 主动close,导致FIN发送给server,server接收到FIN后,给client回复一个ACK,之后,server会关闭和client的连接,即向client发送一个FIN,client接收到FIN之后,会发送一个ACK给server. 此时client就进入TIME_WAIT状态.**如果server没有收到ACK,server会重新发送一个FIN信息给client,client会重发ACK**,server然后继续等待client发送一个ACK.这样保证了双方的可靠终止.2端都知道对方已经终止了.那么,**在这个TIME_WAIT时间中**,可以重发ACK,如果client没有收到FIN信息,则TCP会向server发送一个RST信息,这个信息会被server解释成error.
|
||||
|
||||
连接终止后网络上任然残留的发送到该连接的数据被丢弃而不至于被新连接接收.
|
||||
|
||||
举个例子:
|
||||
|
||||
在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个TCP连接A.然后此链接A被close掉了.然后此时又在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个新的TCP连接B.很可能A和B连接是有不同的应用程序建立的.那么,当我们close掉A之后,网络上很有可能还有属于A连接两端的数据m正在网路上被传送.而此时A被close掉了,重新建立了B连接,由于A和B连接的地址和端口都是一样的.这样,m数据就会被最终发送到B连接的两端.这样就造成了混乱,B接收到了原本数据A的数据.处于TIME_WAIT状态的连接会禁止新的同样的连接(如A,B)连接被建立.除非等到TIME_WAIT状态结束,也就是2MSL时间之后.其中,一个MSL时间是为了网络上的正在被发送到该链接的数据被丢弃,另一个MSL使得应答信息被丢弃.这样,2MSL之后,保证重新建立的所得到的数据绝对不会是发往就连接的数据.
|
||||
在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个TCP连接A.然后此链接A被close掉了.然后此时又在10.12.24.48 port:21和206.8.16.32 port:23(不必关心哪一端是server哪一端是client)之间建立了一个新的TCP连接B.很可能A和B连接是有不同的应用程序建立的.那么,当我们close掉A之后,网络上很有可能还有属于A连接两端的数据m正在网路上被传送.而此时A被close掉了,重新建立了B连接,由于A和B连接的地址和端口都是一样的.这样,m数据就会被最终发送到B连接的两端.这样就造成了混乱,**B接收到了原本数据A的数据**.__处于TIME_WAIT状态的连接会禁止新的同样的连接(如A,B)连接被建立.除非等到TIME_WAIT状态结束,也就是2MSL时间之后__.其中,一个MSL时间是为了网络上的正在被发送到该链接的数据被丢弃,另一个MSL使得应答信息被丢弃.这样,2MSL之后,保证重新建立的所得到的数据绝对不会是发往就连接的数据.
|
||||
|
||||
@@ -17,9 +17,7 @@ http://www.cnblogs.com/xuyuan77/archive/2008/09/06/1285800.html
|
||||
我们将传统方案中的线程执行过程分为三个过程:T1、T2、T3。
|
||||
|
||||
T1:线程创建时间
|
||||
|
||||
T2:线程执行时间,包括线程的同步等时间
|
||||
|
||||
T3:线程销毁时间
|
||||
|
||||
那么我们可以看出,线程本身的开销所占的比例为(T1+T3) / (T1+T2+T3)。如果线程执行的时间很短的话,这比开销可能占到20%-50%左右。如果任务执行时间很频繁的话,这笔开销将是不可忽略的。
|
||||
@@ -35,11 +33,8 @@ http://www.cnblogs.com/xuyuan77/archive/2008/09/06/1285800.html
|
||||
一般线程池都必须具备下面几个组成部分:
|
||||
|
||||
1) 线程池管理器:用于创建并管理线程池
|
||||
|
||||
2) 工作线程: 线程池中实际执行的线程
|
||||
|
||||
2) 工作线程: 线程池中实际执行的线程
|
||||
3) 任务接口: 尽管线程池大多数情况下是用来支持网络服务器,但是我们将线程执行的任务抽象出来,形成任务接口,从而是的线程池与具体的任务无关。
|
||||
|
||||
4) 任务队列:线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。
|
||||
|
||||
我们实现的通用线程池框架由五个重要部分组成CThreadManage,CThreadPool,CThread,CJob,CWorkerThread,除此之外框架中还包括线程同步使用的类CThreadMutex和CCondition。
|
||||
|
||||
@@ -7,13 +7,10 @@ Created 星期五 03 六月 2011
|
||||
|
||||
Linux网络编程中,socket的选项很多.其中几个比较重要的选项有:SO_LINGER(仅仅适用于TCP,SCTP), SO_REUSEADDR.
|
||||
|
||||
SO_LINGER
|
||||
|
||||
__SO_LINGER__
|
||||
在默认情况下,当调用close关闭socke的使用,close会立即返回,但是,如果send buffer中还有数据,系统会试着先把send buffer中的数据发送出去,然后close才返回.
|
||||
|
||||
SO_LINGER选项则是用来修改这种默认操作的.于SO_LINGER相关联的一个结构体如下:
|
||||
|
||||
view plaincopy to clipboardprint?
|
||||
SO_LINGER选项则是用来修改这种默认操作的. SO_LINGER相关联的一个结构体如下:
|
||||
|
||||
#include <sys/socket.h>
|
||||
struct linger {
|
||||
@@ -23,71 +20,66 @@ view plaincopy to clipboardprint?
|
||||
|
||||
当调用setsockopt之后,该选项产生的影响取决于linger结构体中 l_onoff和l_linger的值:
|
||||
|
||||
0 = l_onoff
|
||||
|
||||
**l_onoff = 0**
|
||||
当l_onoff被设置为0的时候,将会关闭SO_LINGER选项,即TCP或则SCTP保持默认操作:close立即返回.l_linger值被忽略.
|
||||
|
||||
l_lineoff值非0,0 = l_linger
|
||||
|
||||
当调用close的时候,TCP连接会立即断开.send buffer中未被发送的数据将被丢弃,并向对方发送一个RST信息.值得注意的是,由于这种方式,是非正常的4中握手方式结束TCP链接,所以,TCP连接将不会进入TIME_WAIT状态,这样会导致新建立的可能和就连接的数据造成混乱。具体原因详见我的上一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
|
||||
l_onoff和l_linger都是非0
|
||||
|
||||
在这种情况下,回事的close返回得到延迟。调用close去关闭socket的时候,内核将会延迟。也就是说,如果send buffer中还有数据尚未发送,该进程将会被休眠直到一下任何一种情况发生:
|
||||
**l_lineoff值非0,l_linger = 0**
|
||||
**当调用close的时候,TCP连接会立即断开.send buffer中未被发送的数据将被丢弃,并向对方发送一个**__RST信息__**.**值得注意的是,由于这种方式,是非正常的4中握手方式结束TCP链接,所以,TCP连接将不会进入TIME_WAIT状态,这样会导致新建立的可能和就连接的数据造成混乱。具体原因详见我的上一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
|
||||
**l_onoff和l_linger都是非0**
|
||||
在这种情况下调用close去关闭socket的时候,内核将会延迟。也就是说,如果send buffer中还有数据尚未发送,该进程将会被休眠直到一下任何一种情况发生:
|
||||
1) send buffer中的所有数据都被发送并且得到对方TCP的应答消息(这种应答并不是意味着对方应用程序已经接收到数据,在后面shutdown将会具体讲道)
|
||||
2) 延迟时间消耗完。在延迟时间被消耗完之后,send buffer中的所有数据都将会被丢弃。
|
||||
2) 延迟时间消耗完。在延迟时间被消耗完之后,send buffer中的所有数据都将会被__丢弃__。
|
||||
|
||||
上面1),2)两种情况中,如果socket被设置为O_NONBLOCK状态,程序将不会等待close返回,send buffer中的所有数据都将会被丢弃。所以,需要我们判断close的返回值。在send buffer中的所有数据都被发送之前并且延迟时间没有消耗完,close返回的话,close将会返回一个EWOULDBLOCK的error.
|
||||
|
||||
下面用几个实例来说明:
|
||||
|
||||
|
||||
A. Close默认操作:立即返回
|
||||
A. __Close默认操作:立即返回__
|
||||
{{./1.jpg}}
|
||||
|
||||
|
||||
此种情况,close立即返回,如果send buffer中还有数据,close将会等到所有数据被发送完之后之后返回。由于我们并没有等待对方TCP发送的ACK信息,所以我们只能保证数据已经发送到对方,我们并不知道对方是否已经接受了数据。由于此种情况,TCP连接终止是按照正常的4次握手方式,需要经过TIME_WAIT。
|
||||
此种情况,__close立即返回,如果send buffer中还有数据,close将会等到所有数据被发送完之后之后返回__。由于我们并没有等待对方TCP发送的ACK信息,所以__我们只能保证数据已经发送到对方,我们并不知道对方是否已经接受了数据。__由于此种情况,TCP连接终止是按照正常的4次握手方式,需要经过TIME_WAIT。
|
||||
|
||||
B. l_onoff非0,并且使之l_linger为一个整数
|
||||
B. l_onoff非0,并且使之l_linger为一个整数
|
||||
|
||||
{{./2.jpg}}
|
||||
|
||||
在这种情况下,close会在接收到对方TCP的ACK信息之后才返回(l_linger消耗完之前)。但是这种ACK信息只能保证对方已经接收到数据,并不保证对方应用程序已经读取数据。
|
||||
在这种情况下,__close会在接收到对方TCP的ACK信息之后才返回(l_linger消耗完之前)。但是这种ACK信息只能保证对方已经接收到数据,并不保证对方应用程序已经读取数据。__
|
||||
|
||||
C. l_linger设置值太小
|
||||
C. l_linger设置值太小
|
||||
|
||||
{{./3.jpg}}
|
||||
|
||||
这种情况,由于l_linger值太小,在send buffer中的数据都发送完之前,close就返回,此种情况终止TCP连接,更l_linger = 0类似,TCP连接终止不是按照正常的4步握手,所以,TCP连接不会进入TIME_WAIT状态,那么,client会向server发送一个RST信息.
|
||||
这种情况,__由于l_linger值太小,在send buffer中的数据都发送完之前,close就返回,此种情况终止TCP连接,与l_linger = 0类似,TCP连接终止不是按照正常的4步握手,所以,TCP连接不会进入TIME_WAIT状态,那么,client会向server发送一个RST信息.__
|
||||
|
||||
D. Shutdown,等待应用程序读取数据
|
||||
D. Shutdown,等待应用程序读取数据
|
||||
|
||||
{{./4.jpg}}
|
||||
|
||||
同上面的B进行对比,调用shutdown后紧接着调用read,此时read会被阻塞,直到接收到对方的FIN,也就是说read是在server的应用程序调用close之后才返回的。当server应用程序读取到来自client的数据和FIN之后,server会进入一个叫CLOSE_WAIT,关于CLOSE_WAIT,详见我的博客《 Linux 网络编程 之 TCP状态转换》 。那么,如果server端要断开该TCP连接,需要server应用程序调用一次close,也就意味着向client发送FIN。这个时候,说明server端的应用程序已经读取到client发送的数据和FIN。read会在接收到server的FIN之后返回。所以,shutdown 可以确保server端应用程序已经读取数据了,而不仅仅是server已经接收到数据而已。
|
||||
同上面的B进行对比,调用shutdown后紧接着调用read,此时read会被阻塞,直到接收到对方的FIN,也就是说read是在server的应用程序调用close之后才返回的。当server应用程序读取到来自client的数据和FIN之后,server会进入一个叫CLOSE_WAIT,关于CLOSE_WAIT,详见我的博客《 Linux 网络编程 之 TCP状态转换》 。那么,如果server端要断开该TCP连接,需要server应用程序调用一次close,也就意味着向client发送FIN。这个时候,说明server端的应用程序已经读取到client发送的数据和FIN。read会在接收到server的FIN之后返回。所以,__shutdown 可以确保server端应用程序已经读取数据了,而不仅仅是server已经接收到数据而已。__
|
||||
|
||||
shutdown参数如下:
|
||||
|
||||
SHUT_RD:调用shutdown的一端receive buffer将被丢弃掉,无法接受数据,但是可以发送数据,send buffer的数据可以被发送出去
|
||||
|
||||
SHUT_WR:调用shutdown的一端无法发送数据,但是可以接受数据.该参数表示不能调用send.但是如果还有数据在send buffer中,这些数据还是会被继续发送出去的.
|
||||
|
||||
SO_REUSEADDR和SO_REUSEPORT
|
||||
|
||||
最近,看到CSDN的linux版块,有人提问,说为什么server程序重启之后,无法连接,需要过一段时间才能连接上.我想对于这个问题,有两种可能:一种可能就是该server一直停留在TIME_WAIT状态.这个时候,需要等待2MSL的时间才能重新连接上,具体细节原因请见我的另一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
SO_REUSEADDR和SO_REUSEPORT
|
||||
最近,看到CSDN的linux版块,有人提问,说为什么server程序重启之后,无法连接,需要过一段时间才能连接上.我想对于这个问题,有两种可能:
|
||||
|
||||
一种可能就是__该server一直停留在TIME_WAIT状态__.这个时候,需要等待2MSL的时间才能重新连接上,具体细节原因请见我的另一篇文章《linux 网络编程之TIME_WAIT状态》
|
||||
另一种可能就是SO_REUSEADDR参数设置问题.关于TIME_WAIT的我就不在这里重述了,这里我讲一讲SO_REUSEADDR.
|
||||
|
||||
SO_REUSEADDR允许一个server程序listen监听并bind到一个端口,既是这个端口已经被一个正在运行的连接使用了.
|
||||
SO_REUSEADDR允许一个server程序listen监听并bind到一个端口,即使这个端口已经被一个正在运行的连接使用了.
|
||||
|
||||
我们一般会在下面这种情况中遇到:
|
||||
|
||||
一个监听(listen)server已经启动
|
||||
当有client有连接请求的时候,server产生一个子进程去处理该client的事物.
|
||||
server主进程终止了,但是子进程还在占用该连接处理client的事情.虽然子进程终止了,但是由于子进程没有终止,该socket的引用计数不会为0,所以该socket不会被关闭.
|
||||
server程序重启.
|
||||
* 一个监听(listen)server已经启动
|
||||
* 当有client有连接请求的时候,server产生一个子进程去处理该client的事物.
|
||||
* server主进程终止了,但是子进程还在占用该连接处理client的事情.虽然父进程终止了,但是由于子进程没有终止,该socket的引用计数不会为0,所以该socket不会被关闭.
|
||||
* server程序重启.
|
||||
|
||||
默认情况下,server重启,调用socket,bind,然后listen,会失败.因为该端口正在被使用.如果设定SO_REUSEADDR,那么server重启才会成功.因此,所有的TCP server都必须设定此选项,用以应对server重启的现象.
|
||||
默认情况下,server重启,调用socket,bind,然后listen,会失败.因为该端口正在被使用.如果设定SO_REUSEADDR,那么server重启才会成功.因此,__所有的TCP server都必须设定此选项,用以应对server重启的现象.__
|
||||
|
||||
SO_REUSEADDR允许同一个端口上绑定多个IP.只要这些IP不同.另外,还可以在绑定IP通配符.但是最好是先绑定确定的IP,最后绑定通配符IP.一面系统拒绝.简而言之,SO_REUSEADDR允许多个server绑定到同一个port上,只要这些server指定的IP不同,但是SO_REUSEADDR需要在bind调用之前就设定.在TCP中,不允许建立起一个已经存在的相同的IP和端口的连接.但是在UDP中,是允许的.
|
||||
SO_REUSEADDR允许同一个端口上绑定多个IP.只要这些IP不同.另外,还可以在绑定IP通配符.但是最好是先绑定确定的IP,最后绑定通配符IP.以免系统拒绝.简而言之,SO_REUSEADDR允许多个server绑定到同一个port上,只要这些server指定的IP不同,__但是SO_REUSEADDR需要在bind调用之前就设定__.在TCP中,不允许建立起一个已经存在的相同的IP和端口的连接.但是在UDP中,是允许的.
|
||||
|
||||
@@ -4,10 +4,9 @@ Creation-Date: 2011-06-04T14:36:51+08:00
|
||||
|
||||
====== Socket数据发送中信号SIGPIPE及相关errno的研究 ======
|
||||
Created 星期六 04 六月 2011
|
||||
Socket数据发送中信号SIGPIPE及相关errno的研究
|
||||
好久没做过C开发了,最近重操旧业。
|
||||
听说另外一个项目组socket开发遇到问题,发送端和接受端数据大小不一致。建议他们采用writen的重发机制,以避免信号中断错误。采用后还是有问题。PM让我帮忙研究下。
|
||||
UNP n年以前看过,很久没做过底层开发,手边也没有UNP vol1这本书,所以做了个测试程序,研究下实际可能发生的情况了。
|
||||
原文地址 http://blog.chinaunix.net/u/31357/showart_242605.html
|
||||
|
||||
听说另外一个项目组socket开发遇到问题,发送端和接受端数据大小不一致。建议他们采用writen的重发机制,以避免信号中断错误。采用后还是有问题。PM让我帮忙研究下。UNP n年以前看过,很久没做过底层开发,手边也没有UNP vol1这本书,所以做了个测试程序,研究下实际可能发生的情况了。
|
||||
|
||||
测试环境:AS3和redhat 9(缺省没有nc)
|
||||
|
||||
@@ -67,12 +66,9 @@ main(int argc, char **argv)
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
||||
|
||||
为了方便观察错误输出,lib/writen.c也做了修改,加了些日志:
|
||||
|
||||
|
||||
|
||||
/* include writen */
|
||||
#include "unp.h"
|
||||
|
||||
@@ -112,20 +108,14 @@ Writen(int fd, void *ptr, size_t nbytes)
|
||||
}
|
||||
|
||||
|
||||
|
||||
client.c放在tcpclieserv目录下,修改了Makefile,增加了client.c的编译目标
|
||||
|
||||
|
||||
|
||||
|
||||
client: client.c
|
||||
${CC} ${CFLAGS} -o $@ $< ${LIBS}
|
||||
|
||||
|
||||
|
||||
接着就可以开始测试了。
|
||||
测试1 忽略SIGPIPE信号,writen之前,对方关闭接受进程
|
||||
|
||||
测试1 忽略SIGPIPE信号,writen之前,对方关闭接受进程
|
||||
本机服务端:
|
||||
nc -l -p 30000
|
||||
|
||||
@@ -141,7 +131,8 @@ Already write 40960, left 0, errno=0
|
||||
Begin send 40960 data
|
||||
Begin Writen 40960
|
||||
writen error: Broken pipe(32)
|
||||
结论:可见write之前,对方socket中断,发送端write会返回-1,errno号为EPIPE(32)
|
||||
结论:__可见write之前,对方socket中断,发送端write会返回-1,errno号为EPIPE(32)__
|
||||
|
||||
测试2 catch SIGPIPE信号,writen之前,对方关闭接受进程
|
||||
|
||||
修改客户端代码,catch sigpipe信号
|
||||
@@ -167,7 +158,7 @@ Begin send 40960 data
|
||||
Begin Writen 40960
|
||||
Signal is 13
|
||||
writen error: Broken pipe(32)
|
||||
结论:可见write之前,对方socket中断,发送端write时,会先调用SIGPIPE响应函数,然后write返回-1,errno号为EPIPE(32)
|
||||
结论:__可见write之前,对方socket中断,发送端write时,会先调用SIGPIPE响应函数,然后write返回-1,errno号为EPIPE(32)__
|
||||
|
||||
测试3 writen过程中,对方关闭接受进程
|
||||
|
||||
@@ -186,7 +177,7 @@ Already write 589821, left 3506179, errno=0
|
||||
Begin Writen 3506179
|
||||
writen error: Connection reset by peer(104)
|
||||
|
||||
结论:可见socket write中,对方socket中断,发送端write会先返回已经发送的字节数,再次write时返回-1,errno号为ECONNRESET(104)
|
||||
结论:__可见socket write中,对方socket中断,发送端write会先返回已经发送的字节数,再次write时返回-1,errno号为ECONNRESET(104)__
|
||||
|
||||
为什么以上测试,都是对方已经中断socket后,发送端再次write,结果会有所不同呢。从后来找到的UNP5.12,5.13能找到答案
|
||||
|
||||
@@ -203,6 +194,3 @@ If the process either catches the signal and returns from the signal handler, or
|
||||
以上解释了测试1,2的现象,write一个已经接受到RST的socket,系统内核会发送SIGPIPE给发送进程,如果进程catch/ignore这个信号,write都返回EPIPE错误.
|
||||
|
||||
因此,UNP建议应用根据需要处理SIGPIPE信号,至少不要用系统缺省的处理方式处理这个信号,系统缺省的处理方式是退出进程,这样你的应用就很难查处处理进程为什么退出。
|
||||
|
||||
原文地址 http://blog.chinaunix.net/u/31357/showart_242605.html
|
||||
|
||||
|
||||
182
Zim/Programme/APUE/pthreads_-_POSIX_threads.txt
Normal file
@@ -0,0 +1,182 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-23T16:27:29+08:00
|
||||
|
||||
====== pthreads - POSIX threads ======
|
||||
Created Tuesday 23 October 2012
|
||||
http://linux.die.net/man/7/pthreads
|
||||
|
||||
===== Name =====
|
||||
pthreads - POSIX threads
|
||||
|
||||
===== Description =====
|
||||
POSIX.1 specifies __a set of interfaces__ (functions, header files) for threaded programming commonly known as **POSIX threads, or Pthreads**. A single process can contain multiple threads, all of which are executing the same program. These threads share **the same global memory** (data and heap segments), but each thread has **its own stack** (automatic variables).
|
||||
|
||||
POSIX.1 also requires that threads share a range of other attributes (i.e., these attributes are __process-wide__ rather than per-thread):
|
||||
- process ID
|
||||
- parent process ID
|
||||
- process group ID and session ID
|
||||
- controlling terminal
|
||||
- user and group IDs
|
||||
- open file descriptors
|
||||
- record locks (see fcntl(2))
|
||||
- signal dispositions
|
||||
- file mode creation mask (umask(2))
|
||||
- current directory (chdir(2)) and root directory (chroot(2))
|
||||
- interval timers (**setitimer**(2)) and POSIX timers (timer_create(2))
|
||||
- nice value (setpriority(2))
|
||||
- resource limits (setrlimit(2))
|
||||
- measurements of the consumption of CPU time (times(2)) and resources (getrusage(2))
|
||||
|
||||
As well as the stack, POSIX.1 specifies that various other attributes are distinct for each thread, including:
|
||||
- thread ID (the **pthread_t** data type)
|
||||
- signal mask (**pthread_sigmask**(3))
|
||||
- the errno variable
|
||||
- alternate signal stack (__sigaltstack__(2))
|
||||
- real-time scheduling policy and priority (sched_setscheduler(2) and sched_setparam(2))
|
||||
|
||||
The following Linux-specific features are also per-thread:
|
||||
- capabilities (see capabilities(7))
|
||||
- CPU affinity (sched_setaffinity(2))
|
||||
|
||||
===== Pthreads function return values =====
|
||||
Most pthreads functions return **0 on success, and an error number of failure**. Note that the pthreads functions __do not set errno__. For each of the pthreads functions that can return an error, POSIX.1-2001 specifies that the function can never fail with the error EINTR.
|
||||
|
||||
===== Thread IDs =====
|
||||
Each of the threads in a process has a unique thread identifier (stored in the type pthread_t). This identifier is returned to the caller of **pthread_create(3)**, and a thread can obtain its own thread identifier using **pthread_self(3)**. Thread IDs are only __guaranteed to be unique within a process__. A thread ID may be reused after a terminated thread has been joined, or a detached thread has terminated. In all pthreads functions that accept a thread ID as an argument, that ID by definition refers to a thread in the same process as the caller.
|
||||
|
||||
===== Thread-safe functions =====
|
||||
A thread-safe function is one that can be safely (i.e., it will deliver the same results regardless of whether it is) called from multiple threads at the same time.
|
||||
POSIX.1-2001 and POSIX.1-2008 require that all functions specified in the standard shall be thread-safe, __except__ for the following functions:
|
||||
|
||||
asctime()
|
||||
basename()
|
||||
catgets()
|
||||
crypt()
|
||||
ctermid() if passed a non-NULL argument
|
||||
ctime()
|
||||
........
|
||||
|
||||
===== Async-cancel-safe functions =====
|
||||
An async-cancel-safe function is one that can **be safely called** in an application where asynchronous cancelability is enabled (see **pthread_setcancelstate**(3)).
|
||||
Only the following functions are required to be async-cancel-safe by POSIX.1-2001 and POSIX.1-2008:
|
||||
|
||||
pthread_cancel()
|
||||
pthread_setcancelstate()
|
||||
pthread_setcanceltype()
|
||||
|
||||
===== Cancellation Points =====
|
||||
POSIX.1 specifies that certain functions must, and certain other functions may, be cancellation points. If a thread is __cancelable__, its cancelability type is __deferred__, and a cancellation request is __pending__ for the thread, then the thread is canceled when it calls a function that is a cancellation point.
|
||||
The following functions are required to be cancellation points by POSIX.1-2001 and/or POSIX.1-2008:
|
||||
|
||||
accept()
|
||||
aio_suspend()
|
||||
clock_nanosleep()
|
||||
.........
|
||||
|
||||
The following functions __may be cancellation points__ according to POSIX.1-2001 and/or POSIX.1-2008:
|
||||
access()
|
||||
asctime()
|
||||
..........
|
||||
An implementation may also mark other functions not specified in the standard as cancellation points. In particular, an implementation is likely to mark any nonstandard function that may block as a cancellation point. (This includes most functions that can touch files.)
|
||||
|
||||
===== Compiling on Linux =====
|
||||
On Linux, programs that use the Pthreads API should be compiled using cc __-pthread__.
|
||||
|
||||
===== Linux Implementations of POSIX Threads =====
|
||||
Over time, two threading implementations have been provided by the GNU C library on Linux:
|
||||
* LinuxThreads
|
||||
This is the original Pthreads implementation. Since glibc 2.4, this implementation is **no longer supported**.
|
||||
* NPTL (Native POSIX Threads Library)
|
||||
This is the modern Pthreads implementation. By comparison with LinuxThreads, NPTL provides __closer conformance__ to the requirements of the POSIX.1 specification and better performance when creating large numbers of threads. NPTL is available since glibc 2.3.2, and requires features that are present in the Linux 2.6 kernel(**futex和实时信号**).
|
||||
|
||||
Both of these are so-called __1:1 implementations__, meaning that each thread maps to a kernel scheduling entity. Both threading implementations employ the Linux clone(2) system call. In NPTL, thread synchronization primitives (mutexes, thread joining, etc.) are implemented using the Linux __futex__(2) system call.
|
||||
|
||||
==== LinuxThreads ====
|
||||
The notable features of this implementation are the following:
|
||||
|
||||
- In addition to the main (initial) thread, and the threads that the program creates using pthread_create(3), the implementation creates __a "manager" thread__. This thread handles thread creation and termination. (Problems can result if this thread is inadvertently killed.注意,管理线程是函数库**内部自动生成**的,而不是指调用进程。)
|
||||
|
||||
- Signals are used internally by the implementation. On Linux 2.2 and later, **the first three real-time signals** are used (see also signal(7)). On older Linux kernels, SIGUSR1 and SIGUSR2 are used. Applications must avoid the use of whichever set of signals is employed by the implementation.
|
||||
|
||||
- Threads __do not__ share process IDs. (In effect, LinuxThreads threads are **implemented as processes** which share more information than usual, but which do not share a common process ID.) LinuxThreads threads (including the manager thread) are **visible as separate processes** using ps(1).
|
||||
|
||||
The LinuxThreads implementation deviates from the POSIX.1 specification in a number of ways, including the following:
|
||||
下面是LinuxThreads实现的与POSIX.1规范__不符__的地方:
|
||||
|
||||
- Calls to getpid(2) return a different value in each thread.
|
||||
|
||||
- Calls to getppid(2) in threads other than the main thread return **the process ID of the manager thread**; instead getppid(2) in these threads should return the same value as getppid(2) in the main thread. 这里的main thread指的是最开始的(initial)线程。
|
||||
|
||||
- When one thread creates a new child process using fork(2), any thread should be able to wait(2) on the child. However, the implementation only allows the thread that created the child to wait(2) on it.
|
||||
|
||||
- When a thread calls execve(2), all other threads are terminated (as required by POSIX.1). However, the resulting process has the same PID as the thread that called execve(2): it should have the same PID as the main thread.
|
||||
|
||||
- Threads do not share user and group IDs. This can cause complications with set-user-ID programs and can cause failures in Pthreads functions if an application changes its credentials using seteuid(2) or similar.
|
||||
|
||||
- Threads do not share a common session ID and process group ID.
|
||||
|
||||
- Threads do not share record locks created using fcntl(2).
|
||||
|
||||
- The information returned by times(2) and getrusage(2) is per-thread rather than process-wide.
|
||||
|
||||
- Threads do not share semaphore undo values (see semop(2)).
|
||||
|
||||
- Threads do not share interval timers.
|
||||
|
||||
- Threads do not share a common nice value.
|
||||
|
||||
- POSIX.1 distinguishes the notions of signals that are directed to the process as a whole and signals that are directed to individual threads. According to POSIX.1, __a process-directed signal__ (sent using kill(2), for example) should be handled by a single, arbitrarily selected thread within the process. LinuxThreads does not support the notion of process-directed signals: signals may only be sent to specific threads.
|
||||
|
||||
- Threads have distinct alternate signal stack settings. However, a new thread's alternate signal stack settings are copied from the thread that created it, so that the threads initially share an alternate signal stack. (A new thread should start with no alternate signal stack defined. If two threads handle signals on their shared alternate signal stack at the same time, unpredictable program failures are likely to occur.)
|
||||
|
||||
==== NPTL ====
|
||||
With NPTL, all of the threads in a process are placed in **the same thread group**; all members of a thread group share the same PID. NPTL does not employ a manager thread. NPTL makes internal use of the first two real-time signals (see also signal(7)); these signals cannot be used in applications.
|
||||
|
||||
NPTL still has at least __one nonconformance__ with POSIX.1:
|
||||
|
||||
- Threads do not share a common nice value.
|
||||
|
||||
Some NPTL nonconformances only occur with older kernels:
|
||||
- The information returned by times(2) and getrusage(2) is per-thread rather than process-wide (fixed in kernel 2.6.9).
|
||||
- Threads do not share resource limits (fixed in kernel 2.6.10).
|
||||
- Threads do not share interval timers (fixed in kernel 2.6.12).
|
||||
- Only the __main thread__ is permitted to start a new session using setsid(2) (fixed in kernel 2.6.16).
|
||||
- Only the main thread is permitted to make the process into a process group leader using setpgid(2) (fixed in kernel 2.6.16).
|
||||
- Threads have distinct alternate signal stack settings. However, a new thread's alternate signal stack settings are copied from the thread that created it, so that the threads initially share an alternate signal stack (fixed in kernel 2.6.16).
|
||||
|
||||
Note the following further points about the NPTL implementation:
|
||||
-
|
||||
If the stack size soft resource limit (see the description of RLIMIT_STACK in setrlimit(2)) is set to a value other than unlimited, then this value defines the default stack size for new threads. To be effective, this limit must be set before the program is executed, perhaps using the ulimit -s shell built-in command (limit stacksize in the C shell).
|
||||
|
||||
===== Determining the Threading Implementation =====
|
||||
Since glibc 2.3.2, the __getconf(1)__ command can be used to determine the system's threading implementation, for example:
|
||||
bash$ getconf GNU_LIBPTHREAD_VERSION
|
||||
NPTL 2.3.4
|
||||
|
||||
With older glibc versions, a command such as the following should be sufficient to determine the default threading implementation:
|
||||
bash$ $( ldd /bin/ls | grep libc.so | awk '{print $3}' ) | \
|
||||
egrep -i 'threads|nptl'
|
||||
Native POSIX Threads Library by Ulrich Drepper et al
|
||||
|
||||
[geekard@kb310 ~]$ __/lib/libpthread.so.0__
|
||||
Native POSIX Threads Library by Ulrich Drepper et al
|
||||
Copyright (C) 2006 Free Software Foundation, Inc.
|
||||
This is free software; see the source for copying conditions.
|
||||
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
|
||||
PARTICULAR PURPOSE.
|
||||
Forced unwind support included.
|
||||
[geekard@kb310 ~]$
|
||||
|
||||
===== Selecting the Threading Implementation: LD_ASSUME_KERNEL =====
|
||||
On systems with a glibc that supports both LinuxThreads and NPTL (i.e., glibc 2.3.x), the LD_ASSUME_KERNEL environment variable can be used to override the dynamic linker's default choice of threading implementation. **This variable tells the dynamic linker to assume that it is running on top of a particular kernel version.** __By specifying a kernel version that does not provide the support required by NPTL, we can force the use of LinuxThreads.__ (The most likely reason for doing this is to run a (broken) application that depends on some nonconformant behavior in LinuxThreads.) For example:
|
||||
bash$ $( **LD_ASSUME_KERNEL=2.2.5** ldd /bin/ls | grep libc.so | \
|
||||
awk '{print $3}' ) | egrep -i 'threads|ntpl'
|
||||
linuxthreads-0.10 by Xavier Leroy
|
||||
|
||||
===== See Also =====
|
||||
clone(2), futex(2), gettid(2), proc(5), futex(7), sigevent(7), signal(7),
|
||||
and various Pthreads manual pages, for example: pthread_attr_init(3), pthread_atfork(3), pthread_cancel(3), pthread_cleanup_push(3), pthread_cond_signal(3), pthread_cond_wait(3), pthread_create(3), pthread_detach(3), pthread_equal(3), pthread_exit(3), pthread_key_create(3), pthread_kill(3), pthread_mutex_lock(3), pthread_mutex_unlock(3), pthread_once(3), pthread_setcancelstate(3), pthread_setcanceltype(3), pthread_setspecific(3), pthread_sigmask(3), pthread_sigqueue(3), and pthread_testcancel(3)
|
||||
|
||||
===== Referenced By =====
|
||||
core(5), intro(3), pthread_attr_getdetachstate(3), pthread_attr_getscope(3), pthread_attr_getstackaddr(3), pthread_attr_setaffinity_np(3), pthread_attr_setguardsize(3), pthread_attr_setinheritsched(3), pthread_attr_setschedparam(3), pthread_attr_setschedpolicy(3), pthread_attr_setstack(3), pthread_attr_setstacksize(3), pthread_cleanup_push_defer_np(3), pthread_getattr_np(3), pthread_getcpuclockid(3), pthread_join(3), pthread_kill_other_threads_np(3), pthread_setaffinity_np(3), pthread_setconcurrency(3), pthread_setschedparam(3), pthread_setschedprio(3), pthread_tryjoin_np(3), pthread_yield(3), sem_overview(7), vfork(2), xfs_copy(8)
|
||||
@@ -6,7 +6,7 @@ Creation-Date: 2011-06-03T21:37:25+08:00
|
||||
Created 星期五 03 六月 2011
|
||||
http://blog.csdn.net/factor2000/archive/2009/02/23/3929816.aspx
|
||||
|
||||
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
|
||||
此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
|
||||
|
||||
SO_LINGER选项用来改变此缺省设置。使用如下结构:
|
||||
|
||||
@@ -19,12 +19,13 @@ struct linger {
|
||||
有下列三种情况:
|
||||
|
||||
1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,**close调用会立即返回**给调用者,如果可能将会传输任何未发送的数据(调用进程立即结束,但进程对应的TCP发送缓冲区中可能还有未发送完的数据,所以TCP连接可能会延迟一段时间后关闭,这个是正常的TIME_WAIT状态);
|
||||
2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个__RST__给对方,而不是通常的四分组终止序列,这避免了**TIME_WAIT**状态;
|
||||
3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到(超时),close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。
|
||||
|
||||
2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将**丢弃保留**在套接口发送缓冲区中的任何数据并发送一个__RST__给对方,而不是通常的四分组终止序列,这避免了**TIME_WAIT**状态;
|
||||
|
||||
3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到(a)所有数据发送完且__被对方确认__,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序__检查close的返回值__是非常重要的,如果在数据发送完并被确认前时间到(超时),close将__返回EWOULDBLOCK错误__且套接口发送缓冲区中的任何数据都__丢失__。close的成功返回仅告诉我们发送的数据(和FIN)__已由对方TCP确认__,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。
|
||||
|
||||
注释:l_linger的单位依赖于实现: 4.4BSD假设其单位是时钟滴答(百分之一秒),但Posix.1g规定单位为秒。
|
||||
|
||||
|
||||
下面的代码是一个使用SO_LINGER选项的例子,使用30秒的超时时限:
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
@@ -42,15 +43,15 @@ z = setsockopt(s,
|
||||
if ( z )
|
||||
perror("setsockopt(2)");
|
||||
|
||||
下面的例子显示了如何设置SO_LINGER的值来中止套接口s上的当前连接:
|
||||
下面的例子显示了如何设置SO_LINGER的值来**中止套接口s上的当前连接**:
|
||||
#define TRUE 1
|
||||
#define FALSE 0
|
||||
int z; /* Status code */
|
||||
int s; /* Socket s */
|
||||
struct linger so_linger;
|
||||
...
|
||||
so_linger.l_onoff = TRUE;
|
||||
so_linger.l_linger = 0;
|
||||
__so_linger.l_onoff = TRUE;__
|
||||
__so_linger.l_linger = 0;__
|
||||
z = setsockopt(s,
|
||||
SOL_SOCKET,
|
||||
SO_LINGER,
|
||||
@@ -60,45 +61,7 @@ if ( z )
|
||||
perror("setsockopt(2)");
|
||||
**close(s); /* Abort connection */**
|
||||
|
||||
|
||||
在上面的这个例子中,当调用close函数时,套接口s会立即中止。__中止的语义是通过将超时值设置为0来实现的__。
|
||||
正常情况下,TCP收到不在已有连接中的数据(但不包括序号是将来收到的哪些)时会自动发送RST给对方,应用进程是不知晓的。
|
||||
但应用进程可以通过将l_linger设为0然后调用close的方法来异常终止(不是通常的四次握手,而是通过RST)与对方的通信。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/********** WINDOWS **********/
|
||||
|
||||
|
||||
|
||||
/* 当连接中断时,需要延迟关闭(linger)以保证所有数据都被传输,所以需要打开SO_LINGER这个选项;
|
||||
* //注:大致意思就是说SO_LINGER选项用来设置当调用closesocket时是否马上关闭socket;
|
||||
* linger的结构在/usr/include/linux/socket.h中定义://注:这个结构就是SetSocketOpt中的Data的数据结构
|
||||
* struct linger
|
||||
* {
|
||||
* int l_onoff; /* Linger active */ //低字节,0和非0,用来表示是否延时关闭socket
|
||||
* int l_linger; /* How long to linger */ //高字节,延时的时间数,单位为秒
|
||||
* };
|
||||
* 如果l_onoff为0,则延迟关闭特性就被取消。
|
||||
|
||||
* 如果非零,则允许套接口延迟关闭; l_linger字段则指明延迟关闭的时间
|
||||
*/
|
||||
|
||||
|
||||
更具体的描述如下:
|
||||
1、若设置了SO_LINGER(亦即linger结构中的l_onoff域设为非零),并设置了零超时间隔,则closesocket()不被阻塞立即执行,不论是否有排队数据未发送或未被确认。这种关闭方式称为“强制”或“失效”关闭,因为套接口的虚电路立即被复位,且丢失了未发送的数据。在远端的recv()调用将以WSAECONNRESET出错。
|
||||
|
||||
2、若设置了SO_LINGER并确定了非零的超时间隔,则closesocket()调用阻塞进程,直到所剩数据发送完毕或超时。这种关闭称为“优雅”或“从容”关闭。请注意如果套接口置为非阻塞且SO_LINGER设为非零超时,则closesocket()调用将以WSAEWOULDBLOCK错误返回。
|
||||
|
||||
3、若在一个流类套接口上设置了SO_DONTLINGER(也就是说将linger结构的l_onoff域设为零),则closesocket()调用立即返回。但是,如果可能,排队的数据将在套接口关闭前发送。请注意,在这种情况下WINDOWS套接口实现将在一段不确定的时间内保留套接口以及其他资源,这对于想用所以套接口的应用程序来说有一定影响。
|
||||
|
||||
|
||||
SO_DONTLINGER 若为真,则SO_LINGER选项被禁止。
|
||||
SO_LINGER延迟关闭连接 struct linger上面这两个选项影响close行为;
|
||||
|
||||
|
||||
选项 间隔 关闭方式 等待关闭与否
|
||||
SO_DONTLINGER 不关心 优雅 否
|
||||
SO_LINGER 零 强制 否
|
||||
SO_LINGER 非零 优雅 是
|
||||
|
||||
72
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能.txt
Normal file
@@ -0,0 +1,72 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-23T10:13:38+08:00
|
||||
|
||||
====== 使用sendfile() 提升网络文件发送性能 ======
|
||||
Created Tuesday 23 October 2012
|
||||
http://hily.me/blog/2011/01/use-sendfile-accelerate-file-sending/
|
||||
|
||||
偶见一好文,清楚地阐述了什么是零拷贝(Zero Copy)以及 sendfile 的由来,不复述下实感不快。
|
||||
原文见:http://www.linuxjournal.com/article/6345
|
||||
|
||||
文章中列出了我们平时通过网络发送文件时会用到的两个系统调用:
|
||||
read(file, tmp_buf, len);
|
||||
write(socket, tmp_buf, len);
|
||||
|
||||
调用过程示意图如下:
|
||||
{{./1.jpg}}
|
||||
|
||||
在用户空间调用 read() 读取文件时发生两次内存拷贝:
|
||||
|
||||
* DMA引擎将文件读取到内核的文件缓冲区
|
||||
* 调用返回用户空间时将内核的文件缓冲区的数据复制到用户空间的缓冲区
|
||||
|
||||
接着调用 write() 把数据写入 socket 时,又发生了两次内存拷贝:
|
||||
|
||||
* 将用户空间的缓冲区的数据复制到内核的 socket 缓冲区
|
||||
* 将内核 socket 缓冲区的数据复制到网络协议引擎
|
||||
|
||||
也就是说,在整个文件发送的过程中,发生了四次内存拷贝。
|
||||
然后,数据读取到用户空间后并没有做过任何加工处理,因此通过网络发送文件时,
|
||||
根本没有必要把文件内容复制到用户空间。
|
||||
|
||||
于是引入了 mmap():
|
||||
tmp_buf = mmap(file, len);
|
||||
write(socket, tmp_buf, len);
|
||||
|
||||
调用过程示意图:
|
||||
{{./2.jpg}}
|
||||
|
||||
* 调用 mmap() 时会将文件直接读取到内核缓冲区,并把内核缓冲区直接__共享__到用户空间
|
||||
* 调用 write() 时,直接将内核缓冲区的数据复制到 socket 缓冲区
|
||||
|
||||
这样一来,就少了用户空间和内核空间之间的内存复制了。这种方式会有个问题,当前进程
|
||||
在调用 write() 时,另一个进程把文件清空了,程序就会报出 SIGBUS 类型错误。
|
||||
|
||||
Linux Kernel __2.1__ 引进了 sendfile(),只需要一个系统调用来实现文件发送。
|
||||
sendfile(socket, file, len);
|
||||
|
||||
调用过程示意图:
|
||||
{{./3.jpg}}
|
||||
* 调用 sendfile() 时会直接在内核空间把文件读取到内核的文件缓冲区
|
||||
* 将内核的文件缓冲区的数据复制到内核的 socket 缓冲区中
|
||||
* 将内核的 socket 缓冲区的数据复制到网络协议引擎
|
||||
|
||||
从性能上看,这种方式只是少了一个系统调用而已,还是做了3次拷贝操作。
|
||||
|
||||
Linux Kernel __2.4__ 改进了 sendfile(),调用接口没有变化:
|
||||
sendfile(socket, file, len);
|
||||
|
||||
调用过程示意图:
|
||||
{{./4.jpg}}
|
||||
|
||||
* 调用 sendfile() 时会直接在内核空间把文件读取到内核的文件缓冲区
|
||||
* 内核的 socket 缓冲区中保存的是当前要发送的数据在内核的文件缓冲区中的位置和偏移量
|
||||
* DMA gather copy 将内核的文件缓冲区的数据复制到网络协议引擎
|
||||
|
||||
这样就只剩下2次拷贝啦。
|
||||
|
||||
在许多 http server 中,都引入了 sendfile 的机制,如 nginx、lighttpd 等,它们正是利用
|
||||
sendfile() 这个特性来实现高性能的文件发送的。
|
||||
|
||||
– EOF –
|
||||
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/1.jpg
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/2.jpg
Normal file
|
After Width: | Height: | Size: 68 KiB |
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/3.jpg
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
Zim/Programme/APUE/使用sendfile()_提升网络文件发送性能/4.jpg
Normal file
|
After Width: | Height: | Size: 57 KiB |
@@ -6,8 +6,6 @@ Creation-Date: 2011-10-07T18:42:52+08:00
|
||||
Created Friday 07 October 2011
|
||||
http://yibin.us/archives/6817
|
||||
|
||||
在一些python讨论版里,经常会见到一些“月经帖”,类似于“我用python读取一个文件乱码”,然后就会抱怨python的编码很麻烦,其实不是python编码难搞定,而是没有真正理解python的编码。
|
||||
|
||||
如在windows环境下的以下示例代码:
|
||||
|
||||
#!/usr/bin/env python
|
||||
@@ -21,9 +19,10 @@ if __name__=='__main__':
|
||||
do()
|
||||
|
||||
此时的ansi.txt编码为ansi,我们在cmd窗口执行,看到如下结果:
|
||||
{{~/sync/notes/zim/python/python中的编码/1.png}}
|
||||
{{./1.png}}
|
||||
|
||||
此时一切正常,但,如果还是用上面的脚本去读取utf8.txt,文件是utf8编码,就会得到下面的结果:
|
||||
{{~/sync/notes/zim/python/python中的编码/2.png}}
|
||||
{{./2.png}}
|
||||
经典的“乱码”出现了,有朋友可能会说了,我在python脚本里指定编码应该就解决了,于是:
|
||||
|
||||
#!/usr/bin/env python
|
||||
@@ -40,7 +39,7 @@ if __name__=='__main__':
|
||||
do()
|
||||
|
||||
再次运行:
|
||||
{{~/sync/notes/zim/python/python中的编码/3.png}}
|
||||
{{./3.png}}
|
||||
OMG,还是乱码。。。。
|
||||
|
||||
能不能正常输出中文不取决于#coding=utf-8,也不取决于目标文件的编码,而是取决于你的__终端输出设备__,这里就是CMD窗口,CMD窗口是不支持UTF-8的,它只支持__GBK__,所以,我们要转码。
|
||||
@@ -64,7 +63,7 @@ if __name__=='__main__':
|
||||
do()
|
||||
|
||||
结果:
|
||||
{{~/sync/notes/zim/python/python中的编码/4.png}}
|
||||
{{./4.png}}
|
||||
正常输出。
|
||||
|
||||
做一个小结:
|
||||
|
||||
@@ -122,13 +122,3 @@ Base64编码可用于在HTTP环境下传递较长的__标识信息__。例如,
|
||||
MIME::Base64 Perl module
|
||||
Firefox extension
|
||||
emacs函数
|
||||
|
||||
===== 参见 =====
|
||||
|
||||
Radix-64
|
||||
ASCII85
|
||||
Quoted-printable
|
||||
uuencode
|
||||
yEnc
|
||||
8BITMIME
|
||||
URL
|
||||
|
||||
@@ -9,10 +9,13 @@ Created Friday 14 October 2011
|
||||
Python 2.7.2 (default, Jun 29 2011, 11:17:09)
|
||||
[GCC 4.6.1] on linux2
|
||||
Type "help", "copyright", "credits" or "license" for more information.
|
||||
>>> '张俊' #python2 会自动将字符串转换为合适的编码字节字符串
|
||||
|
||||
>>> '张俊' #python2 会自动将字符串转换为合适编码的字节字符串
|
||||
'\xe5\xbc\xa0\xe4\xbf\x8a' #自动转换为utf-8编码的字节字符串
|
||||
|
||||
>>> u'张俊' #显式指定字符串类型为unicode类型, 此类型字符串没有编码,保存的是字符在unicode**字符集中的代码点(序号)**
|
||||
u'\u5f20\u4fca'
|
||||
|
||||
>>> '张俊'.encode('utf-8') #python2 已经自动将其转化为utf-8类型编码,因此再次编码(python2会将该字符串当作用ascii或unicode编码过)会出现错误。
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
|
||||
7
Zim/Programme/python/python笔记.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:28+08:00
|
||||
|
||||
====== python笔记 ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
40
Zim/Programme/python/python笔记/dict.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:05+08:00
|
||||
|
||||
====== dict ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
Help on class dict in module __builtin__:
|
||||
|
||||
class dict(object)
|
||||
| dict() -> new empty dictionary
|
||||
| dict(mapping) -> new dictionary initialized from **a mapping object**'s #mapping为__一个__pairs对象
|
||||
| (key, value) pairs
|
||||
| dict(iterable) -> new dictionary initialized as if via:
|
||||
| d = {}
|
||||
| for **k, v** in iterable: #迭代器对象每次返回的元素必须是一个容器类型,__容器中元素的个数为2__.**如[a,b], "ab",(a,b)**
|
||||
| d[k] = v
|
||||
| dict(__**kwargs)__ -> new dictionary initialized with the name=value pairs
|
||||
| in the keyword argument list. For example: dict(one=1, two=2)
|
||||
|
|
||||
| Methods defined here:
|
||||
|
||||
|
||||
>>> dict(__[('sape', 4139), ('guido', 4127), ('jack', 4098)]__)
|
||||
{'sape': 4139, 'jack': 4098, 'guido': 4127}
|
||||
|
||||
>>> dict([(x, x**2) for x in (2, 4, 6)]) # use a list comprehension
|
||||
{2: 4, 4: 16, 6: 36}
|
||||
|
||||
>>> dict(sape=4139, guido=4127, jack=4098)
|
||||
{'sape': 4139, 'jack': 4098, 'guido': 4127}
|
||||
tel = {'jack': 4098, 'sape': 4139}
|
||||
|
||||
>>> dc=dict(["df","12"]);dc #["df","12"]为一科迭代对象,每次返回的元素为两个字符的str,所以可以被unpack给key,value
|
||||
{'1': '2', 'd': 'f'}
|
||||
>>> dc=dict(["df",__"123"__]);dc
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: dictionary update sequence element __#1 has length 3; 2 is required__
|
||||
>>>
|
||||
20
Zim/Programme/python/python笔记/float.txt
Normal file
@@ -0,0 +1,20 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:56+08:00
|
||||
|
||||
====== float ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> float("0xff")
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: invalid literal for float(): 0xff
|
||||
>>>
|
||||
|
||||
>>> __float.fromhex("0xfff")__
|
||||
4095.0
|
||||
>>>
|
||||
|
||||
>>> float("0.111")
|
||||
0.111
|
||||
>>>
|
||||
37
Zim/Programme/python/python笔记/int_long.txt
Normal file
@@ -0,0 +1,37 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:52+08:00
|
||||
|
||||
====== int long ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> 1&2 #按__位与__
|
||||
0
|
||||
|
||||
>>> 0xff&0xf1 #按位与
|
||||
241
|
||||
>>> 0xff&0xf0
|
||||
240
|
||||
>>> __hex__(0xff&0xf0) #返回的__字符串__
|
||||
'0xf0'
|
||||
__与hex()类似, bin(), oct()等返回的都是int或long型的字符串代表__
|
||||
|
||||
>>> 1&&2 __#python没有&&, ||, !逻辑运算符,但是有and, or, not,而且这三个逻辑运算符返回的是最后一个元素的内容__
|
||||
File "<stdin>", line 1
|
||||
1&&2
|
||||
^
|
||||
SyntaxError: invalid syntax
|
||||
|
||||
>>> 1 and 2 __#返回的是最后一个元素的内容而不是True或False,这里为2__
|
||||
2
|
||||
|
||||
>>> 'fff' & 'dfad' __#str类型没有定义__and__方法,所以没有位运算__
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: unsupported operand type(s) for &: 'str' and 'str'
|
||||
>>> help(str)
|
||||
|
||||
>>> 'fff' and 'dfad'
|
||||
'dfad'
|
||||
>>>
|
||||
|
||||
25
Zim/Programme/python/python笔记/list.txt
Normal file
@@ -0,0 +1,25 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:01+08:00
|
||||
|
||||
====== list ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> l=range(1,10)
|
||||
>>> l
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
>>> __l[1:8:-1]__
|
||||
[]
|
||||
>>> l[8:1:-1]
|
||||
[9, 8, 7, 6, 5, 4, 3]
|
||||
>>> __l[::-1]__
|
||||
[9, 8, 7, 6, 5, 4, 3, 2, 1]
|
||||
>>> l[-1::-1]
|
||||
[9, 8, 7, 6, 5, 4, 3, 2, 1]
|
||||
>>> __l[-1:-9:-1]__
|
||||
[9, 8, 7, 6, 5, 4, 3, 2]
|
||||
>>> l[:]
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
>>> l[::]
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
|
||||
18
Zim/Programme/python/python笔记/set.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:08+08:00
|
||||
|
||||
====== set ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> set1=set(1,2,3)
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: set expected __at most 1 arguments,__ got 3
|
||||
|
||||
>>> set1=set((1,2,3))
|
||||
>>> set1
|
||||
set([1, 2, 3])
|
||||
>>>
|
||||
|
||||
|
||||
40
Zim/Programme/python/python笔记/str.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:20:41+08:00
|
||||
|
||||
====== str ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
| join(...)
|
||||
| S.join(iterable) -> string
|
||||
|
|
||||
| Return a string which is the concatenation of __the strings in the__
|
||||
__ | iterable__. The separator between elements is S.
|
||||
|
||||
iterable迭代器对象每次返回的__必须是字符串对象__。
|
||||
|
||||
>>> ":".join("abcd")
|
||||
'a:b:c:d'
|
||||
|
||||
>>> ":".join(['a','b','c','d'])
|
||||
'a:b:c:d'
|
||||
|
||||
>>> ":".join(['a',__123__,'c'])
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: sequence item 1: __expected string__, int found
|
||||
|
||||
>>> ":".join(['a',['ab'],'c'])
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
TypeError: sequence item 1: expected string, list found
|
||||
>>>
|
||||
|
||||
| rsplit(...)
|
||||
| S.rsplit([sep [,maxsplit]]) -> list of strings
|
||||
|
|
||||
| Return a list of the words in the string S, using sep as the
|
||||
| delimiter string, starting at the end of the string and working
|
||||
| to the front. If maxsplit is given, at most maxsplit splits are
|
||||
| done. If sep is not specified or is __None__, any whitespace string
|
||||
| is a separator.
|
||||
38
Zim/Programme/python/python笔记/unpack.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:47:32+08:00
|
||||
|
||||
====== unpack ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
>>> for k,v in ["fdf",23,"dfdf",33]:
|
||||
... print k,v
|
||||
...
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: __too many__ values to unpack
|
||||
|
||||
顺序容器类型如str, list, tuple__每次迭代时只能返回其中的一个元素__。
|
||||
所以第一次返回循环返回**"fdf"**,但是它有三个元素最多只能赋值给两个
|
||||
变量。
|
||||
|
||||
>>> for k,v in "dfdf":
|
||||
... print k,v
|
||||
...
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: __need more than 1 value__ to unpack
|
||||
|
||||
字符串迭代时,每次返回其中的一个字符。所以最多只能unpack给一个变量。
|
||||
|
||||
>>> k,v="dfdf"
|
||||
Traceback (most recent call last):
|
||||
File "<stdin>", line 1, in <module>
|
||||
ValueError: __too many values to unpack__
|
||||
|
||||
unpack一个顺序容器类型时,左边变量的数目必须要与容器中元素的个数相同。
|
||||
|
||||
>>> k,v="df"
|
||||
>>> print k,v
|
||||
d f
|
||||
>>>
|
||||
7
Zim/Programme/python/python笔记/内置函数.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T13:21:30+08:00
|
||||
|
||||
====== 内置函数 ======
|
||||
Created Thursday 04 October 2012
|
||||
|
||||
121
Zim/Programme/python/编写_Unix_管道风格的_Python_代码.txt
Normal file
@@ -0,0 +1,121 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-04T22:01:26+08:00
|
||||
|
||||
====== 编写 Unix 管道风格的 Python 代码 ======
|
||||
Created Thursday 04 October 2012
|
||||
http://www.oschina.net/question/54100_11910
|
||||
|
||||
先推荐一份幻灯片,David Beazley ("Python essiential reference", PLY 的作者) 在 PyCon’2008 上报告的幻灯片,强烈推荐!!这篇文章的很多内容都来自或者受这份幻灯片的启发而来。
|
||||
|
||||
在上一篇文章里介绍了 Unix 管道的好处,那可不可以在写程序时也使用这样的思想呢?当然可以。看过 SICP 就知道,其实函数式编程中的 __map, filter__ 都可以看作是管道思想的应用。但其实管道的思想不仅可以在函数式语言中使用,只要语言支持定义函数,有能够存放一组数据的数据结构,就可以使用管道的思 想。
|
||||
|
||||
一个日志处理任务
|
||||
|
||||
这里直接以前面推荐的幻灯片里的例子来说明,应用场景如下:
|
||||
|
||||
某个目录及子目录下有一些 web 服务器的日志文件,日志文件名以 access-log 开头
|
||||
日志格式如下
|
||||
81.107.39.38 - ... "GET /ply/ply.html HTTP/1.1" 200 97238
|
||||
81.107.39.38 - ... "GET /ply HTTP/1.1" 304 -
|
||||
其中最后一列数字为发送的字节数,若为 ‘-’ 则表示没有发送数据
|
||||
|
||||
目标是算出总共发送了多少字节的数据,实际上也就是要把日志记录的没一行的最后一列数值加起来
|
||||
我不直接展示如何用 Unix 管道的风格来处理这个问题,而是先给出一些“不那么好”的代码,指出它们的问题,最后再展示管道风格的代码,并介绍如何使用 generator 来避免效率上的问题。想直接看管道风格的,点这里。
|
||||
|
||||
问题并不复杂,几个 for 循环就能搞定:
|
||||
|
||||
sum = 0
|
||||
for path, dirlist, filelist in __os.walk(top)__:
|
||||
for name in __fnmatch.filter__(filelist, "access-log*"):
|
||||
# 对子目录中的每个日志文件进行处理
|
||||
with open(name) as f:
|
||||
for line in f:
|
||||
if line[-1] == '-':
|
||||
continue
|
||||
else:
|
||||
sum += int(line__.rsplit(None, 1)__[1])
|
||||
|
||||
利用 os.walk 这个问题解决起来很方便,由此也可以看出 python 的 for 语句做遍历是多么的方便,不需要额外控制循环次数的变量,省去了设置初始值、更新、判断循环结束条件等工作,相比 C/C++/Java 这样的语言真是太方便了。看起来一切都很美好。
|
||||
|
||||
然而,设想以后有了新的统计任务,比如:
|
||||
|
||||
1. 统计某个特定页面的访问次数
|
||||
2. 处理另外的一些日志文件,日志文件名字以 error-log 开头
|
||||
|
||||
完成这些任务直接拿上面的代码过来改改就可以了,文件名的 pattern 改一下,处理每个文件的代码改一下。其实每次任务的处理中,找到特定名字为特定 pattern 的文件的代码是一样的,直接修改之前的代码其实就引入了重复。
|
||||
|
||||
如果重复的代码量很大,我们很自然的会注意到。然而 python 的 for 循环实在太方便了,像这里找文件的代码一共就两行,哪怕重写一遍也不会觉得太麻烦。for 循环的方便使得我们会忽略这样简单代码的重复。然而,再怎么方便好用,for 循环无法重用,只有把它放到函数中才能进行重用。
|
||||
|
||||
(先考虑下是你会如何避免这里的代码的重复。下面马上出现的代码并不好,是“误导性”的代码,我会在之后再给出“更好”的代码。)
|
||||
|
||||
因此,我们__把上面代码中不变的部分提取成一个通用的函数,可变的部分以参数的形式传入__,得到下面的代码。
|
||||
|
||||
def generic_process(topdir, filepat, processfunc):
|
||||
for path, dirlist, filelist in os.walk(top):
|
||||
for name in fnmatch.filter(filelist, filepat):
|
||||
with open(name) f:
|
||||
processfunc(f)
|
||||
|
||||
sum = 0
|
||||
# 很遗憾,python 对 closure 中的变量不能进行赋值操作,
|
||||
# 因此这里只能使用全局变量
|
||||
def add_count(f):
|
||||
global sum
|
||||
for line in f:
|
||||
if line[-1] == '-':
|
||||
continue
|
||||
else:
|
||||
sum += int(line.rsplit(None, 1)[1])
|
||||
|
||||
generic_process('logdir', 'access-log*', add_count)
|
||||
|
||||
看起来不变和可变的部分分开了,然而 generic_process 的设计并不好。它除了寻找文件以外还调用了日志文件处理函数,因此在其他任务中很可能就无法使用。另外 add_count 的参数必须是 file like object,因此测试时不能简单的直接使用字符串。
|
||||
|
||||
===== 管道风格的程序 =====
|
||||
下面考虑用 Unix 的工具和管道我们会如何完成这个任务:
|
||||
|
||||
find logdir -name "access-log*" | \
|
||||
xargs cat | \
|
||||
grep '[^-]$' | \
|
||||
awk '{ total += $NF } END { print total }'
|
||||
|
||||
find 根据文件名 pattern 找到文件,cat 把所有文件内容合并输出到 stdout,grep 从 stdin 读入,过滤掉行末为 ‘-’ 的行,awk 提取每行最后一列,将数值相加,最后打印出结果。(省掉 cat 是可以的,但这样一来 grep 就需要直接读文件而不是只从标准输入读。)
|
||||
|
||||
我们可以在 python 代码中模拟这些工具,__Unix 的工具通过文本来传递结果,在 python 中可以使用 list__。
|
||||
|
||||
def find(topdir, filepat, processfunc):
|
||||
files = []
|
||||
for path, dirlist, filelist in os.walk(top):
|
||||
for name in fnmatch.filter(filelist, filepat):
|
||||
files.append(name)
|
||||
return files
|
||||
|
||||
def cat(files):
|
||||
lines = []
|
||||
for file in files:
|
||||
with open(file) as f:
|
||||
for line in f:
|
||||
lines.append(line)
|
||||
return lines
|
||||
|
||||
def grep(pattern, lines):
|
||||
result = []
|
||||
import re
|
||||
pat = re.compile(pattern)
|
||||
for line in lines:
|
||||
if pat.search(line):
|
||||
result.append(line)
|
||||
resurn result
|
||||
|
||||
lines = grep('[^-]$', cat(find('logdir', 'access-log*')))
|
||||
col = (line.rsplit(None, 1)[1] for line in lines)
|
||||
print sum(int(c) for c in col)
|
||||
|
||||
有了 find, cat, grep 这三个函数,只需要连续调用就可以像 Unix 的管道一样将这些函数组合起来。数据在管道中的变化如下图(简洁起见,过滤器直接标在箭头上 ):
|
||||
|
||||
{{./1.gif}}
|
||||
|
||||
看起来现在的代码行数比最初直接用 for 循环的代码要多,但现在的代码就像 Unix 的那些小工具一样,每一个都更加可能被用到。我们可以把更多常用的 Unix 工具用 Python 来模拟,从而在 Python 代码中以 Unix 管道的风格来编写代码。
|
||||
|
||||
不过上面的代码性能很差,多个临时的 list 被创建。解决的办法是用 generator,因为篇幅比较长,具体做法放到下一篇文章中。
|
||||
BIN
Zim/Programme/python/编写_Unix_管道风格的_Python_代码/1.gif
Normal file
|
After Width: | Height: | Size: 6.7 KiB |
@@ -177,7 +177,7 @@ This manual is meant as a brief introduction to features found in Bash. The Bash
|
||||
|
||||
===== 1 Introduction =====
|
||||
|
||||
* What is Bash?: A short description of Bash.
|
||||
* What is Bash? :A short description of Bash.
|
||||
* What is a shell?: A brief introduction to shells.
|
||||
|
||||
===== 1.1 What is Bash[bæʃ]? =====
|
||||
@@ -275,9 +275,9 @@ This chapter briefly summarizes the shell's `__building blocks__': commands, con
|
||||
|
||||
When the shell reads input, it proceeds through a sequence of operations. If the input indicates the beginning of a comment, the shell ignores the comment symbol (‘#’), and the rest of that line.
|
||||
|
||||
Otherwise, roughly speaking, the shell reads its input and divides the input into __words and operators__, employing the __quoting rules __to select which meanings to assign various words and characters.
|
||||
Otherwise, roughly speaking, the shell reads its input and divides the input into __words(words是一组连续的字符,用metacharacter分割。) and operators(具有特定功能的字符序列,由control operator和重定向符组成)__, employing the __quoting rules __to select which meanings to assign various words and characters.
|
||||
|
||||
The shell then parses these tokens into commands and other constructs, removes the **special meaning** of certain words or characters, expands others, redirects input and output as needed, executes the specified command, waits for the command's exit status, and makes that exit status available for further inspection or processing.
|
||||
The shell then parses these tokens(由word和operator组成。) into commands and other constructs, removes the **special meaning** of certain words or characters, expands others, redirects input and output as needed, executes the specified command, waits for the command's exit status, and makes that exit status available for further inspection or processing.
|
||||
|
||||
==== 3.1.1 Shell Operation ====
|
||||
|
||||
@@ -299,7 +299,7 @@ The following is a brief description of the shell's operation when it reads and
|
||||
* ANSI-C Quoting: How to expand ANSI-C sequences in quoted strings.
|
||||
* Locale Translation: How to translate strings into different languages.
|
||||
|
||||
Quoting is used to __remove the special meaning of certain characters or words__ to the shell. Quoting can be used to disable special treatment for special characters, to prevent reserved words from being recognized as such, and to prevent parameter expansion.
|
||||
Quoting is used to __remove the special meaning of certain characters or words__ to the shell. Quoting can be used to disable special treatment for special characters, to prevent reserved words from being recognized as such, and to prevent parameter expansion.
|
||||
|
||||
Each of the shell metacharacters (see Definitions) has special meaning to the shell and must be quoted if it is to__ represent itself__. When the command history expansion facilities are being used (see History Interaction), the history expansion character, usually__ ‘!’__, must be quoted to prevent history expansion. See Bash History Facilities, for more details concerning history expansion.
|
||||
|
||||
@@ -312,7 +312,8 @@ A non-quoted backslash ‘\’ is the Bash escape character. It preserves the __
|
||||
|
||||
=== 3.1.2.2 Single Quotes(单引号中的任何字符都无特殊含义,因此单引号中的\'是无效的) ===
|
||||
|
||||
Enclosing characters in single quotes (‘'’) preserves the__ literal value of each character__ within the quotes. A single quote may **not occur **between single quotes, even when preceded by a backslash.单引号中的\无特殊含义。
|
||||
Enclosing characters in single quotes (‘'’) preserves the__ literal value of each character__ within the quotes. A single quote may **not occur **between single quotes, even when preceded by a backslash.
|
||||
单引号中的\无特殊含义。
|
||||
|
||||
=== 3.1.2.3 Double Quotes(除$ ` \和!外,其它任何字符都无特殊含义。其中\只有当后接$ ` \ 或换行时才有特殊含义,否则为正常字符) ===
|
||||
|
||||
@@ -320,9 +321,7 @@ Enclosing characters in double quotes (‘"’) preserves the literal value of a
|
||||
|
||||
The backslash retains its special meaning__ only when followed by one of the following characters: ‘$’, ‘`’, ‘"’, ‘\’, or newline__. Within double quotes, backslashes that are followed by one of these characters are **removed**. Backslashes preceding characters without a special meaning are** left unmodified**. A double quote may be quoted within double quotes by preceding it with a backslash. If enabled, history expansion will be performed unless an ‘!’ appearing in double quotes is escaped using a backslash. The backslash preceding the ‘!’ is not removed.
|
||||
|
||||
单独列出的
|
||||
|
||||
===== 特殊规则 =====
|
||||
=== 特殊规则 ===
|
||||
:__The special parameters ‘*’ and ‘@’ have special meaning when in double quotes__ (see Shell Parameter Expansion).
|
||||
|
||||
|
||||
@@ -369,7 +368,7 @@ __The expanded result is single-quoted__, as if the dollar sign had not been pre
|
||||
[geekard@geekard ~]$ set |grep IFS
|
||||
__IFS=$' \t\n'__
|
||||
[geekard@geekard ~]$
|
||||
原来是因为值中含有ANSI-C的转义字符,所以使用了$'...'形式。
|
||||
原来是因为值中含有ANSI-C的转义字符,所以使用了 **$'...' **形式。
|
||||
[geekard@geekard ~]$ echo ' \t\n' #正常情况下单引号中的内容**无任何特殊含义**,所以,\t\n并__不是__转义字符。
|
||||
\t\n
|
||||
[geekard@geekard ~]$ echo $' \t\n' #但是,如果使用$' \t\n'形式,bash将其解释为__含有转义字符序列的特殊字符串__
|
||||
@@ -385,7 +384,7 @@ cd
|
||||
|
||||
=== 3.1.2.5 Locale-Specific Translation ===
|
||||
|
||||
A double-quoted string preceded by a dollar sign (‘$’) will cause the string to be translated according to the c**urrent locale**. If the current locale is__ C or POSIX__, the dollar sign is ignored. If the string is translated and replaced, the replacement is **double-quoted**.
|
||||
A double-quoted string preceded by a dollar sign (‘$’) will cause the string to be translated according to the **current locale**. If the current locale is__ C or POSIX__, the dollar sign is ignored. If the string is translated and replaced, the replacement is **double-quoted**.
|
||||
|
||||
Some systems use the message catalog selected by the LC_MESSAGES shell variable. Others create the name of the message catalog from the value of the TEXTDOMAIN shell variable, possibly adding a suffix of ‘.mo’. If you use the TEXTDOMAIN variable, you may need to set the TEXTDOMAINDIR variable to the location of the message catalog files. Still others use both variables in this fashion: TEXTDOMAINDIR/LC_MESSAGES/LC_MESSAGES/TEXTDOMAIN.mo.
|
||||
|
||||
@@ -407,32 +406,31 @@ More complex shell commands are composed of simple commands __arranged together
|
||||
* Compound Commands: Shell commands for control flow.
|
||||
* Coprocesses: Two-way communication between commands.
|
||||
最后四个都是将简单命令组合为复杂命令的方法,它们__对外是一个整体__,可以被重定向。
|
||||
3.2.1 Simple Commands
|
||||
|
||||
==== 3.2.1 Simple Commands ====
|
||||
A simple command is the kind of command encountered most often. It's just a** sequence of words separated by **__blanks__**, terminated by one of the shell's **__control operators__ (see Definitions). The first word generally specifies a command to be executed, with the rest of the words being that command's arguments.
|
||||
|
||||
The return status (see Exit Status) of a simple command is its exit status as provided by the posix 1003.1 waitpid function, or __128+n__ if the command was terminated by **signal n**.
|
||||
|
||||
|
||||
==== 3.2.2 Pipelines ====
|
||||
|
||||
A pipeline is a sequence of simple commands separated by one of the control operators ‘|’ or ‘|&’.
|
||||
|
||||
The format for a pipeline is
|
||||
|
||||
[time [-p]] [!] command1 [ [| or |&] command2 ...]
|
||||
[time [-p]] [!] command1 [ [| or __|& __] command2 ...]
|
||||
|
||||
The output(一般是标准输出) of each command in the pipeline is connected via a pipe to the input of the next command. That is, each command reads the previous command's output. This connection is performed before any redirections specified by the command. 管道在任何重定向操作之前完成。
|
||||
The output (一般是标准输出) of each command in the pipeline is connected via a pipe to the input of the next command. That is, each command reads the previous command's output. This connection is performed before any redirections specified by the command. 管道在任何重定向操作之前完成。
|
||||
|
||||
If ‘|&’ is used, the __standard error__ of command1 is connected to command2's standard input through the pipe; it is shorthand for __2>&1 |__. This implicit redirection of the standard error is performed after any redirections specified by the command.
|
||||
|
||||
The reserved word time causes timing statistics to be printed for the pipeline once it finishes. The statistics currently consist of __elapsed (wall-clock) time and user and system time__ consumed by the command's execution. The -p option changes the output format to that specified by posix. The TIMEFORMAT variable may be set to a format string that specifies how the timing information should be displayed. See Bash Variables, for a description of the available formats. The use of time as a reserved word permits the timing of shell builtins, shell functions, and pipelines. An external time command cannot time these easily.
|
||||
The reserved word __time__ causes timing statistics to be printed for the pipeline once it finishes. The statistics currently consist of __elapsed (wall-clock) time and user and system time__ consumed by the command's execution. The -p option changes the output format to that specified by posix. The TIMEFORMAT variable may be set to a format string that specifies how the timing information should be displayed. See Bash Variables, for a description of the available formats. The use of time as a reserved word permits the timing of shell builtins, shell functions, and pipelines. An external time command cannot time these easily.
|
||||
|
||||
If the pipeline is not executed asynchronously (see Lists), the shell waits for all commands in the pipeline to complete.
|
||||
|
||||
Each command in a pipeline is executed __in its own subshell __(see Command Execution Environment). The exit status of a pipeline is the exit status of the__ last command__ in the pipeline, unless the** pipefail option** is enabled (see The Set Builtin). If pipefail is enabled, the pipeline's return status is the value of the last (rightmost) command to exit with a non-zero status, or zero if all commands exit successfully. If the reserved word ‘!’ precedes the pipeline, the exit status is the logical negation of the exit status as described above. The shell waits for all commands in the pipeline to terminate before returning a value.
|
||||
|
||||
管道线中各命令都在**各自的subshell**中执行,只有当最后一个命令执行完毕时,整个命令才结束,同时整个管道线命令的退出值为**最后一个命令**的退出值(而非中间某个命令的退出值)。如果打开了pipefail选项,则管道线中任何一个命令执行失败(退出吗非0),则整个管道线退出。
|
||||
管道线中各命令都在__各自__**的subshell**中执行,只有当最后一个命令执行完毕时(不管中间的命令是否执行成功),整个命令才结束,同时整个管道线命令的退出值为**最后一个命令**的退出值(而非中间某个命令的退出值)。如果打开了pipefail选项,则管道线中任何一个命令执行失败(退出码非0),则整个管道线退出。
|
||||
|
||||
|
||||
==== 3.2.3 Lists of Commands ====
|
||||
@@ -506,7 +504,7 @@ Note that wherever a ‘;’ appears in the description of a command's syntax, i
|
||||
An alternate form of the for command is also supported: 借鉴了C的语法规则
|
||||
|
||||
for __(( expr1 ; expr2 ; expr3 )) __; do commands ; done
|
||||
|
||||
#这里的expr值得是算术表达式。
|
||||
First, the arithmetic expression expr1 is evaluated according to the rules described below (see Shell Arithmetic). The arithmetic expression expr2 is then evaluated repeatedly __until it evaluates to zero__. Each time expr2 evaluates to a non-zero value, commands are executed and the arithmetic expression expr3 is evaluated. If any expression is omitted, it behaves as if it evaluates to 1. The return value is the exit status of the last command in list that is executed, or false if any of the expressions is invalid.
|
||||
|
||||
The** break** and **continue** builtins (see Bourne Shell Builtins) may be used to control loop execution.
|
||||
@@ -532,7 +530,7 @@ The** break** and **continue** builtins (see Bourne Shell Builtins) may be used
|
||||
case will selectively execute the command-list corresponding to the **first** pattern that matches word. If the shell option nocasematch (see the description of shopt in The Shopt Builtin) is enabled, the match is performed without regard to the case of alphabetic characters. The __‘|’__ is used to separate multiple patterns, and the ‘)’ operator** terminates a pattern list**. A list of patterns and an associated command-list is known as a__ clause__.
|
||||
|
||||
Each clause must be terminated with **‘;;’, ‘;&’, or ‘;;&’.** The word undergoes tilde expansion, parameter expansion, command substitution, arithmetic expansion, and quote removal **before** matching is attempted. Each pattern undergoes tilde expansion, parameter expansion, command substitution, and arithmetic expansion.
|
||||
pattern使用的是bash的匹配模式,多个pattern只能使用|相连,表示**或关系**__。bash会对模式和command-list中的内容进行“~”扩展、参数扩展、命令替换和算术扩展__。
|
||||
pattern使用的是__bash的匹配模式__,多个pattern只能使用|相连,表示**或关系**__。bash会对模式和command-list中的内容进行“~”扩展、参数扩展、命令替换和算术扩展__。
|
||||
|
||||
There may be an arbitrary number of case clauses, each terminated by a ‘;;’, ‘;&’, or ‘;;&’. The first pattern that matches determines the command-list that is executed.
|
||||
|
||||
@@ -551,7 +549,6 @@ pattern使用的是bash的匹配模式,多个pattern只能使用|相连,表
|
||||
If the ‘;;’ operator is used, no subsequent matches are attempted after the first pattern match. Using ‘__;&__’ in place of ‘;;’ causes execution to continue with the command-list associated with the **next clause**, if any. Using ‘;;&’ in place of ‘;;’ causes the shell to __test__ the patterns in the next clause, if any, and execute any associated command-list on a successful match.
|
||||
使用;;终止符号时,bash只会执行其前面的command-list然后退出case结构;使用;&结构时,bash还会__接着执行下一个__pattern对应的commandlist;使用;;&时,bash会__先判断__下一个pattern是否匹配,若是就执行对应的commandlist。
|
||||
|
||||
|
||||
The return status is zero if no pattern is matched. Otherwise, the return status is the exit status of the command-list executed.
|
||||
* select
|
||||
The select construct allows the __easy generation of menus__. It has almost the same syntax as the for command:
|
||||
@@ -573,9 +570,9 @@ The **PS3 **prompt is then displayed and a line is read from the standard input.
|
||||
done
|
||||
|
||||
|
||||
* ((...))
|
||||
* ((...)) #会被shell当作命令来执行,返回结果为true或false。
|
||||
|
||||
(( expression ))
|
||||
(( expression )) #expression必须为算术表达式,不能是命令,不会被扩展。
|
||||
|
||||
The__ arithmetic expression__ is evaluated according to the rules described below (see Shell Arithmetic). If the value of the expression is non-zero, the return status is 0; otherwise the return status is 1. This is exactly equivalent to
|
||||
|
||||
@@ -587,7 +584,7 @@ The **PS3 **prompt is then displayed and a line is read from the standard input.
|
||||
|
||||
Return a status of 0 or 1 depending on the evaluation of the __conditional expression__ **expression**. Expressions are composed of the primaries described below in Bash Conditional Expressions. __Word splitting and filename expansion are not performed __on the words between the ‘[ [’ and ‘] ]’; tilde expansion, parameter and variable expansion, arithmetic expansion, command substitution, process substitution, and **quote removal** are performed. Conditional operators such as ‘-f’ must be unquoted to be recognized as primaries.
|
||||
|
||||
When used with ‘[[’, The __‘<’__** and **__‘>’__ operators __sort lexicographically__ using the current locale.
|
||||
When used with ‘[[’, The __‘<’__** and **__‘>’__ operators __sort lexicographically(对字符串而言,不能用于数字)__ using the current locale.
|
||||
|
||||
When the__ ‘==’ and ‘!=’__ operators are used, the string to the right of the operator is __considered a pattern__ and matched according to the rules described below in Pattern Matching. If the shell option nocasematch (see the description of shopt in The Shopt Builtin) is enabled, the match is performed without regard to the case of alphabetic characters. The return value is 0 if the string matches (‘==’) or does not match (‘!=’)the pattern, and 1 otherwise. Any part of the pattern may be quoted to force it to be matched as a string.
|
||||
|
||||
@@ -606,6 +603,7 @@ The **PS3 **prompt is then displayed and a line is read from the standard input.
|
||||
|
||||
The && and || operators do not evaluate expression2 if the value of expression1 is sufficient to determine the return value of the entire conditional expression.
|
||||
|
||||
对于[...]或test命令而言,其中不能有&&或||操作符,它们必须要放在[..]或test的外面;但是[[可以在内部使用它们。
|
||||
|
||||
=== 3.2.4.3 Grouping Commands ===
|
||||
|
||||
@@ -613,9 +611,9 @@ Bash provides two ways to group a list of commands to be __executed as a unit__.
|
||||
|
||||
()
|
||||
|
||||
( list ) #在一个__子shell中__执行list中的命令。括号是运算符,因此它与list之间可以没有空格。
|
||||
( list ) #在同一个__子shell中__执行list中的命令。括号是运算符,因此它与list之间可以没有空格。
|
||||
|
||||
Placing a list of commands between parentheses causes __a subshell environment __to be created (see Command Execution Environment), and each of the commands in list to be executed in **that subshell**. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
|
||||
Placing a list of commands between parentheses causes __a subshell environment __to be created (see Command Execution Environment), and each of the commands in list to be executed in **that subshell(正常情况下,list中的每个命令都是在不同的subshell中执行的。)**. Since the list is executed in a subshell, variable assignments do not remain in effect after the subshell completes.
|
||||
{}
|
||||
|
||||
{ list__; __} #在__当前shell__中执行list中的命令,注意__最后的分号不可少。大括号是关键字,因此,它与list之间必须要有空格。__
|
||||
@@ -632,7 +630,7 @@ A coprocess is a shell command preceded by the __coproc reserved word__. A copro
|
||||
|
||||
The format for a coprocess is:
|
||||
|
||||
coproc [NAME] **com{** #注意是**单条命令**,如果使用符合命令则必须用{}引住。
|
||||
coproc [NAME] **com{ } ** #注意是**单条命令**,如果使用复合命令则必须用{}引住。
|
||||
|
||||
This creates a coprocess named NAME. If NAME is not supplied, the default name is __COPROC__. NAME must not be supplied if command is a simple command (see Simple Commands); otherwise, it is interpreted as the first word of the simple command.
|
||||
|
||||
@@ -1335,7 +1333,8 @@ The exit status of an executed command is the value returned by the **waitpid**
|
||||
|
||||
For the shell's purposes, a command which exits with __a zero exit status has succeeded__. A non-zero exit status indicates failure. This seemingly counter-intuitive scheme is used so there is one well-defined way to indicate success and a variety of ways to indicate various failure modes. When a command __terminates on a fatal signal whose number is N, Bash uses the value 128+N as the exit status__.
|
||||
|
||||
* If a command is not found, the child process created to execute it returns a status of __127__. If a command is found but is not executable, the return status is __126__.
|
||||
* If a command is not found, the child process created to execute it returns a status of
|
||||
* If a command is found but is not executable, the return status is
|
||||
* If a command fails because of an error **during expansion or redirection**, the exit status is greater than zero.
|
||||
* The exit status is used by the Bash __conditional commands__ (see Conditional Constructs) and some of the __list constructs__ (see Lists).
|
||||
|
||||
|
||||
@@ -34,8 +34,6 @@ ls: cannot access thisfiledoesntexist: No such file or directory #coproc的出
|
||||
#let the output of the coprocess go to stdout
|
||||
$ __{ coproc mycoproc { awk '{print "foo" $0;fflush()}' ;} >&3 ;} 3>&1__
|
||||
|
||||
|
||||
|
||||
{....} 分组命令代表在**当前shell中**执行其中的命令,所以3>&1,表示将{...}中使用的3描述符与__当前shell__的1描述符相连接。
|
||||
而 {...}中的>&3是在coproc与当前shell的pipe建立之后执行的,其标准输出的描述符其环境中的3相连后,实际上是与当前shell的标准输出相连。
|
||||
|
||||
|
||||
@@ -113,7 +113,6 @@ What happens if someone kills your script while critical-section is running? The
|
||||
|
||||
if [ ! -e $lockfile ]; then
|
||||
**trap "rm -f $lockfile; exit" INT TERM EXIT #在捕获信号前需安装信号及其执行的命令**
|
||||
~~ #上述命令有一个bug,存在重复触发的死循环。~~
|
||||
touch $lockfile
|
||||
critical-section
|
||||
rm $lockfile
|
||||
|
||||
@@ -57,7 +57,7 @@ exit 0
|
||||
**********竞争处理
|
||||
if ( __set -o noclobber__; echo "$$" > "$lockfile") 2> /dev/null; #如果lockfile存在,则**含有重定向的命令**出错返回
|
||||
then
|
||||
trap 'rm -f "$lockfile"; exit $?' INT TERM EXIT
|
||||
trap 'rm -f "$lockfile"; __exit $?__' INT TERM EXIT
|
||||
critical-section
|
||||
rm -f "$lockfile"
|
||||
trap __-__ INT TERM EXIT
|
||||
@@ -69,7 +69,6 @@ fi
|
||||
|
||||
if [ ! -e $lockfile ]; then
|
||||
**trap "rm -f $lockfile; exit" INT TERM EXIT #在捕获信号前需安装信号及其执行的命令**
|
||||
#上述命令有一个bug,存在重复触发的死循环。
|
||||
touch $lockfile
|
||||
critical-section
|
||||
rm $lockfile
|
||||
@@ -78,7 +77,7 @@ else
|
||||
echo "critical-section is already running"
|
||||
fi
|
||||
|
||||
****************Counted loops
|
||||
****************Counted loops---------------
|
||||
# Three expression for loop:
|
||||
for__ (( i = 0; i < 20; i++ ))__
|
||||
do
|
||||
|
||||
@@ -28,7 +28,7 @@ Created Friday 23 December 2011
|
||||
**bar,** i=
|
||||
[geekard@geekard ~]$ echo ${i:=bar}, i=$i #如果i变量未初始化或初始化为空,则参数扩展为__字符串bar__,i变量__设置为字符串bar__。
|
||||
**bar**, i=**bar**
|
||||
[geekard@geekard ~]$ unset i; echo ${i:?bar}, i=$i; date #如果变量i未初始化或初始化为空,则显示**i: =bar**同时__停止当前命令和后续命令的执行__。
|
||||
[geekard@geekard ~]$ unset i; echo ${i:?bar}, i=$i; date #如果变量i未初始化或初始化为空,则显示**i: bar**同时__停止当前命令和后续命令的执行__。
|
||||
bash:** i: bar**
|
||||
[geekard@geekard ~]$ echo ${i:+bar}, i=$i #如果变量i存在且不为空,则参数扩展为字符串__bar__的值,否则扩展为__空值__。
|
||||
, i=
|
||||
|
||||
@@ -97,14 +97,14 @@ done
|
||||
|
||||
|
||||
注意:
|
||||
1. <$pipe放在while的里或外面是有区别的,前者每次loop都重新打开$pipe文件,后者在整个loop中只打开文件一次。
|
||||
1. <$pipe放在while的里或外面是有区别的,前者每次loop都重新打开$pipe文件,后者在整个loop中只打开文件一次。
|
||||
2. __bash对pipe是行缓冲__。
|
||||
|
||||
-----------------------------
|
||||
You should __explicitly exit from the trap command__, otherwise the script will continue running past it. You should also catch a few other signals.
|
||||
|
||||
So: trap "rm -f $pipe" EXIT
|
||||
Becomes: trap "rm -f $pipe; exit" INT TERM EXIT
|
||||
Becomes: trap "rm -f $pipe; exit" INT TERM EXIT
|
||||
|
||||
Excellent article. Thank you!
|
||||
----------
|
||||
@@ -113,4 +113,3 @@ Not really necessary in this case
|
||||
__The EXIT trap gets executed when the shell exits regardless of why it exits so trapping INT and TERM aren't really necessary in this case.__
|
||||
|
||||
However, your point about "exit" is good: trapping a signal removes the default action that occurs when a signal occurs, so if the default action is to exit the program and you want that to happen in addition to executing your code, you need to include an exit statement in your code.
|
||||
|
||||
|
||||
@@ -67,12 +67,10 @@ dfs! s
|
||||
[geekard@geekard ~]$
|
||||
|
||||
|
||||
|
||||
|
||||
*********** 字符串中若含有空格、换行、TAB键,则必须要用引号包围:
|
||||
[geekard@geekard ~]$ echo "dfd\df"
|
||||
dfd\df
|
||||
[geekard@geekard ~]$ echo 'df #字符串中可以包含换行,但是C语言的字符串中不行(必须要使用转义字符)。
|
||||
[geekard@geekard ~]$ echo 'df #__Shell字符串中可以包含换行__,但是C语言的字符串中不行(必须要使用转义字符)。
|
||||
> df'
|
||||
df
|
||||
df
|
||||
|
||||
@@ -48,7 +48,8 @@ http://molinux.blog.51cto.com/2536040/469296
|
||||
4
|
||||
|
||||
[root@localhost ~]#
|
||||
[root@localhost ~]# A[0]=9 [root@localhost ~]# A[10]=1
|
||||
[root@localhost ~]# A[0]=9
|
||||
[root@localhost ~]# A[10]=1
|
||||
[root@localhost ~]# echo ${A[*]}
|
||||
9 1
|
||||
[root@localhost ~]# echo ${#A[*]}
|
||||
|
||||
@@ -8,7 +8,7 @@ Created Thursday 22 December 2011
|
||||
===== 条件测试命令: =====
|
||||
* test 和[ 是**等价**的条件测试命令,都可以单独执行但__无输出值,只有退出状态,0表示测试为真,非0表示测试为假。__
|
||||
|
||||
* 和 为**关键字**而非命令,是[ ]的**增强版本**,里面还可以用&&、||、<、>等逻辑和关系运算符(但__不能有算术运算符__),类似C语言的语法,~~因此返回值0表示测试为假,返回值1表示测试为真,这与test和[的返回状态值意义恰好相反,~~一般只用在条件测试中。
|
||||
* [ [ 和 ] ] 为**关键字**而非命令,是[ ]的**增强版本**,里面还可以用&&、||、<、>等逻辑和关系运算符(但__不能有算术运算符__),类似C语言的语法,一般只用在条件测试中。
|
||||
|
||||
* __((...)):比较特殊,先对__**算术、关系、逻辑表达式**__计算(求值),如果结果非空或非零,则返回状态为真(0),否则返回假(1);注意:没有输出值,只有退出状态值。__
|
||||
|
||||
@@ -16,7 +16,7 @@ Created Thursday 22 December 2011
|
||||
|
||||
|
||||
==== 注意: ====
|
||||
* 前三个是__专门用来做条件测试__的(因为它们作为命令执行时,没有输出值),而最后一个是shell用于__命令替换的语法__。
|
||||
* 前三个是__专门用来做条件测试__的(因为它们可以作为命令来执行,执行结果表明是真还是假),而最后一个是shell用于__算术替换的语法__。
|
||||
* 前两个__只能对__数值、字符串、文件做测试,而第三个__只能对__算术、关系、逻辑表达式做条件测试。
|
||||
|
||||
[geekard@geekard ~]$ **((0))**; echo $?
|
||||
@@ -38,7 +38,7 @@ Created Thursday 22 December 2011
|
||||
[geekard@geekard ~]$ __((i=2+3))__; echo i=$i, $? # 对表达式求值,将结果5赋给变量i,__ 5非0__故双括号返回真。
|
||||
i=5, 0
|
||||
[geekard@geekard ~]$
|
||||
[geekard@geekard ~]$ unset i; __$((i=2+3))__; echo i=$i,$? #__ shell__对$((...))中的表达式进行计算,把__执行计算结果当作命令来运行__。
|
||||
[geekard@geekard ~]$ unset i; __$((i=2+3))__; echo i=$i,$? #__ shell__对$((...))中的表达式进行计算,把__执行计算结果当作命令来运行__。也就是说__$(())不能作为命令来执行__。
|
||||
bash: 5: command not found
|
||||
i=5,127
|
||||
[geekard@geekard ~]$
|
||||
@@ -67,7 +67,7 @@ bash: 5: command not found
|
||||
5
|
||||
0
|
||||
0
|
||||
[geekard@geekard ~]$ ((2+3)); echo $?; __echo ((2+3))__** #shell不会自动对((..))求值替换**
|
||||
[geekard@geekard ~]$ ((2+3)); echo $?; __echo ((2+3))__** #shell不会自动对((..))求值替换,可以使用$(())**
|
||||
bash: syntax error near unexpected token `('
|
||||
[geekard@geekard ~]$
|
||||
[geekard@geekard ~]$ echo `((2+3))` #双括号没有输出,只有退出状态,故echo命令输出一空行
|
||||
@@ -90,14 +90,14 @@ bash: syntax error near unexpected token `('
|
||||
(2)字符串测试
|
||||
= 等于
|
||||
!= 不相等
|
||||
-z字符串 字符串长度伪则为真
|
||||
-n字符串 字符串长度不伪则为真
|
||||
-z字符串 字符串长度为0则为真
|
||||
-n字符串 字符串长度不为0则为真
|
||||
(3)文件测试
|
||||
-e文件名 文件存在为真
|
||||
-r文件名 文件存在且为只读时为真
|
||||
-w文件名 文件存在且可写时为真
|
||||
-x文件名 文件存在且可执行为真
|
||||
-s文件名 如果文件存在且长度不了0
|
||||
**-s**文件名 如果文件存在且长度不为0
|
||||
-d文件名 文件存在且为目录时为真
|
||||
-f文件名 文件存在且为普通文件为真
|
||||
-c文件名 文件存在且为字符类型特殊文件为真
|
||||
|
||||
@@ -334,7 +334,7 @@ s/e/E/g
|
||||
s/i/I/g
|
||||
s/o/O/g
|
||||
s/u/U/g' <old >new
|
||||
#bash的字符串引号里**可以包含换行**,但是单引号单不能对单换行单转以,双引号可以。
|
||||
#bash的字符串引号里**可以包含换行**,但是单引号单不能对单换行单转义,双引号可以。
|
||||
|
||||
===== A sed interpreter script =====
|
||||
|
||||
@@ -362,7 +362,6 @@ Sed comments are lines where the **first non-white** character is a "#." On many
|
||||
does work.
|
||||
|
||||
===== Passing arguments into a sed script =====
|
||||
|
||||
Passing a word into a shell script that calls sed is easy if you remembered my tutorial on the Unix quoting mechanism. To review, you use the single quotes to turn quoting on and off. A simple shell script that uses sed to emulate grep is:
|
||||
|
||||
#!/bin/sh
|
||||
@@ -887,7 +886,7 @@ This line\
|
||||
And this line
|
||||
'
|
||||
|
||||
===== Adding lines and the pattern space =====
|
||||
===== Adding lines and the pattern space =====
|
||||
|
||||
I have mentioned the pattern space before. Most commands operate **on** the pattern space, and subsequent commands may act on the __results__ of the last modification. The three previous commands, like the read file command, add the new lines to the output stream, __bypassing__ the pattern space.
|
||||
|
||||
|
||||
160
Zim/Utils/ulimit.txt
Normal file
@@ -0,0 +1,160 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-15T18:47:54+08:00
|
||||
|
||||
====== ulimit ======
|
||||
Created Monday 15 October 2012
|
||||
http://www.ibm.com/developerworks/cn/linux/l-cn-ulimit/
|
||||
|
||||
==== 概述 ====
|
||||
系统性能一直是一个受关注的话题,如何通过最简单的设置来实现最有效的性能调优,如何在有限资源的条件下保证程序的运作,ulimit 是我们在处理这些问题时,经常使用的一种简单手段。ulimit 是一种 linux 系统的**内建功能**,它具有一套参数集,用于为__由它所在的 shell 进程及其子进程__的资源使用设置限制。本文将在后面的章节中详细说明 ulimit 的功能,使用以及它的影响,并以具体的例子来详细地阐述它在限制资源使用方面的影响。
|
||||
|
||||
===== ulimit 的功能和用法 =====
|
||||
|
||||
==== ulimit 功能简述 ====
|
||||
假设有这样一种情况,当一台 Linux 主机上同时登陆了 10 个人,在系统资源无限制的情况下,这 10 个用户同时打开了 500 个文档,而假设每个文档的大小有 10M,这时系统的内存资源就会受到巨大的挑战。
|
||||
|
||||
而实际应用的环境要比这种假设复杂的多,例如在一个嵌入式开发环境中,各方面的资源都是非常紧缺的,对于开启**文件描述符**的数量,分配**堆栈**的大小,CPU 时间,虚拟内存大小等等,都有非常严格的要求。资源的合理限制和分配,不仅仅是保证系统可用性的必要条件,也与系统上软件运行的性能有着密不可分的联系。这时,ulimit 可以起到很大的作用,它是一种简单并且有效的实现资源限制的方式。
|
||||
|
||||
ulimit 用于__限制 shell 启动进程所占用的资源__,支持以下各种类型的限制:所创建的内核文件的大小、进程数据块的大小、Shell 进程创建文件的大小、内存锁住的大小、常驻内存集的大小、打开文件描述符的数量、分配堆栈的最大大小、CPU 时间、单个用户的最大线程数、Shell 进程所能使用的最大虚拟内存。同时,它支持硬资源和软资源的限制。
|
||||
|
||||
作为临时限制,ulimit 可以作用于通过使用其命令登录的 shell 会话,在会话终止时便结束限制,并不影响于其他 shell 会话。而对于长期的固定限制,ulimit 命令语句又可以被添加到由登录 shell 读取的文件中,作用于特定的 shell 用户。在下面的章节中,将详细介绍如何使用 ulimit 做相应的资源限制。
|
||||
|
||||
==== 如何使用 ulimit ====
|
||||
ulimit 通过一些参数选项来管理不同种类的系统资源。在本节,我们将讲解这些参数的使用。
|
||||
|
||||
ulimit 命令的格式为:ulimit [options] [limit]
|
||||
|
||||
具体的 options 含义以及简单示例可以参考以下表格。
|
||||
**表 1. ulimit 参数说明**
|
||||
|
||||
选项 [options] 含义 例子
|
||||
-H 设置硬资源限制,一旦设置不能增加。 ulimit – Hs 64;限制硬资源,线程栈大小为 64K。
|
||||
-S 设置软资源限制,设置后可以增加,但是不能超过硬资源设置。 ulimit – Sn 32;限制软资源,32 个文件描述符。
|
||||
-a 显示当前所有的 limit 信息。 __ulimit – a__;显示当前所有的 limit 信息。
|
||||
-c 最大的 core 文件的大小, 以 blocks 为单位。 ulimit – c unlimited; 对生成的 core 文件的大小不进行限制。
|
||||
-d 进程最大的数据段的大小,以 Kbytes 为单位。 ulimit -d unlimited;对进程的数据段大小不进行限制。
|
||||
-f 进程可以创建文件的最大值,以 blocks 为单位。 ulimit – f 2048;限制进程可以创建的最大文件大小为 2048 blocks。
|
||||
-l 最大可加锁内存大小,以 Kbytes 为单位。 ulimit – l 32;限制最大可加锁内存大小为 32 Kbytes。
|
||||
-m 最大内存大小,以 Kbytes 为单位。 ulimit – m unlimited;对最大内存不进行限制。
|
||||
-n 可以打开最大文件描述符的数量。 ulimit – n 128;限制最大可以使用 128 个文件描述符。
|
||||
-p __管道缓冲区__的大小,以 Kbytes 为单位。 ulimit – p 512;限制管道缓冲区的大小为 512 Kbytes。
|
||||
-s 线程栈大小,以 Kbytes 为单位。 ulimit – s 512;限制线程栈的大小为 512 Kbytes。
|
||||
-t 最大的 CPU 占用时间,以秒为单位。 ulimit – t unlimited;对最大的 CPU 占用时间不进行限制。
|
||||
-u 用户最大可用的进程数。 ulimit – u 64;限制用户最多可以使用 64 个进程。
|
||||
-v 进程最大可用的虚拟内存,以 Kbytes 为单位。 ulimit – v 200000;限制最大可用的虚拟内存为 200000 Kbytes。
|
||||
|
||||
我们可以通过以下几种方式来使用 ulimit:
|
||||
|
||||
在用户的启动脚本中如果用户使用的是 bash,就可以在用户的目录下的 .bashrc 文件中,加入 ulimit – u 64,来限制用户最多可以使用 64 个进程。此外,可以在与 .bashrc 功能相当的启动脚本中加入 ulimt。在应用程序的启动脚本中如果用户要对某个应用程序 myapp 进行限制,可以写一个简单的脚本 startmyapp。
|
||||
|
||||
ulimit – s 512
|
||||
myapp
|
||||
|
||||
以后只要通过脚本 startmyapp 来启动应用程序,就可以限制应用程序 myapp 的线程栈大小为 512K。
|
||||
|
||||
直接在控制台输入
|
||||
|
||||
user@tc511-ui: ulimit – p 256
|
||||
限制管道的缓冲区为 256K。
|
||||
|
||||
==== 用户进程的有效范围 ====
|
||||
ulimit 作为对资源使用限制的一种工作,是有其作用范围的。那么,它限制的对象是单个用户,单个进程,还是整个系统呢?事实上,ulimit 限制的是__当前 shell 进程以及其派生的子进程__。举例来说,如果用户同时运行了两个 shell 终端进程,只在其中一个环境中执行了 ulimit – s 100,则该 shell 进程里创建文件的大小收到相应的限制,而同时另一个 shell 终端包括其上运行的子程序都不会受其影响:
|
||||
**Shell 进程 1**
|
||||
ulimit – s 100
|
||||
cat testFile >newFile
|
||||
File size limit exceeded
|
||||
|
||||
**Shell 进程 2**
|
||||
cat testFile >newFile
|
||||
ls – s newFile
|
||||
323669 newFile
|
||||
|
||||
那么,是否有针对__某个具体用户__的资源加以限制的方法呢?答案是有的,方法是通过修改系统的 /etc/security/limits 配置文件。该文件不仅能限制指定用户的资源使用,还能限制指定组的资源使用。该文件的每一行都是对限定的一个描述,格式如下:
|
||||
|
||||
<domain><type><item><value>
|
||||
|
||||
domain 表示用户或者组的名字,还可以使用 * 作为通配符。Type 可以有两个值,soft 和 hard。Item 则表示需要限定的资源,可以有很多候选值,如 stack,cpu,nofile 等等,分别表示最大的堆栈大小,占用的 cpu 时间,以及打开的文件数。通过添加对应的一行描述,则可以产生相应的限制。例如:
|
||||
|
||||
* hard noflle 100
|
||||
|
||||
该行配置语句限定了**任意用户**所能创建的最大文件数是 100。
|
||||
|
||||
现在已经可以**对进程和用户分别做资源限制**了,看似已经足够了,其实不然。很多应用需要对__整个系统的资源__使用做一个总的限制,这时候我们需要修改 /proc 下的配置文件。/proc 目录下包含了很多系统当前状态的参数,例如 /proc/sys/kernel/pid_max,/proc/sys/net/ipv4/ip_local_port_range 等等,从文件的名字大致可以猜出所限制的资源种类。由于该目录下涉及的文件众多,在此不一一介绍。有兴趣的读者可打开其中的相关文件查阅说明。
|
||||
|
||||
===== ulimit 管理系统资源的例子 =====
|
||||
ulimit 提供了在 shell 进程中限制系统资源的功能。本章列举了一些使用 ulimit 对用户进程进行限制的例子,详述了这些限制行为以及对应的影响,以此来说明 ulimit 如何对系统资源进行限制,从而达到调节系统性能的功能。
|
||||
|
||||
**使用 ulimit 限制 shell 的内存使用**
|
||||
在这一小节里向读者展示如何使用 – d,– m 和 – v 选项来对 shell 所使用的内存进行限制。
|
||||
首先我们来看一下不设置 ulimit 限制时调用 ls 命令的情况:
|
||||
图 2. 未设置 ulimit 时 ls 命令使用情况
|
||||
{{./1}}
|
||||
|
||||
|
||||
大家可以看到此时的 ls 命令运行正常。下面设置 ulimit:
|
||||
ulimit -d 1000 -m 1000 -v 1000
|
||||
这里再温习一下前面章节里介绍过的这三个选项的含义:
|
||||
|
||||
-d:设置数据段的最大值。单位:KB。
|
||||
-m:设置可以使用的常驻内存的最大值。单位:KB。
|
||||
-v:设置虚拟内存的最大值。单位:KB。
|
||||
|
||||
通过上面的 ulimit 设置我们已经把当前 shell 所能使用的最大内存限制在 1000KB 以下。接下来我们看看这时运行 ls 命令会得到什么样的结果:
|
||||
haohe@sles10-hehao:~/code/ulimit> ls test -l
|
||||
/bin/ls: error while loading shared libraries: libc.so.6: **failed to map segment**
|
||||
from shared object: Cannot allocate memory
|
||||
从上面的结果可以看到,此时 ls 运行失败。根据系统给出的错误信息我们可以看出是由于**调用 libc 库时内存分配失败**而导致的 ls 出错。那么我们来看一下这个 libc 库文件到底有多大:
|
||||
图 3. 查看 libc 文件大小
|
||||
{{./2}}
|
||||
|
||||
从上面的信息可以看出,这个 libc 库文件的大小是 1.5MB。而我们用 ulimit 所设置的内存使用上限是 1000KB,小于 1.5MB,这也就充分证明了 ulimit 所起到的限制 shell 内存使用的功能。
|
||||
|
||||
**使用 ulimit 限制 shell 创建的文件的大小**
|
||||
接下来向读者展示如何使用 -f 选项来对 shell 所能创建的文件大小进行限制。
|
||||
|
||||
首先我们来看一下,没有设置 ulimit -f 时的情况:
|
||||
图 4. 查看文件
|
||||
{{./3}}
|
||||
|
||||
现有一个文件 testFile 大小为 323669 bytes,现在使用 cat 命令来创建一个 testFile 的 copy:
|
||||
图 5. 未设置 ulimit 时创建复本
|
||||
{{./4}}
|
||||
从上面的输出可以看出,我们成功的创建了 testFile 的拷贝 newFile。
|
||||
|
||||
下面我们设置 ulimt – f 100:
|
||||
ulimit -f 100
|
||||
-f 选项的含义是:用来设置 shell 可以创建的文件的最大值。**单位是 blocks**。
|
||||
|
||||
现在我们再来执行一次相同的拷贝命令看看会是什么结果:
|
||||
图 6. 设置 ulimit 时创建复本
|
||||
{{./5}}
|
||||
这次创建 testFile 的拷贝失败了,系统给出的出错信息时文件大小超出了限制。在 Linux 系统下一个 block 的默认大小是 512 bytes。所以上面的 ulimit 的含义就是限制 shell 所能创建的文件最大值为 512 x 100 = 51200 bytes,小于 323669 bytes,所以创建文件失败,符合我们的期望。这个例子说明了如何使用 ulimit 来控制 shell 所能创建的最大文件。
|
||||
|
||||
**使用 ulimit 限制程序所能创建的 socket 数量**
|
||||
考虑一个现实中的实际需求。对于一个 C/S 模型中的 server 程序来说,它会为多个 client 程序请求创建多个 socket 端口给与响应。如果恰好有大量的 client 同时向 server 发出请求,那么此时 server 就会需要创建大量的 socket 连接。但在一个系统当中,往往需要限制单个 server 程序所能使用的最大 socket 数,以供其他的 server 程序所使用。那么我们如何来做到这一点呢?答案是我们可以通过 ulimit 来实现!细心的读者可能会发现,通过前面章节的介绍似乎没有限制 socket 使用的 ulimit 选项。是的,ulimit 并没有哪个选项直接说是用来限制 socket 的数量的。但是,我们有 -n 这个选项,它是用于限制一个进程所能打开的文件描述符的最大值。在 Linux 下__一切资源皆文件__,普通文件是文件,磁盘打印机是文件,socket 当然也是文件。**在 Linux 下创建一个新的 socket 连接,实际上就是创建一个新的文件描述符。**如下图所示(查看某个进程当前打开的文件描述符信息):
|
||||
图 7. 查看进程打开文件描述符
|
||||
{{./6}}
|
||||
因此,我们可以通过使用 ulimit – n 来限制程序所能打开的最大文件描述符数量,从而达到限制 socket 创建的数量。
|
||||
|
||||
**使用 ulimit 限制 shell 多线程程序堆栈的大小(增加可用线程数量)**
|
||||
在最后一个例子中,向大家介绍如何使用 -s(单位 KB)来对**线程的堆栈大小**进行限制,从而减少整个多线程程序的内存使用,增加可用线程的数量。这个例子取自于一个真实的案例。我们所遇到的问题是系统对我们的多线程程序有如下的限制:
|
||||
|
||||
ulimit -v 200000
|
||||
|
||||
根据本文前面的介绍,这意味着我们的程序最多只能使用不到 200MB 的虚拟内存。由于我们的程序是一个多线程程序,程序在运行时会根据需要创建新的线程,这势必会增加总的内存需求量。一开始我们对堆栈大小的限制是 1024 (本例子中使用 1232 来说明):
|
||||
|
||||
# ulimit – s 1232
|
||||
|
||||
当我们的程序启动后,通过 **pmap** 来查看其内存使用情况,可以看到多个占用 1232KB 的数据段,这些就是程序所创建的线程所使用的堆栈:
|
||||
图 8. 程序线程所使用的堆栈
|
||||
{{./7}}
|
||||
每当一个新的线程被创建时都需要新分配一段大小为 1232KB 的内存空间,而我们总的虚拟内存限制是 200MB,所以如果我们需要创建更多的线程,那么一个可以改进的方法就是__减少每个线程的固定堆栈大小__,这可以通过 ulimit – s 来实现:
|
||||
# ulimit -s 512
|
||||
我们将堆栈大小设置为 512KB,这时再通过 pmap 查看一下我们的设置是否起作用:
|
||||
图 9. 设置 ulimit 后堆栈大小
|
||||
{{./8}}
|
||||
从上面的信息可以看出,我们已经成功的将线程的堆栈大小改为 512KB 了,这样在总内存使用限制不变的情况下,我们可以通过本小节介绍的方法来增加可以创建的线程数,从而达到改善程序的多线程性能。
|
||||
|
||||
===== 总结 =====
|
||||
综上所述,linux 系统中的 ulimit 指令,对资源限制和系统性能优化提供了一条便捷的途径。从用户的 shell 启动脚本,应用程序启动脚本,以及直接在控制台,都可以通过该指令限制系统资源的使用,包括所创建的内核文件的大小、进程数据块的大小、Shell 进程创建文件的大小、内存锁住的大小、常驻内存集的大小、打开文件描述符的数量、分配堆栈的最大大小、CPU 时间、单个用户的最大线程数、Shell 进程所能使用的最大虚拟内存,等等方面。本文中的示例非常直观的说明了 ulimit 的使用及其产生的效果,显而易见,ulimit 对我们在 Linux 平台的应用和开发工作是非常实用的。
|
||||
BIN
Zim/Utils/ulimit/1
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
Zim/Utils/ulimit/2
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
Zim/Utils/ulimit/3
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
Zim/Utils/ulimit/4
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/Utils/ulimit/5
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
Zim/Utils/ulimit/6
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
Zim/Utils/ulimit/7
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Zim/Utils/ulimit/8
Normal file
|
After Width: | Height: | Size: 12 KiB |
93
Zim/Utils/ulimit/实验.txt
Normal file
@@ -0,0 +1,93 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-15T19:33:26+08:00
|
||||
|
||||
====== 实验 ======
|
||||
Created Monday 15 October 2012
|
||||
|
||||
ulimit命令是shell的__内置命令__,因为它设置的资源限制值需要被当前shell或其子进程继承。进程的资源限制通常在系统的初始化时由进程0建立,然后由每个后续进程继承。一般用户登录时使用PAM机制时,系统会自动读取__/etc/security/limits.conf__文件中的内容,设置__会话中所有进程__的资源限制。
|
||||
|
||||
limits.conf 文件实际是 Linux __PAM__(插入式认证模块,Pluggable Authentication Modules)中 __pam_limits.so__ 的配置文件,而且只针对于**单个会话**。
|
||||
|
||||
limits.conf的格式如下:
|
||||
|
||||
username|@groupname type resource limit
|
||||
|
||||
username|@groupname:设置需要被限制的用户名,组名前面加@和用户名区别。也可以用通配符*来做所有用户的限制。
|
||||
|
||||
type:有 soft,hard 和 -,soft 指的是当前系统生效的设置值。hard 表明系统中所能设定的最大值。soft 的限制不能比hard 限制高。用 - 就表明**同时设置**了 soft 和 hard 的值。
|
||||
|
||||
resource:
|
||||
core - 限制内核转储文件的大小
|
||||
date - 最大数据大小
|
||||
fsize - 最大文件大小
|
||||
memlock - 最大锁定内存地址空间
|
||||
nofile - 打开文件的最大数目
|
||||
rss - 最大持久设置大小
|
||||
stack - 最大栈大小
|
||||
cpu - 以分钟为单位的最多 CPU 时间
|
||||
noproc - 进程的最大数目
|
||||
as - 地址空间限制
|
||||
maxlogins - 此用户允许登录的最大数目
|
||||
|
||||
要使 limits.conf 文件配置生效,必须要确保 pam_limits.so 文件被加入到启动文件中。查看 /etc/pam.d/login 文件中有:
|
||||
|
||||
session required /lib/security/pam_limits.so
|
||||
|
||||
Bash__内建了__一个限制器"ulimit"。注意任何硬限制都不能设置得太高,因此如果你在/etc/profile或用户的 .bash_profile (用户不能编辑或删除这些文件)中定义了限制规则,你就能对用户的Bash shell实施限制。这对于**缺少PAM支持的LINUX旧发行版本**是很有用的。你还必须确保用户不能改变他们的登录shell。
|
||||
|
||||
[__root__@kb310 ~]# **getconf **-a |grep OPEN
|
||||
**OPEN_MAX 1024**
|
||||
_POSIX_OPEN_MAX 1024
|
||||
[root@kb310 ~]# ulimit -HSn 2048 #root可以提高系统的硬限制(H)和软限制(S), 但是H__必须大于或等于__S的值。
|
||||
[root@kb310 ~]# getconf -a |grep OPEN #getconf和ulimit -a显示的是进程的__软限制值__。
|
||||
**OPEN_MAX 2048**
|
||||
_POSIX_OPEN_MAX 2048
|
||||
[root@kb310 ~]# ulimit **-a**
|
||||
core file size (blocks, -c) 0 #(a, b):a表示后面数值的单位,b表示设置或显示该资源的**ulimit命令行选项**。这里为0表示**不能生成**core文件。
|
||||
data seg size (kbytes, -d) unlimited #可以从__ulimit -c unlimited__命令指定进程生成的core file大小不限。
|
||||
scheduling priority (-e) 30
|
||||
file size (blocks, -f) unlimited
|
||||
pending signals (-i) 30802
|
||||
max locked memory (kbytes, -l) unlimited
|
||||
max memory size (kbytes, -m) unlimited
|
||||
**open files (-n) 2048**
|
||||
pipe size (**512 bytes**, -p) 8 #管道的容量(pipe capacity),决定write是否阻塞。PIPE_BUF决定一次write的内容是否可能与别的进程write内容__交错,即write的原子性__。
|
||||
POSIX message queues (bytes, -q) 819200
|
||||
real-time priority (-r) 99
|
||||
stack size (kbytes, -s) 8192
|
||||
cpu time (seconds, -t) unlimited
|
||||
max user processes (-u) 30802
|
||||
virtual memory (kbytes, -v) unlimited
|
||||
file locks (-x) unlimited
|
||||
[root@kb310 ~]# logout
|
||||
[__geekard__@kb310 Documents]$ ulimit -a
|
||||
core file size (blocks, -c) 0
|
||||
data seg size (kbytes, -d) unlimited
|
||||
scheduling priority (-e) 30
|
||||
file size (blocks, -f) unlimited
|
||||
pending signals (-i) 30802
|
||||
max locked memory (kbytes, -l) unlimited
|
||||
max memory size (kbytes, -m) unlimited
|
||||
open files (-n) 1024
|
||||
pipe size (512 bytes, -p) 8
|
||||
POSIX message queues (bytes, -q) 819200
|
||||
real-time priority (-r) 99
|
||||
stack size (kbytes, -s) 8192
|
||||
cpu time (seconds, -t) unlimited
|
||||
max user processes (-u) 30802
|
||||
virtual memory (kbytes, -v) unlimited
|
||||
file locks (-x) unlimited
|
||||
[geekard@kb310 ~]$ ulimit -n 1026 #没有加H或S参数时,默认设置进程的软限制。非root进程__只能减小__其软限制值。
|
||||
-bash: ulimit: open files: cannot modify limit: __Operation not permitted__
|
||||
[geekard@kb310 ~]$ ulimit -n **1023**
|
||||
[geekard@kb310 ~]$ ulimit -a |grep open
|
||||
open files (-n) 1023
|
||||
[geekard@kb310 ~]$ ulimit __-H__n 1022 #任何进程都可以降低其硬限制值,但是它__必须要大于或等于__其软限制值。这种降低对普通用户不可逆。
|
||||
-bash: ulimit: open files: cannot modify limit: __Invalid__ argument #因为软限制值为1023,所以设的硬限制值**无效**。
|
||||
[geekard@kb310 ~]$ ulimit __-S__n 1022
|
||||
[geekard@kb310 ~]$ ulimit -Sn 10
|
||||
[geekard@kb310 ~]$ ulimit -Hn 1022 #__降低__硬限制值,而且比软限制值大。
|
||||
[geekard@kb310 ~]$ ulimit -a |grep open
|
||||
open files (-n) 10 #显示的是进程的软限制值。
|
||||
[geekard@kb310 ~]$
|
||||
7
Zim/job.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-03T19:57:06+08:00
|
||||
|
||||
====== job ======
|
||||
Created Monday 03 September 2012
|
||||
|
||||
198
Zim/job/BGP原理总结.txt
Normal file
@@ -0,0 +1,198 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-04T10:40:17+08:00
|
||||
|
||||
====== BGP原理总结 ======
|
||||
Created Tuesday 04 September 2012
|
||||
http://blog.chinaunix.net/space.php?uid=12380499&do=blog&id=105407
|
||||
|
||||
BGP,边界网关协议.一种应用在**大型网络**上的协议,主要用来控制AS间通信,具有灵活的可控性,同时也是一种复杂的协议.
|
||||
|
||||
类似距离矢量特性,BGP是一种路径矢量协议,__每个AS作为一跳__.它通过TCP协议运行,使用TCP 179端口通信,可靠性由TCP进行控制,因为BGP的路由表一般来说都很大,所以使用TCP利用窗口滑动技术可以提高传输效率,这一点不同于EIGRP、OSPF这样的IGP协议,后者使用1-1的确认方式,效率低下。BGP存活消息每隔60秒发一次,保持时间180秒。
|
||||
|
||||
BGP必须__手工指定邻接关系__,BGP邻接关系分为**内部邻居**(处于同一AS中),内部邻居之间可以跨越多个路由器,默认TTL跳数为255,所以无需额外使用多跳属性;**外部邻居**(不同的AS中),一般来说外部邻居都是__直连__的邻居,因为这样通过直连或者静态路由就可以到达对方,如果不是直连,则需要有本地路由配合确保能够到达对方,而且必须明确设置多跳属性,因为外部BGP邻居默认跳为1跳,配置方法:neighbor ip ebgp-multihop num。
|
||||
|
||||
BGP定义的属性:
|
||||
公认强制属性:1。AS路径 2。下一跳 3。源头 这些都是__必须被传播__的。
|
||||
公认自由:1。本地优先级 2。原子聚合(告诉邻接AS,始发路由器对路由进行了聚合)
|
||||
可选传递:1。聚合站 2。共同体
|
||||
可选非传递:MED(多出口鉴别器)
|
||||
CISCO私有还多了一个:权重
|
||||
|
||||
AS路径属性:每个BGP路由都携带这样的路径列表,BGP拒绝列表中包含自己AS的路径,这样确保无环路。
|
||||
|
||||
下一跳属性:从EBGP学习到路由条目,在__传播给IBGP__时候,默认保持下一跳不变,这里的下一跳是进入下一个AS的边界路由器的接口IP,因此在AS内的路由,如果要到下一个AS,它就要首先查找BGP表,从BGP表里发现要通过的下一跳,所以IP路由表中必须要有到达下一跳的路由信息,否则下一跳不可达的BGP路由是不会被装入路由表的。如果AS内的路由器没办法获得到达下一跳的路径信息,那就应该使用next-hop-self属性(修改下一跳)。对于多路访问网络第三方下一跳会自动修正下一跳为最合适的接口(但是NBMA情况下,第三方下一跳有时会出问题)。BY:http://www.mycisco.cn
|
||||
|
||||
源头属性:定义路径信息的初始来源的源头,它可能为:
|
||||
1。IGP,起始在AS内部,通过NETWORK宣告的。用i表示
|
||||
2。EGP,早期的,现在已过时。
|
||||
3。不完全,源头未知或者通过其他方法获悉的,例如被充分发到BGP中的,不完整的源头用?表示。
|
||||
|
||||
本地优先级属性:只在AS内部有作用,只发送给内部BGP邻居,影响__数据出去__的方向。CISCO默认值100。可以使用在BGP进程下bgp default local-preference num来修改,越大越好。可以在neighbor ip remote-as num后跟上route-map name in,在IN方向上对不同的外部网络设置不同的本地优先级,这样本地AS到达不同的外部网络时可以选择不同的出口。这里需要注意,为了影响自己AS到达外部网络,所以外部网络在从不同的外部邻居学习进来的时候就要在各自的IN方向设置本地优先级,这样本地BGP表里才会是通过不同的接口有不同的本地优先级别。
|
||||
|
||||
权重属性:CISCO私有的,只对当前路由器有效,影响数据出方向,权重越大越好,可以使用neighbor ip remote-as num weight num让权重与外部邻居挂钩,从而让一个路由器有多个到外部AS出口时选择最大的一个。
|
||||
|
||||
MED属性:在自己AS内定义,影响外部AS数据进来,这是唯一一个影响__数据入方向__的属性,正因为是影响数据入方向,所以MED作的工作是传播给邻接AS,让邻接AS根据不同的MED选择不同的出路。因此在使用route-map作为neighbor命令的参数时候要使用OUT方向(影响别人从而达到影响自己的目的)。全局修改MED在BGP进程下使用default-metric num.
|
||||
|
||||
BGP**水平分割**原则:从IBGP学到的不应该在传播给下一个IBGP(学习的IBGP除外)。正是由于此,__IBGP之间需要进行全互连__,这样才能确保各个IBGP都能学习到外部BGP路由。
|
||||
|
||||
BGP**同步**规则:BGP规定,BGP路由器不会将通过IBGP学习到路由发送给EBGP(但是会装入BGP表,但不是最优路径,所以不会发给别人),除非这个路由在IGP中有获悉,即只有当IGP与IBGP都有到达某个网络的路由时,这个BGP路由才能发送给EBGP(从外部学到的BGP路由必须在IGP中也存在对应的路由,这就要求将外部BGP重分发到IGP中,一般来说这种情况不可能,外部BGP路由太大了)。本来这个规则是为了防止路由黑洞的,但是却带来了一个麻烦,即一旦IGP里路由与BGP的不同步,路由就不能正常被使用。因此现在的ISP网络大都是在AS内实行全互连,且同时都运行BGP,这样就可以关闭同步。
|
||||
|
||||
指定BGP邻居的方法:
|
||||
neighbor 邻居IP、对等体组名 remote-as AS号
|
||||
设置对等体组方法:
|
||||
neighbor 对等体组名 peer-group
|
||||
加入对等体组方法:
|
||||
neighbor 对等体IP peer-group 对等体组名
|
||||
BGP对对等体独特要求:BGP的路由更新源地址一定要是和指定邻居中的IP一致。因此如果使用环回地址作为对等体IP,那么就需要配上neighbor 对等体IP(环回) remote-as update-source 环回地址.外部对等体最需要注意的就是多跳问题,见上面总结,同时也必须确保这个跳是可达的。
|
||||
=============一个范例============
|
||||
router bgp 65111
|
||||
neighbor 1.1.1.1 remote-as 65111 (内部邻居,使用的对等体IP是个对方的环回地址)
|
||||
neighbor 1.1.1.1 update-source loopback0 (指定更新源是自己的环回地址)
|
||||
neighbor 1.1.1.1 next-hop-self (修改下一跳)
|
||||
neighbor 2.2.2.2 remote-as 65222 (外部邻居,也使用对方环回接口)
|
||||
neighbor 2.2.2.2 ebgp-multihop 2 (设置多跳,跳数2)
|
||||
neighbor 2.2.2.2 update-source loopback 0 (指定更新源是自己的环回地址)
|
||||
ip route 2.2.2.2 255.255.255.255 172.16.31.2(设置到达外部邻居环回接口的路由,否则邻居关系起不来)
|
||||
重置BGP绘画,因为BGP一般运算很占资源,所以一般不能轻易使用clear ip bgp * ,一般应带上soft in或者soft out参数。
|
||||
BGP邻居的状态:
|
||||
1。IDLE
|
||||
2。CONNECT
|
||||
3。ACTIVE
|
||||
4。OPEN SENT
|
||||
5。OPEN CONFIRM
|
||||
6。Established
|
||||
只有在已建立状态后,路由器才相互发送路由信息。
|
||||
空闲状态表示路由器还不知道如何前往邻居,如果一直是这个状态,要检查邻居配置以及1、2层连通性,检查有无到达对方的网络。
|
||||
活动状态表明,它已经找到邻居,并发送了打开分组,但还未得到响应。如果长时间这个状态,检查邻居是配置了正确的邻接关系和检查链路是否是单向链路,或检查对方有无回来的路。
|
||||
CISCO路由器对BGP条目的选择标准:(BGP只选一个最好的路径加入路由表)
|
||||
1。权重最大的(当前路由器有效)
|
||||
2。本地优先级最高的(AS内有效)
|
||||
3。当前路由器通告的(next hop=0.0.0.0)
|
||||
4。AS PATH最短的
|
||||
5。源头编码最小的(igp<egp<不完全)
|
||||
6.MED最小的
|
||||
7。EBGP优先IBGP路径、
|
||||
8。经由最近的IGP邻居的路径
|
||||
9。最老的EBGP路径
|
||||
10。BGP邻居中路由器ID最小的
|
||||
11。邻居IP地址最小的
|
||||
BY:http://www.mycisco.cn
|
||||
BGP的汇总特性:
|
||||
关闭与否auto-summary对影响BGP控制重分来进来的路由汇总特性,若打开该功能,则重分发进来的子网会被自动汇总为相应的分类网络,关闭则保持分来而来的子网。新版本IOS对重分来的路由不再进行自动汇总。
|
||||
network宣告规则:
|
||||
1。如果命令中不带掩码且自动汇特性打开,要求宣告为对应的分类网络,此时要求路由表中必须存在至少该分类网络的一个子网路由,否则不会通告该分类网络到其他路由器的BGP进程。如果最后一个子网也消失了,那路由器将从所有其他路由器中撤回这个分类网络。
|
||||
如果命令中不带掩码且自动汇特性关闭,则需要IP路由表中有一条严格相同的路由存在.
|
||||
case1:
|
||||
router bgp 1
|
||||
network 172.16.0.0
|
||||
r3-2514#sh ip bgp
|
||||
结果: 没有宣告进去.前提:未关闭汇总,宣告的是主类,IP表中无子网路由及精确一致路由.
|
||||
=====================================================================
|
||||
case2 :
|
||||
router bgp 1
|
||||
network 172.16.0.0
|
||||
ip route 172.16.10.0 255.255.255.0 null 0
|
||||
r3-2514#sh ip bgp
|
||||
BGP table version is 2, local router ID is 3.3.3.3
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
*> 172.16.0.0 0.0.0.0 0 32768 i
|
||||
结果:正常宣告.前提:未关闭汇总,宣告的是主类,IP表中有主类的子网存在.
|
||||
===================================================================
|
||||
case3:
|
||||
router bgp 1
|
||||
network 172.16.0.0
|
||||
ip route 172.16.0.0 255.255.0.0 null 0
|
||||
|
||||
r3-2514#sh ip bgp
|
||||
BGP table version is 1, local router ID is 3.3.3.3
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
* 172.16.0.0 0.0.0.0 0 32768 i
|
||||
结果:宣告进去,(特殊:变成最优要等很长时间).前提:未关闭自动汇总,宣告的是主类,IP路由表有精确匹配,但无子网路由.
|
||||
=================================================================
|
||||
case4:
|
||||
router bgp 1
|
||||
network 172.16.10.0
|
||||
r3-2514#sh ip bgp
|
||||
结果:宣告不进去, 前提:宣告172.16.10.0且不带MASK,没有配置子网路由和精确匹配.
|
||||
=================================================================
|
||||
case5:
|
||||
router bgp 1
|
||||
network 172.16.10.0
|
||||
ip route 172.16.10.0 255.255.255.0 null 0
|
||||
r3-2514#sh ip bgp
|
||||
结果:宣告不进去,前提:宣告172.16.10.0不带MASK,配置精确匹配.
|
||||
|
||||
==================================================================
|
||||
case6:
|
||||
router bgp 1
|
||||
network 172.16.10.0
|
||||
ip route 172.16.10.0 255.255.255.252 null 0
|
||||
|
||||
结果:宣告不进去.前提:宣告的是172.16.10.0 不带mask,IP路由表中有它的子网路由.
|
||||
|
||||
总结:未关闭自动汇总时,使用主类网络通告(不带MASK),同时IP表中存在至少一条子网路由(或精确匹配的).
|
||||
=======================================================================
|
||||
case7 :
|
||||
router bgp 1
|
||||
no auto-summary
|
||||
network 172.16.0.0
|
||||
ip route 172.16.10.0 255.255.255.0 null 0
|
||||
r3-2514#sh ip bgp
|
||||
结果:宣告不进去.前提:关闭自动汇总,宣告主类,有子网路由.
|
||||
========================================================================
|
||||
case8:
|
||||
router bgp 1
|
||||
no auto-summary
|
||||
network 172.16.0.0
|
||||
ip route 172.16.0.0 255.255.0.0 null 0
|
||||
r3-2514#sh ip bgp
|
||||
BGP table version is 2, local router ID is 3.3.3.3
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
*> 172.16.0.0 0.0.0.0 0 32768 i
|
||||
结果:正常宣告.前提:关闭自动汇总,宣告主类,IP路由表有完全匹配路由.
|
||||
==========================================================================
|
||||
case9:
|
||||
router bgp 1
|
||||
no auto-summary
|
||||
network 172.16.0.0或net 172.16.10.0
|
||||
ip route 172.16.10.0 255.255.255.0 null 0
|
||||
r3-2514#sh ip bgp
|
||||
结果:宣告不进去.前提:关闭自动汇总,宣告172.16.10.0或172.16.0.0,IP路由表有完全匹配路由.
|
||||
|
||||
===========================================================================
|
||||
case10:
|
||||
router bgp 1
|
||||
no auto-summary
|
||||
network 172.16.10.0
|
||||
ip route 172.16.0.0 255.255.0.0 null 0
|
||||
r4-2501#sh ip bgp
|
||||
结果:宣告不进去.
|
||||
总结:关闭汇总,只能通告分类网络(不带mask),且必须要求宣告的网络与IP路由表的条目要完全匹配.
|
||||
============================================================================
|
||||
2。如果命令中带掩码,那么IP路由表中必须存在有一条与该通告命令完全一样(网络号和掩码都要相同)的路由,否则不会通告该网络。
|
||||
关于BGP宣告方面文章看这里:
|
||||
http://www.cisco.com/en/US/tech/tk365/technologies_tech_note09186a00800945ff.shtml#topic1
|
||||
利用network进行BGP的汇总:
|
||||
其实本身network并不是汇总的命令,它是BGP宣告网络的方法,只是我们可以利用它在宣告的时候就宣告为汇总的网络。
|
||||
从BGP的network宣告规则可以看出,要宣告一个汇总路由条目,前提是路由表中要已存在相同地址和掩码的路由条目,所以很多时候为了宣告汇总路由往往需要先手工指定一个相同地址和前缀的静态路由(指向NULL0接口),这样既可以保证宣告成功又能保证当没有具体子网时候数据被丢弃,防止路由黑洞。例如:
|
||||
route bgp 65111
|
||||
network 192.168.0.0
|
||||
network 192.168.1.0
|
||||
network 192.168.2.0
|
||||
network 192.168.3.0
|
||||
这样的宣告可以改成这样汇总:
|
||||
ip route 192.168.0.0 255.255.255.252 null 0
|
||||
router bgp 65111
|
||||
network 192.168.0.0 255.255.252.0
|
||||
利用aggregate-address汇总BGP路由:
|
||||
这是汇总BGP表里路由的命令,它只聚合已经存在于BGP表中的网络。它在汇总后会自动增加一条指向空接口的路由到路由表中。
|
||||
aggregate-address ip地址 掩码 summary-only|as-set
|
||||
参数summary-only表示只通告汇总路由出去,默认是汇总+具体路由一起出去的。
|
||||
as-set表示在聚合路由的的AS路径属性字段里填上所有具体路由中包含的那些AS号,否则只包含执行聚合的AS的号。
|
||||
7
Zim/job/BGP集训营.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-04T10:22:58+08:00
|
||||
|
||||
====== BGP集训营 ======
|
||||
Created Tuesday 04 September 2012
|
||||
|
||||
132
Zim/job/BGP集训营/第一天BGP概览基础.txt
Normal file
@@ -0,0 +1,132 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-04T10:23:08+08:00
|
||||
|
||||
====== 第一天BGP概览基础 ======
|
||||
Created Tuesday 04 September 2012
|
||||
http://ltyluck.blog.51cto.com/170459/204641
|
||||
|
||||
要访问Sina的网站,我们需要有以下几个步骤:
|
||||
1、有ISP的接入
|
||||
2、有到该站点的路由
|
||||
3、有DNS提供IP到命名的服务
|
||||
|
||||
Inter-domain routing(域间路由)
|
||||
域(domain):即自治系统(Autonomous system,AS)
|
||||
AS:处于同一技术掌控和政策掌控之下的连续的路由范围。为了解决Internet的扩展性,并且降低管理成本,增加公网(Internet)流量选路策略的灵活性。
|
||||
|
||||
路由协议根据运算机制进行分类:
|
||||
1. 静态路由
|
||||
2. 动态路由
|
||||
|
||||
动态路由根据部署的边界进行分类:
|
||||
1. 域内路由(intra-domain routing):也叫做内部网关协议(Interior gateway protocol,IGP)
|
||||
2. 域间路由(inter-domain routing):也叫做外部网关协议(Exterior gateway protocol,EGP)
|
||||
注:这里的网关等同于AS。
|
||||
|
||||
常见的IGP:RIP,OSPF,IS-IS,__EIGRP,IGRP__
|
||||
常见的EGP:EGP,BGPv1, BGPv2, BGPv3, BGPv4
|
||||
EGP被淘汰是因为他没有一个度量的标准。
|
||||
BGPv1, BGPv2, BGPv3:完全被淘汰
|
||||
|
||||
域间路由解决方案要考虑到的问题:
|
||||
1. 扩展性
|
||||
2. 安全性
|
||||
3. 路由策略
|
||||
|
||||
BGP对应上述问题衍生出的特点:
|
||||
1. 触发式增量更新
|
||||
2. 可靠连接(TCP)
|
||||
3. 没有度量(Metric)的概念,采用__属性(Attribute)__来实现选路功能,并且有一套非常强大的选路策略工具集。
|
||||
TCP 179端口作为通信端口,滑动窗口,三次握手。
|
||||
|
||||
三次握手来建立连接:
|
||||
1. 源设备向目标设备发送一个TCP的SYN(S————D:SYN)
|
||||
2. 目标设备向源设备反回一个TCP的SYN ACK(d————S:SYN ACK)
|
||||
3. 源再向目标设备反馈一个TCP的ACK(S————D:ACK)
|
||||
传输数据
|
||||
|
||||
四次握手来终止连接:
|
||||
1. 源向目标方发送一个FIN(S————D:FIN)
|
||||
2/3.分别目标设备向源设备发送一个ACK/FIN(D——S:ACK/FIN)
|
||||
4. 源设备向目标设备发送一个ACK(S——D:ACK)
|
||||
连接终止。
|
||||
|
||||
如果收到一个TCP带有RST字段的时候,强制将这条TCP给切断
|
||||
需要一种保活机制来维护TCP连接通路。这种机制信息叫keepalive
|
||||
|
||||
客户和ISP连接方式:
|
||||
1. 单宿主(single-homed):一般使用私有AS号,使用PA地址。
|
||||
2. 多宿主(multi-homed):一般使用公有AS号,一般使用PI地址
|
||||
|
||||
AS范围:1-65535
|
||||
其中1-64511:公有AS号(public):必须向InterNIC去买,由IANA分配,公网可见。
|
||||
64512-65535:私有AS号(private):无须购买,可由ISP提供,公网不可见。
|
||||
|
||||
公网IP地址:
|
||||
1. PI(__运营商独立地址__):与ISP无关,直接向地址注册机构购买的。
|
||||
2. PA(运营商分配地址)与ISP相关,由ISP租赁给你。
|
||||
|
||||
客户AS——__ISP传输AS__——客户AS
|
||||
|
||||
BGP信息:
|
||||
1. Open:用来建立BGP会话
|
||||
2. Keepalive:用来维护TCP连接
|
||||
3. Update:用来__承载BGP路由信息__,每一个BGP更新都会相应的附带一些__BGP属性__。
|
||||
4. Notification:当BGP检测到错误之后,用于切断BGP会话。
|
||||
|
||||
BGP路由器鉴别机制:BGP Router-ID(RID):
|
||||
1. 如果有逻辑接口,逻辑接口IP地址最大的IP地址作为RID
|
||||
2. 如果只有物理接口,物理接口IP地址最大的IP地址作为RID
|
||||
|
||||
BGP邻居关系的建立:
|
||||
1. 首先,当BGP路由器之间准备建立TCP连接,此时,BGP邻居关系的状态称之为空闲状态(idle)
|
||||
2. 当TCP连接建立完成之后,此时BGP邻居关系的状态称之为活跃状态(active)
|
||||
3. 当TCP双向连接建立之后,BGP路由器发送open包,open包包含的参数包含:AS号,keepalive计时器,RID等,这个状态叫做OpenSent状态
|
||||
4. 对方给予open包,这个状态叫做OpenConfirm状态
|
||||
5. 如果双方Open包参数协商成功,那么__BGP邻居(会话)关系__建立,达到 Established状态,如果不成功,可能回退到Active状态,再重新发送Open包,试图建立BGP会话。
|
||||
3和4在CISCO IOS的show命令里是看不到的,统称为active状态。
|
||||
|
||||
**1、** **AS-PATH属性**:AS-PATH属性越短,路径越优先。
|
||||
**2、** **起源(Origin)属性**:定义BGP更新是如何产生的:
|
||||
(1)、起源于IGP:BGP更新是通过BGP进程下network命令所发布出来的。(i)
|
||||
(2)、起源于EGP:BGP更新是通过__EGP重分发__进来的。(e)
|
||||
(3)、起源不完整(incomplete):BGP更新是通过IGP(OSPF,EIGRP等)重分发进来的。(?)
|
||||
如果同一个BGP更新有3种起源方式。IGP优先于EGP优先Incomplete
|
||||
|
||||
**3、下一跳(NEXT_HOP)属性**:到达目标AS的下一个AS(**IP地址**)。
|
||||
|
||||
BGP连接关系:
|
||||
1.内部BGP(Internal BGP,IBGP)
|
||||
建立BGP邻居关系的BGP路由器位于同一自治系统。对于IBGP邻居,AS-PATH属性是不变,下一跳属性也不变。
|
||||
2.外部BGP(External BGP,EBGP)
|
||||
建立BGP邻居关系的BGP路由器位于不同自治系统。对于EBGP,AS_PATH属性根据经过的AS号码向前叠加,下一跳属性也会改变。对于EBGP而言:下一跳属性是宣告该EBGP更新的接口IP地址。
|
||||
|
||||
**知名任意属性:**
|
||||
1、本地优先级(Local_pref):默认值100。用于__同一个AS多出口的选路控制__,值超高,也就越优先。本地优先级只在AS内比较(只在IBGP邻居间比较),用于__影响出站流量__。 本地产生的为空。
|
||||
2、Atomic aggregate:用于通知邻居AS,说明执行汇总路由器的路由信息。地址信息以RID呈现。
|
||||
**可选传递属性:**
|
||||
1. Aggregator:定义了实行路由聚合(汇总)的路由器的IP地址AS号信息,地址信息是接口信息。
|
||||
2. 社团属性(community):用于BGP的路由标记(打标签)。
|
||||
**可选非传递:**
|
||||
1. MED:默认值为0,值越低越优先,用于不同AS多出口的选路比较。用于__影响进站流量__。默认情况下,来自不同AS的MED属性不能相互比较。如果MED属性为空(NULL),CISCO设备把空值当成0,其他厂商把空值当成无穷大。CISCO私有属性:权重(0-65535)
|
||||
1. 本一产生的BGP更新权重默认是32768
|
||||
2. 从邻居学过来的BGP更新权重默认为0,值越高,越优先。权重本地有效,不传递。影响出站流量。
|
||||
|
||||
|
||||
BGP的选路法则:(必须要下一跳可达)
|
||||
如果一个BGP更新同时通过多个邻居学习到,就要进行一系列的选路比较法则。过程如下:
|
||||
1. 先比较权重,权重高的越优先。(只对本地有效,只能影响自己不能影响别人)
|
||||
2. 比较本地优先级,越高越优先,仅在同一个AS内的IBGP邻居比较。
|
||||
3. 本地产生(下一跳属性为0.0.0.0)的优先于从邻居学过来的。
|
||||
4. AS-PATH长度最短。(BGP:路径矢量协议)
|
||||
5. 比较起源属性:IGP优先于EGP优先于Incomlete
|
||||
6. 比较MED属性,越低越优先。
|
||||
7. EBGP(AD=20)优先于IBGP(AD=200)
|
||||
-------------------------------------------------分水岭------------------------------------------------------------
|
||||
8a.如果是EBGP更新,那么最老(最先被学习到的)的EBGP更新更优先
|
||||
8b.如果是IBGP更新,离邻居IGP度量值最小的路径优先。
|
||||
-------------------------------------------------分水岭------------------------------------------------------------
|
||||
9.比较BGP RID,越低越优先。
|
||||
10.比较学习到BGP更新的接口地址,接口IP地址越小越优先。
|
||||
|
||||
402
Zim/job/BGP集训营/第二天BGP对等体的分类.txt
Normal file
@@ -0,0 +1,402 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-04T10:34:08+08:00
|
||||
|
||||
====== 第二天BGP对等体的分类 ======
|
||||
Created Tuesday 04 September 2012
|
||||
|
||||
BGP分类:
|
||||
1. EBGP
|
||||
2. IBGP
|
||||
|
||||
BGP邻居关系就是对等体关系; BGP邻居就是BGP对等体(peer)
|
||||
__BGP对等体关系是手动建立__。信息交换(BGP更新)也是手动化的。
|
||||
|
||||
EBGP对等体关系:
|
||||
默认情况下,__EBGP建立对等体关系必须物理直连__(必须使用物理接口来建立)。原因:
|
||||
1. BGP会对信息的发送源进行校验:你指定的BGP对等体地址和BGP信息发送源接口必须匹配。默认情况下,所有BGP信息的发送源接口都是出站物理接口。
|
||||
2. 默认情况下,EBGP信息TTL值为1。
|
||||
|
||||
IBGP对等体关系:
|
||||
默认情况下,IBGP建立对等体关系**不必**物理直连。原因:
|
||||
1. BGP会对信息的发送源进行校验:你指定的BGP对等体地址和BGP信息发送源接口必须匹配。默认情况下,所有BGP信息的发送源接口都是出站物理接口。
|
||||
2. 默认情况下,IBGP信息TTL值为64。
|
||||
|
||||
IBGP是靠IGP的快速收敛来达到快速收敛。IBGP用于传输AS承载外部(客户)EBGP更新。传输AS内的IGP:
|
||||
1.负责建立IBGP对等体关系(TCP连接)
|
||||
2.负责帮助IBGP对等体的快速收敛。
|
||||
|
||||
EBGP可以做到防止环路; IBGP可以通过__水平分割__防止环路; 通过IBGP对等体学习到的BGP更新不会再宣告经任意的IBGP对等体。
|
||||
下一跳不可达问题(重分发,指定下一跳)
|
||||
递归解析
|
||||
BGP和IGP的相互的
|
||||
BGP的基本配置
|
||||
|
||||
R0的基本配置:
|
||||
Router>en
|
||||
Router#conf t
|
||||
Enter configuration commands, one per line. End with CNTL/Z.
|
||||
Router (config)#host R0
|
||||
R0(config)#no ip do lo
|
||||
R0(config)#line con 0
|
||||
R0(config-line)#logg syn
|
||||
R0(config-line)#exec-timeout 0 0
|
||||
R0(config-line)#exit
|
||||
R0(config)#
|
||||
R0(config)#int lo0
|
||||
R0(config-if)#ip add 1.1.1.1 255.255.255.0
|
||||
R0(config-if)#no shut
|
||||
R0(config-if)#exit
|
||||
R0(config)#int fa0/0
|
||||
R0(config-if)#ip add 192.168.1.1 255.255.255.0
|
||||
R0(config-if)#no shut
|
||||
R0(config-if)#exit
|
||||
R0(config)#int e1/0
|
||||
R0(config-if)#ip add 192.168.2.1 255.255.255.0
|
||||
R0(config-if)#no shut
|
||||
R0(config-if)#exit
|
||||
R0(config)#router bgp 1[lty1]
|
||||
R0(config-router)#neighbor 192.168.1.2 remote-as 1[lty2]
|
||||
R0(config-router)#neighbor 192.168.2.2 remote-as 1
|
||||
Router>en
|
||||
Router#conf t
|
||||
Enter configuration commands, one per line. End with CNTL/Z.
|
||||
Router(config)#host R1
|
||||
R1(config)#no ip do lo
|
||||
R1(config)#line con 0
|
||||
R1(config-line)#logg syn
|
||||
R1(config-line)#exec-timeout 0 0
|
||||
R1(config-line)#exit
|
||||
R1(config)#
|
||||
R1(config)#int lo0
|
||||
R1(config-if)#ip add 2.2.2.2 255.255.255.0
|
||||
R1(config-if)#no shut
|
||||
R1(config-if)#exit
|
||||
R1(config)#int fa0/0
|
||||
R1(config-if)#ip add 192.168.1.2 255.255.255.0
|
||||
R1(config-if)#no shut
|
||||
R1(config-if)#exit
|
||||
R1(config)#int e1/0
|
||||
R1(config-if)#ip add 192.168.2.2 255.255.255.0
|
||||
R1(config-if)#no shut
|
||||
R1(config-if)#exit
|
||||
R1(config)#router bgp 1
|
||||
R1(config-router)#neighbor 192.168.1.1 remote-as 1
|
||||
R1(config-router)#neighbor 192.168.2.1 remote-as 1
|
||||
R1(config-router)#end
|
||||
R1#
|
||||
在R0或R1上查看BGP的表项
|
||||
R0#sh ip bgp
|
||||
R0#
|
||||
如果为空,表示没有BGP路由表项
|
||||
BGP network命令:表示将某些IP前缀以手动的形式向对等体宣告出去。被发布的IP前缀的掩码必须和network命令精确匹配。
|
||||
现在将我的1.1.1.0宣告出去。
|
||||
R0(config)#router bgp 1
|
||||
R0(config-router)#network 1.1.1.0 mask 255.255.255.0[lty3]
|
||||
R0(config-router)#end
|
||||
R0#
|
||||
现在去对方的BGP表项中看看有没有内容:
|
||||
R1#sh ip bgp
|
||||
BGP table version is 2[lty4] , local router ID is 2.2.2.2[lty5]
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
|
||||
r RIB-failure[刘廷友6] , S Stale
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
* i1.1.1.0/24 192.168.2.1 0 100 0 i
|
||||
*>i 192.168.1.1 0 100 0 i
|
||||
R1#
|
||||
已经有了
|
||||
再看看R1上面的路由表:
|
||||
R1#sh ip rout
|
||||
Codes: C - connected, S - static, R - RIP, M - mobile, B - BGP
|
||||
D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
|
||||
N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
|
||||
E1 - OSPF external type 1, E2 - OSPF external type 2
|
||||
i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
|
||||
ia - IS-IS inter area, * - candidate default, U - per-user static route
|
||||
o - ODR, P - periodic downloaded static route
|
||||
Gateway of last resort is not set
|
||||
1.0.0.0/24 is subnetted, 1 subnets
|
||||
B 1.1.1.0 [200/0] via 192.168.1.1, 00:05:10
|
||||
[lty7] 2.0.0.0/24 is subnetted, 1 subnets
|
||||
C 2.2.2.0 is directly connected, Loopback0
|
||||
C 192.168.1.0/24 is directly connected, FastEthernet0/0
|
||||
C 192.168.2.0/24 is directly connected, Ethernet1/0
|
||||
R1#
|
||||
现在通过递归查找看一下1.1.1.0这条路由通过那个接口出去。
|
||||
R1#sh ip route 1.1.1.0
|
||||
Routing entry for 1.1.1.0/24
|
||||
Known via "bgp 1", distance 200, metric 0, type internal
|
||||
Last update from 192.168.1.1 00:06:56 ago
|
||||
Routing Descriptor Blocks:
|
||||
* 192.168.1.1, from 192.168.1.1, 00:06:56 ago[lty8]
|
||||
Route metric is 0, traffic share count is 1
|
||||
AS Hops 0
|
||||
R1#sh ip route 192.168.1.1
|
||||
Routing entry for 192.168.1.0/24
|
||||
Known via "connected", distance 0, metric 0 (connected, via interface)
|
||||
Routing Descriptor Blocks:
|
||||
* directly connected, via FastEthernet0/0
|
||||
Route metric is 0, traffic share count is 1
|
||||
R1#
|
||||
分析BGP表:
|
||||
R1#sh ip bgp
|
||||
BGP table version is 2, local router ID is 2.2.2.2
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
|
||||
r RIB-failure, S Stale
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete[lty9]
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
*[lty10] i[lty11] 1.1.1.0/24 192.168.2.1 [lty12] 0[lty13] 100[lty14] 0 [lty15] i[lty16]
|
||||
*>[lty17] i 192.168.1.1 0 100 0 i
|
||||
R1#
|
||||
如果要更详细的看某条路由:
|
||||
R1#show ip bgp 1.1.1.0
|
||||
BGP routing table entry for 1.1.1.0/24, version 2
|
||||
Paths: (2 available, best #2, table Default-IP-Routing-Table)
|
||||
Not advertised to any peer
|
||||
Local
|
||||
192.168.2.1 from 192.168.2.1 (1.1.1.1)
|
||||
Origin IGP, metric 0, localpref 100, valid, internal
|
||||
Local
|
||||
192.168.1.1 from 192.168.1.1 (1.1.1.1)
|
||||
Origin IGP, metric 0, localpref 100, valid, internal, best
|
||||
R1#
|
||||
现在再去R0上面看看BGP表:
|
||||
R0#sh ip bgp
|
||||
BGP table version is 2, local router ID is 1.1.1.1
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
|
||||
r RIB-failure, S Stale
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
*> [lty18] 1.1.1.0/24 0.0.0.0[lty19] 0 [lty20] 32768[lty21] i
|
||||
这就是两张BGP表不同之处。
|
||||
将前面BGP的配置删除了。
|
||||
R0(config)#no router bgp 1
|
||||
R1(config)#no router bgp 1
|
||||
现在我们通过回环接口来建立BGP邻居
|
||||
R0(config)#router bgp 1
|
||||
R0(config-router)#neighbor 2.2.2.2 remote-as 1
|
||||
R0(config-router)#end
|
||||
R1(config)#router bgp 1
|
||||
R1(config-router)#neighbor 1.1.1.1 remote-as 1
|
||||
现在查看一下BGP邻居表
|
||||
R0#sh ip bgp summary
|
||||
BGP router identifier 1.1.1.1, local AS number 1
|
||||
BGP table version is 1, main routing table version 1
|
||||
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
|
||||
2.2.2.2 4 1 0 0 0 0 0 never Active
|
||||
R0#
|
||||
看看为什么
|
||||
R0(config)#router bgp 1
|
||||
R0(config-router)#nei
|
||||
R0(config-router)#neighbor 2.2.2.2 up
|
||||
R0(config-router)#neighbor 2.2.2.2 update-source lo0
|
||||
R0(config-router)#end
|
||||
R1(config-router)#neighbor 1.1.1.1 up
|
||||
R1(config-router)#neighbor 1.1.1.1 update-source lo0
|
||||
R1(config-router)#end
|
||||
现在看看BGP邻居表:
|
||||
R0#sh ip bgp summary
|
||||
BGP router identifier 1.1.1.1, local AS number 1
|
||||
BGP table version is 1, main routing table version 1
|
||||
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
|
||||
2.2.2.2 4 1 0 0 0 0 0 never Active
|
||||
R0#
|
||||
看看为什么这时候他的状态还是active呢?
|
||||
这是因为我们R0上面没有2.2.2.0/24的路由。所以建立不起来邻居。要建立邻居我们就需要有2.2.2.0/24的路由,所以这里我们起一个ospf协议,以保障我们BGP邻居的正常建立。
|
||||
R0(config-router)#net 192.168.1.0 0.0.0.255 a 0
|
||||
R0(config-router)#net 192.168.2.0 0.0.0.255 a 0
|
||||
R0(config-router)#net 1.1.1.0 0.0.0.255 a 0
|
||||
R0(config-router)#exit
|
||||
R1(config)#router ospf 100
|
||||
R1(config-router)#net 192.168.1.0 0.0.0.255 a 0
|
||||
R1(config-router)#net 192.168.2.0 0.0.0.255 a 0
|
||||
R1(config-router)#net 2.2.2.0 0.0.0.255 a 0
|
||||
R1(config-router)#exit
|
||||
现在先看看OSPF协议运行起来没有!在R0上面查看一下路由表!看看有没有2.2.2.0/24路由没有。
|
||||
R0#sh ip route
|
||||
Codes: C - connected, S - static, R - RIP, M - mobile, B - BGP
|
||||
D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
|
||||
N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
|
||||
E1 - OSPF external type 1, E2 - OSPF external type 2
|
||||
i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
|
||||
ia - IS-IS inter area, * - candidate default, U - per-user static route
|
||||
o - ODR, P - periodic downloaded static route
|
||||
Gateway of last resort is not set
|
||||
1.0.0.0/24 is subnetted, 1 subnets
|
||||
C 1.1.1.0 is directly connected, Loopback0
|
||||
2.0.0.0/32 is subnetted, 1 subnets
|
||||
O 2.2.2.2 [110/2] via 192.168.1.2, 00:03:30, FastEthernet0/0
|
||||
[lty22] C 192.168.1.0/24 is directly connected, FastEthernet0/0
|
||||
C 192.168.2.0/24 is directly connected, Ethernet1/0
|
||||
R0#
|
||||
现在再来看看我们的BGP的邻居建立起来没有?
|
||||
R0#sh ip bgp summary
|
||||
BGP router identifier 1.1.1.1, local AS number 1
|
||||
BGP table version is 1, main routing table version 1
|
||||
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
|
||||
2.2.2.2 4 1 7 7 1 0 0 00:04:03 0
|
||||
R0#
|
||||
从这里我们可以看见!BGP邻居已经建立起来了!通过回环口来建立邻居,邻居就只有一个了。
|
||||
现在将这两台路由器变成EBGP邻居关系。
|
||||
现在将两台路由器上面的BGP删除了
|
||||
R0(config)#no router bgp 1
|
||||
R1(config)#no router bgp 1
|
||||
现在建立EBGP邻居
|
||||
R0(config)#router bgp 1
|
||||
R0(config-router)#nei
|
||||
R0(config-router)#neighbor 192.168.2.2 remote-as 1
|
||||
R0(config-router)#neighbor 192.168.1.2 remote-as 2
|
||||
R0(config-router)#
|
||||
*Mar 1 00:59:27.563: %BGP-5-ADJCHANGE: neighbor 192.168.1.2 Up
|
||||
R0(config-router)#network
|
||||
R0(config-router)#network
|
||||
% Incomplete command.
|
||||
R0(config-router)#net
|
||||
R0(config-router)#network 1.1.1.0 mask 255.255.255.0
|
||||
R0(config-router)#end
|
||||
R0#
|
||||
R1(config)#router bgp 2
|
||||
R1(config-router)#nei
|
||||
R1(config-router)#neighbor 192.168.1.1 remote
|
||||
R1(config-router)#neighbor 192.168.1.1 remote-as 1
|
||||
R1(config-router)#end
|
||||
现在我们查看一下BGP的邻居表:
|
||||
R0#sh ip bgp summary
|
||||
BGP router identifier 1.1.1.1, local AS number 1
|
||||
BGP table version is 2, main routing table version 2
|
||||
1 network entries using 117 bytes of memory
|
||||
1 path entries using 52 bytes of memory
|
||||
2/1 BGP path/bestpath attribute entries using 248 bytes of memory
|
||||
0 BGP route-map cache entries using 0 bytes of memory
|
||||
0 BGP filter-list cache entries using 0 bytes of memory
|
||||
BGP using 417 total bytes of memory
|
||||
BGP activity 1/0 prefixes, 1/0 paths, scan interval 60 secs
|
||||
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
|
||||
192.168.1.2 4 2 9 10 2 0 0 00:06:19 0
|
||||
192.168.2.2 4 2 5 5 2 0 0 00:00:08 0
|
||||
R0#
|
||||
现在查看一下BGP表:
|
||||
R1#sh ip bgp
|
||||
BGP table version is 2, local router ID is 2.2.2.2
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
|
||||
r RIB-failure, S Stale
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
* 1.1.1.0/24 192.168.2.1 0 0 1 i
|
||||
*> 192.168.1.1 0 0 1 i
|
||||
R1#
|
||||
看看为什么这里会有两条BGP表项呢?
|
||||
现在我们通过回环回来建立一下EBGP邻居:
|
||||
R0(config)#no router bgp 1
|
||||
R0(config)#router bgp 1
|
||||
R0(config-router)#nei
|
||||
R0(config-router)#neighbor 2.2.2.2 remote
|
||||
R0(config-router)#neighbor 2.2.2.2 remote-as 2
|
||||
R0(config-router)#nei
|
||||
R0(config-router)#neighbor 2.2.2.2 up
|
||||
R0(config-router)#neighbor 2.2.2.2 update-source lo0
|
||||
R0(config-router)#nei
|
||||
R0(config-router)#neighbor 2.2.2.2 e
|
||||
R0(config-router)#neighbor 2.2.2.2 ebgp-multihop
|
||||
R0(config-router)#net
|
||||
R0(config-router)#network 1.1.1.1
|
||||
R0(config-router)#no network 1.1.1.1
|
||||
R0(config-router)#net
|
||||
R0(config-router)#network 1.1.1.0 mask 255.255.255.0
|
||||
R0(config-router)#end
|
||||
R0#
|
||||
R1(config)#no router bgp 2
|
||||
R1(config)#
|
||||
*Mar 1 01:08:41.339: %BGP-5-ADJCHANGE: neighbor 192.168.1.1 Down BGP protocol initialization
|
||||
*Mar 1 01:08:41.343: %BGP-5-ADJCHANGE: neighbor 192.168.2.1 Down BGP protocol initialization
|
||||
R1(config)#router bgp
|
||||
R1(config)#router bgp 2
|
||||
R1(config-router)#nei
|
||||
R1(config-router)#neighbor 1.1.1.1 re
|
||||
R1(config-router)#neighbor 1.1.1.1 remot
|
||||
R1(config-router)#neighbor 1.1.1.1 remote-as 1
|
||||
R1(config-router)#nei
|
||||
R1(config-router)#neighbor 1.1.1.1 up
|
||||
R1(config-router)#neighbor 1.1.1.1 update-source lo0
|
||||
R1(config-router)#nei
|
||||
R1(config-router)#neighbor 1.1.1.1 e
|
||||
R1(config-router)#neighbor 1.1.1.1 ebgp-multihop
|
||||
R1(config-router)#nei
|
||||
R1(config-router)#neighbor 2.2.2.0 mask 255.255.255.0
|
||||
^
|
||||
% Invalid input detected at '^' marker.
|
||||
R1(config-router)#
|
||||
*Mar 1 01:10:47.403: %BGP-5-ADJCHANGE: neighbor 1.1.1.1 Up
|
||||
R1(config-router)#network
|
||||
R1(config-router)#network 2.2.2.0 mask 255.255.255.0
|
||||
R1(config-router)#end
|
||||
R1#
|
||||
现在查看一下BGP邻居:
|
||||
R1#sh ip bgp summary
|
||||
BGP router identifier 2.2.2.2, local AS number 2
|
||||
BGP table version is 3, main routing table version 3
|
||||
2 network entries using 234 bytes of memory
|
||||
2 path entries using 104 bytes of memory
|
||||
3/2 BGP path/bestpath attribute entries using 372 bytes of memory
|
||||
1 BGP AS-PATH entries using 24 bytes of memory
|
||||
0 BGP route-map cache entries using 0 bytes of memory
|
||||
0 BGP filter-list cache entries using 0 bytes of memory
|
||||
BGP using 734 total bytes of memory
|
||||
BGP activity 2/0 prefixes, 2/0 paths, scan interval 60 secs
|
||||
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
|
||||
1.1.1.1 4 1 5 5 3 0 0 00:01:59 1
|
||||
R1#
|
||||
在再看一下BGP表
|
||||
R1#sh ip bgp
|
||||
BGP table version is 3, local router ID is 2.2.2.2
|
||||
Status codes: s suppressed, d damped, h history, * valid, > best, i - internal,
|
||||
r RIB-failure, S Stale
|
||||
Origin codes: i - IGP, e - EGP, ? - incomplete
|
||||
Network Next Hop Metric LocPrf Weight Path
|
||||
*> 1.1.1.0/24 1.1.1.1 0 0 1 i
|
||||
*> 2.2.2.0/24 0.0.0.0 0 32768 i
|
||||
R1#
|
||||
log-adjacency-changes这条命令从我们运行BGP路由协议以后,自动会运行这条命令。用于向用户反映BGP事件
|
||||
BGP同步:BGP路由器不会把自己通过IBGP对等到体学习到的外部BGP更新(EBGP更新)宣告给自己的EBGP对等体,除非这台BGP路由器已经通过IGP学习到该外部EBGP路由。
|
||||
BGP表的IP前缀进入IP路由表的条件:下一跳可达。这条BGP表就会复制到路由表里面。
|
||||
--------------------------------------------BGP原理已完-------------------------------------------------------
|
||||
BGP选路策略:
|
||||
1. BGP过滤
|
||||
2. BGP选路控制
|
||||
IP前缀列表(prefix list)vs. IP ACL
|
||||
例如:
|
||||
Access-list 1 permit 192.168.1.0 0.0.0.255 (192.168.1.0/24)
|
||||
Ip prefix-list mybgp[lty23] seq 5 [lty24] permit 192.168.1.0/24 (192.168.1.0/24)
|
||||
前缀列表里面有一个
|
||||
把B类地址中主机数量有254的子网全部给挑出来。
|
||||
Ip prefix-list my permiy 128.0.0.0/2 ge[lty25] 24 le[lty26] 24[lty27]
|
||||
正则表达式(regular expression,regex)
|
||||
[lty1]启动BGP路由协议
|
||||
[lty2]指定邻居,后面的remote-as是指AS号,如果这个AS号与自己BGP后面的AS号相同的话表示是IBGP邻居,如果两个AS号表示是EBGP邻居
|
||||
[lty3]宣告网段,在这被发布的IP前缀的掩码必须和network命令精确匹配。
|
||||
[lty4]BGP表的版本号,每一次更新了路由信息时这个版本号就会向上加1
|
||||
[lty5]本地的RID号
|
||||
[刘廷友6]一般由于地址冲突带来的故障,一般对通信不会产生影响。
|
||||
[lty7]这一条就是通过BGP学习过来的!
|
||||
在这里要注意看其它的直连路由都有出站接口,而这条BGP路由没有出站接口。
|
||||
[lty8]
|
||||
[lty9]起源代码
|
||||
[lty10]*表示有效的
|
||||
[lty11]表示这个条目是通过IBGP或EBGP学习过来的
|
||||
[lty12]下一跳
|
||||
[lty13]MED
|
||||
[lty14]本地优先级
|
||||
[lty15]权重
|
||||
[lty16]I表示起源于IGP
|
||||
[lty17]这个条目作为bast最佳的,并放入路由表
|
||||
[lty18]这里没有i,表示自己产生的
|
||||
[lty19]自己产生的下一跳是 0.0.0.0
|
||||
[lty20]自己产生的本地优先级为空
|
||||
[lty21]自己产生的权重是32768
|
||||
[lty22]我们可以看见,这里OSPF已经运行起来了!2.2.2.0已经学到了!
|
||||
[lty23]命令
|
||||
[lty24]序列号,默认是5
|
||||
[lty25]大于等于
|
||||
[lty26]小于等于
|
||||
[lty27]这样写表示等于24
|
||||
65
Zim/job/OSPF动态路由协议基本工作原理.txt
Normal file
@@ -0,0 +1,65 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-03T20:42:27+08:00
|
||||
|
||||
====== OSPF动态路由协议基本工作原理 ======
|
||||
Created Monday 03 September 2012
|
||||
http://net.zdnet.com.cn/network_security_zone/2010/0414/1705709.shtml
|
||||
|
||||
随着Internet技术在全球范围内的飞速发展,IP网络作为一种最有前景的网络技术,受到了人们的普遍关注。而作为IP网络生存、运作、组织的核心——IP路由技术提供了解决IP网络动态可变性、实时性、QoS等关键技术的一种可能。在众多的路由技术中,OSPF协议已成为目前Internet广域网和Intranet企业网采用最多、应用最广泛的路由技术之一。本文在分析OSPF动态路由协议基本工作原理的基础上,提出了Dijkstra算法和OSPF路由表计算的实现方法。
|
||||
|
||||
目前应用较多的路由协议有RIP和OSPF,它们同属于__内部网关协议__,但RIP基于__距离矢量算法__,而OSPF基于链路状态的__最短路径优先算法__。它们在网络中利用的传输技术也不同。
|
||||
|
||||
RIP是利用__UDP的520号端口__进行传输,实现中利用套接口编程,而OSPF则直接在IP上进行传输,它的协议号为89.
|
||||
|
||||
在RIP当中,所有的路由都由__跳数__来描述,到达目的地的路由最大不超过16跳,且只保留__唯一__的一条路由,这就限制了RIP的__服务半径__,即其只适用于小型的简单网络。同时,运行RIP的路由器需要定期地(一般30s)将自己的路由表__广播__到网络当中,达到对网络拓扑的聚合,这样不但聚合的速度慢而且极容易引起广播风暴、累加到无穷、路由环致命等问题。为此,OSPF应运而生。OSPF是基于链路状态的路由协议,它克服了RIP的许多缺陷:
|
||||
|
||||
第一,OSPF不再采用跳数的概念,而是根据接口的吞吐率、拥塞状况、往返时间、可靠性等实际链路的负载能力定出__路由代价__,同时选择最短、最优路由并允许保持到达同一目标地址的__多条路由__,从而平衡网络负荷;
|
||||
第二,OSPF支持不同服务类型的不同代价,从而实现__不同QoS的路由服务;__
|
||||
第三,OSPF路由器不再交换路由表,而是同步各路由器对网络状态的认识,即__链路状态数据库__,然后通过Dijkstra最短路径算法计算出网络中各目的地址的最优路由。
|
||||
|
||||
这样OSPF路由器间不需要定期地交换大量数据,而只是保持着一种连接,一旦有链路状态发生变化时,才通过__组播方式__对这一变化做出反应,这样不但减轻了不参与系统的负荷而且达到了对网络拓扑的快速聚合。而这些正是OSPF强大生命力和应用潜力的根本所在。
|
||||
|
||||
一、OSPF工作原理分析
|
||||
|
||||
OSPF是一种__分层次__的路由协议,其层次中最大的实体是__AS(自治系统)__,即遵循**共同路由策略**管理下的一部分网络实体。在每个AS中,将网络划分为不同的__区域__。每个区域都有自己特定的标识号。对于__主干(backbone)区域__,负责在区域之间分发链路状态信息。这种分层次的网络结构是根据OSPF的实际提出来的。当网络中自治系统非常大时,网络拓扑数据库的内容就更多,所以如果不分层次的话,一方面容易造成数据库溢出,另一方面当网络中某一链路状态发生变化时,会引起整个网络中每个节点都重新计算一遍自己的路由表,既浪费资源与时间,又会影响路由协议的性能(如聚合速度、稳定性、灵活性等)。因此,需要把自治系统划分为多个域,__每个域内部维持本域一张唯一的拓扑结构图__,且各域根据自己的拓扑图各自计算路由,__域边界路由器把各个域的内部路由总结后在域间扩散。__这样,当网络中的某条链路状态发生变化时,此链路所在的域中的每个路由器重新计算本域路由表,而其它域中路由器只需**修改其路由表中的相应条目**而无须重新计算整个路由表,节省了计算路由表的时间。
|
||||
|
||||
OSPF由两个互相关联的主要部分组成:__“呼叫”协议和“可靠泛洪”机制__。**呼叫协议检测**__邻居__**并维护**__邻接__**关系,可靠泛洪算法可以确保统一域中的所有的OSPF路由器始终具有一致的链路状态数据库,而该数据库构成了对域的网络拓扑和链路状态的映射。**链路状态数据库中每个条目称为__LSA(链路状态通告)__,共有5种不同类型的LSA,路由器间交换信息时就是交换这些LSA. 每个路由器都维护一个用于跟踪网络链路状态的数据库,然后各路由器的路由选择就是基于链路状态,通过Dijkastra算法建立起来最短路径树,用该树跟踪系统中的每个目标的最短路径。**最后再通过计算域间路由、自治系统外部路由确定完整的路由表。**与此同时,OSPF动态监视网络状态,一旦发生变化则迅速扩散达到对网络拓扑的快速聚合,从而确定出新的网络路由表。
|
||||
|
||||
OSPF的设计实现要涉及到**指定路由器**、备份指定路由器的选举、协议包的接收、发送、泛洪机制、路由表计算等一系列问题。本文仅就Dijkstra算法与路由表的计算进行讨论。
|
||||
|
||||
二、Dijkstra算法
|
||||
|
||||
Dijkstra算法是路由表计算的依据,通过Dijkstra算法可以得到有关网络节点的最短路径树,然后由最短路径优先树得到路由表。
|
||||
|
||||
1.Dijkstra算法的描述如下:
|
||||
|
||||
(1)初始化集合E,使之只包含源节点S,并初始化集合R,使之包含所有其它节点。初始化路径列O,使其包含一段从S起始的路径。这些路径的长度值等于相应链路的量度值,并以递增顺序排列列表O.
|
||||
|
||||
(2)若列表O为空,或者O中第1个路径长度为无穷大,则将R中所有剩余节点标注为不可达,并终止算法。
|
||||
|
||||
(3)首先寻找列表O中的最短路径P,从O中删除P.设V为P的最终节点。若V已在集合E中,继续执行步骤2.否则,P为通往V的最短路径。将V从R移至E.
|
||||
|
||||
(4)建立一个与P相连并从V开始的所有链路构成的侯选路径集合。这些路径的长度是P的长度加上与P相连的长度。将这些新的链路插入有序表O中,并放置在其长度所对应的等级上。继续执行步骤2.
|
||||
|
||||
2.Dijkstra算法举例:
|
||||
|
||||
下面我们以路由器A为例,来说明最短路径树的建立过程:
|
||||
|
||||
(1)路由器A找到了路由器B、C,将它们列入候选列表.
|
||||
(2)从候选列表中找出最小代价项B,将B加入最短路径树并从候选列表中删除。接着从B开始寻找,找到了D,将其放入候选列表.
|
||||
(3)从列表中找出C,再由C又找到了D.但此时D的代价为4,所以不再加入候选列表。最后将D加入到最短路径树。此时候选列表为空,完成了最短路径树的计算。
|
||||
|
||||
三、OSPF路由表的计算与实现
|
||||
|
||||
有关路由表的计算是OSPF的核心内容,它是动态生成路由器内核路由表的基础。在路由表条目中,应包括有目标地址、目标地址类型、链路的代价、链路的存活时间、链路的类型以及下一跳等内容。关于整个计算的过程,主要由以下五个步骤来完成:
|
||||
|
||||
(1)保存当前路由表,当前存在的路由表为无效的,必须从头开始重新建立路由表;
|
||||
(2)域内路由的计算,通过Dijkstra算法建立最短路径树,从而计算域内路由;
|
||||
(3)域间路由的计算,通过检查Summary-LSA来计算域间路由,若该路由器连到多个域,则只检查主干域的Summary-LSA;
|
||||
(4)查看Summary-LSA:在连到一个或多个传输域的域边界路由器中,通过检查该域内的Summary-LSA来检查是否有比第(2)(3)步更好的路径;
|
||||
(5)AS外部路由的计算,通过查看AS-External-LSA来计算目的地在AS外的路由。
|
||||
|
||||
通过以上步骤,OSPF生成了路由表。但这里的路由表还不同于路由器中实现路由转发功能时用到的内核路由表,它只是OSPF本身的内部路由表。因此,完成上述工作后,往往还要通过路由增强功能与内核路由表交互,从而实现多种路由协议的学习。
|
||||
|
||||
OPSF作为一种重要的内部网关协协议的普遍应用,极大地增强了网络的可扩展性和稳定性,同时也反映出了动态路由协议的强大功能。但是,在有关OSPF协议的研究、实现中尚存在一些问题,如数据库的溢出、度量的刻画、以及MTU协商等等。同时,在IPv6中,OSPFv3基于链路的处理机制、IP地址的变化、泛洪范围的增加、包格式、LSA的变化以及邻居的识别等技术都将是我们共同探讨的课题。
|
||||
160
Zim/job/OSPF路由协议综述及其配置.txt
Normal file
@@ -0,0 +1,160 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-03T19:57:11+08:00
|
||||
|
||||
====== OSPF路由协议综述及其配置 ======
|
||||
Created Monday 03 September 2012
|
||||
|
||||
链路状态路由协议(link-state routing protocol)的一些特征:
|
||||
|
||||
1.对网络发生的变化能够快速响应
|
||||
2.当网络发生变化的时候发送**触发式更新**(triggered update)
|
||||
3.发送周期性更新(**链路状态刷新**),间隔时间为30分钟
|
||||
|
||||
链路状态路由协议只在网络拓扑发生变化以后产生路由更新。当链路状态发生变化以后,检测到变化的设备创建__LSA(link state advertisement)__,通过使用__组播地址__传送给所有的__邻居设备__,然后每个设备拷贝一份LSA,更新它自己的__链路状态数据库(link state database,LSDB)__,接着再转发LSA给其他的邻居设备。这种__LSA的洪泛(flooding)__保证了所有的路由设备在更新自己的路由表之前更新它自己的LSDB
|
||||
|
||||
LSDB通过使用__Dijkstra算法__(**shortest path first**,SPF)来计算到达目标网络的最佳路径,建立一条__SPF树(tree)__,然后最佳路径从SPF树里选出来,被放进路由表里。
|
||||
|
||||
OSPF和IS-IS协议被归类到__链路状态路由协议__中。链路状态路由协议在一个特定的__区域(area)__里从邻居处收集网络信息,一旦路由信息都被**收集齐**以后,每个路由器开始通过使用Dijkstra算法(SPF)**独立计算**到达目标网络的最佳路径
|
||||
|
||||
运行了链路状态路由协议的路由器跟踪以下信息:
|
||||
1.它们各自的邻居
|
||||
2.在同一个区域中的所有路由器
|
||||
3.到达目标网络的最佳路径
|
||||
|
||||
为了能够做出更好的路由决策,OSPF路由器必须维持的有以下内容:
|
||||
1.neighbor table:也叫__adjacency database__.存储了邻居路由器的信息。如果一个OSPF路由器和它的邻居路由器失去联系,在几秒中的时间内,它会标记所有到达那条路由均为无效并且重新计算到达目标网络的路径
|
||||
2.topology table:一般叫做LSDB. OSPF路由器通过LSA学习到其他的路由器和网络状况,__LSA存储在LSDB中__
|
||||
3.routing table:也就是我们所说的路由表了,也叫**forwarding database**,包含了到达目标网络的最佳路径的信息
|
||||
|
||||
链路状态路由协议和距离向量路由协议的一个区别就是:距离向量路由协议是routing by rumors,也就是说,距离向量路由协议依靠邻居发给它的信息来做路由决策,而且路由器不需要保持完整的网络信息;而运行了链路状态路由协议的路由器保持的有__完整的网络信息的快照__,而且每个路由器自己做出路由决策
|
||||
|
||||
OSPF的网络设计要求是__双层层次化(2-layer hierarchy)__,包括如下2层:
|
||||
1.transit area(backbone或area 0)
|
||||
2.regular areas(nonbackbone areas)
|
||||
|
||||
transit area负责的主要功能是IP包快速和有效的传输。transit area__互联__OSPF其他区域(类型)。一般的,这个区域里不会出现端用户(end user)
|
||||
regular areas负责的主要功能就是**连接用户和资源**。这种区域一般是根据功能和地理位置来划分。一般的,一个regular area__不允许__其他区域的流量通过它到达另外一个区域,__必须穿越transit area__比如area 0. regular areas还可以有很多子类型,比如stub area,locally area和not-so-stubby area
|
||||
|
||||
在链路状态路由协议中,所有的路由器都保持有LSDB,OSPF路由器越多,LSDB就越大。这可能对了解完整的网络信息有帮助,但是随着网络的增长,__可扩展性__的问题就会越来越大。采用的__折中方案就是引入区域的概念__。在某一个区域里的路由器__只保持该区域中所有路由器__**或链路的详细信息**和其他区域的一般信息。当某个路由器或某条链路出故障以后,信息**只会**在那个区域以内在邻居之间传递。那个区域以外的路由器不会收到该信息。OSPF要求层次化的网络设计,意味着所有的区域要和area 0__直接相连__。如下图:
|
||||
{{./1.jpg}}
|
||||
|
||||
注意area 1和area 2或3之间的连接是不允许的,它们都必须通过backbone area 0进行连接。Cisco建议每个区域中路由器的数量为50到100个构建area 0的路由器称为__骨干路由器__(backbone router,BR),如上图,A和B就是BR;__区域边界路由器__(area border router,ABR)连接area 0和nonbackbone areas.如图,C,D和E就是ABR.
|
||||
|
||||
ABR通常具有以下特征:
|
||||
1.**分隔LSA洪泛的区域**
|
||||
2.是区域地址汇总的主要因素
|
||||
3.一般做为默认路由的源头
|
||||
4.__为每个区域保持LSDB__
|
||||
|
||||
理想的设计是使每个ABR只连接2个区域,backbone和其他区域,3个区域为上限
|
||||
|
||||
|
||||
运行OSPF的路由器通过交换__hello包__和别的路由器建立__邻接(adjacency)关系__,过程如下:
|
||||
1.路由器和别的路由器交换hello包,目标地址采用**多播地址**
|
||||
2.hello包交换完毕,**邻接关系形成**
|
||||
3.接下来通过交换LSA和对接收方的确认进行**同步LSDB**.对于OSPF路由器而言,进入__完全__**邻接状态**
|
||||
4.如果需要的话,路由器转发新的LSA给其他的邻居,来保证整个区域内LSDB的完全同步
|
||||
|
||||
对于点到点的WAN串行连接,两个OSPF路由器通常使用**HDLC或PPP**来形成完全邻接状态
|
||||
对于LAN连接,选举一个路由器做为__designated router(DR)__再选举一个做为__backup designated router(BDR)__,所有其他的和DR以及BDR相连的路由器形成完全邻接状态而且**只传输LSA给DR和BDR**.
|
||||
|
||||
DR从邻居处转发更新到另外一个邻居那里。DR的主要功能就是在一个LAN内的所有路由器拥有相同的数据库,而且把__完整的数据库信息__发送给__新加入__的路由器。路由器之间还会和LAN内的其他路由器(非DR/BDR,即DR OTHERs)维持一种**部分邻居关系**(two-way adjacency)。OSPF的邻接一旦形成以后,会交换LSA来同步LSDB,LSA将进行可靠的洪泛
|
||||
|
||||
链路状态陆游协议使用Dijkstra算法来查找到达目标网络中的最佳路径。所有的路由器拥有**相同**的LSDB后,__把自己放进SPF tree中的root里__,然后根据每条链路的耗费(cost),选出耗费最低的做为最佳路径,最后把最佳路径放进**forwarding database(路由表)**里。下图就是一个SPF计算的例子:
|
||||
|
||||
{{./2.jpg}}
|
||||
|
||||
1.LSA遵循split horizon原则,H对E宣告它的存在,E把H的宣告和它自己的宣告再传给C和G;C和G再和之前类似,继续传播开来……
|
||||
2.X有4个邻居:A,B,C和D,假设这里都是以太网,每条网链路的耗费为10,经过计算,路由器可以算出最佳路径。上图的右半部分实线所标即为最佳路径
|
||||
|
||||
LS Data Structures: LSA Options
|
||||
关于LSA的操作流程图如下:
|
||||
{{./3.jpg}}
|
||||
|
||||
如图可以看出当路由器收到一个LSA以后,先会查看它自己的LSDB看有没有相应的条目,如果没有就加进自己的LSDB中去,并反馈__LSA确认包(LSAck)__,接着再继续洪泛LSA,最后运行SPF算法算出新的路由表
|
||||
|
||||
如果当它收到LSA的时候,自己的LSDB有该条目而且__版本号__一样,就忽略这个LSA;如果有相应条目,但是收到的LSA的版本号__更新__,就加进自己的LSDB中,发回LSAck,洪泛LSA,最后用SPF计算最佳路径;如果版本号没有自己LSDB中那条新,就__反馈LSU信息给发送源__
|
||||
|
||||
Types of OSPF Packets
|
||||
OSPF包的5种类型如下:
|
||||
1.hello:用来建立邻居关系的包
|
||||
2.database description(DBD):用来检验路由器之间数据库的同步
|
||||
3.link state request(LSR):链路状态请求包
|
||||
4.link state update(LSU):特定链路之间的请求记录
|
||||
5.link state acknowledgement(LSAck):确认包
|
||||
|
||||
OSPF Packet Header Format
|
||||
5种OSPF包都是__直接被封装在IP包里__的而不使用TCP或UDP.由于没有使用可靠的TCP协议,但是OSPF包又要求可靠的传输,所以就有了LSAck包。如下图所示就是OSPF包在IP包里的形式:
|
||||
{{./4.jpg}}
|
||||
|
||||
协议号为89(EIGRP协议号为8,一些字段如下:
|
||||
1.Version Number:当前为OSPF版本2
|
||||
2.Type:定义OSPF包的类型
|
||||
3.Packet Length:包的长度,单位字节
|
||||
4.**Router ID(RID)**:产生OSPF包的__源路由器__
|
||||
5.**Area ID**:定义OSPF包是从哪个area产生出来的
|
||||
6.Checksum(校验和):错误校验
|
||||
7.**Authentication Type**:验证方法,可以是明文(cleartext)密码或者是Message Digest 5(MD5)加密格式
|
||||
8.Data:对于hello包来说,该字段是已知邻居的列表;对于DBD包来说,该字段包含的是__LSDB的汇总信息__,包括RID等等;对于LSR包来说,该字段包含的是需要的LSU类型和需要的LSU类型的RID;对于LSU包来说,包含的是完全的LSA条目,多个LSA条目可以装在一个包里;对于LSAck来说,字段为空
|
||||
|
||||
OSPF Neighbor Adjacency Establishment
|
||||
__Hello协议__用来建立和保持OSPF邻居关系,采用__多播地址224.0.0.5__,hello包包含的信息如下:
|
||||
1.Router ID(RID):路由器的32位长的一个唯一标识符,选举规则是,如果loopback接口不存在的话,就选物理接口中IP地址等级最高的那个;否则就选取loopback接口
|
||||
2.hello/dead intervals:定义了发送hello包频率(默认在一个多路访问网络中间隔为__10秒__);dead间隔是4倍于hello包间隔。邻居路由器之间的这些计时器必须设置成一样
|
||||
3.neighbors:__邻居列表__
|
||||
4.area ID:为了能够通信,OSPF路由器的接口必须属于__同一网段中的同一区域(area)__,即共享子网以及子网掩码信息
|
||||
5.__router priority__:优先级,选举DR和BDR的时候使用。8位长的一串数字
|
||||
6.DR/BDR IP address:DR/BDR的IP地址信息
|
||||
7.authentication password:如果启用了验证,邻居路由器之间必须交换相同的密码信息。此项可选
|
||||
8.stub area flag:stub area是通过使用默认路由代替路由更新的一种技术(有点像EIGRP中的stub功能)
|
||||
|
||||
Establishing Bidirectional Communication
|
||||
1.星型(star/hub-and-spoke):最常见的帧中继网络拓扑,代价最小
|
||||
2.全互连(full-mesh):冗余,但是代价大,在这样的环境中计算VC的数量,使用n(n-1)/2的公式,n为网络中的节点数
|
||||
3.部分互连(partial-mesh):前两种的折中方案
|
||||
|
||||
OSPF运行的两种RFC中定义的模式如下:
|
||||
|
||||
1.NBMA:一般和部分互连的网络结合使用,需要选举DR/BDR和人工指定邻居.优点是相对point-to-multipoint模式它的负载较低
|
||||
2.point-to-multipoint:把非广播的网络当作点到点连接的集合,自动发现邻居,不指定DR/BDR,一般和部分互连的网络结合使用.优点是配置较为简便
|
||||
|
||||
一些其他的可运行模式如下:
|
||||
|
||||
1.point-to-multipoint nonbroadcast
|
||||
2.broadcast
|
||||
3.point-to-point
|
||||
|
||||
定义OSPF网络类型的命令如下:
|
||||
|
||||
Router(config-if)#ip OSPF network [{broadcast | nonbroadcast | point-to-multipoint | point-to-multipoint nonbroadcast}]
|
||||
|
||||
几种选项的含义如下:
|
||||
|
||||
1.broadcast:使得WAN接口看上去像LAN接口;一个IP子网;多播hello包自动发现邻居;选举DR/BDR;要求网络全互连
|
||||
2.nonbroadcast(NBMA):一个IP子网;邻居手工指定;选举DR/BDR;DR/BDR要求和DROTHER完全互连;一般用在部分互连的网络中
|
||||
3.point-to-multipoint:一个IP子网;多播hello包自动发现邻居;不要求DR/BDR的选举;一般用在部分互连的网络中
|
||||
4.point-to-multipoint nonbroadcast:如果VC中多播和广播能力没有启用的话就不能使用point-to-multipoint模式,也路由器没办法多播hello包;邻居必须人工指定;不需选举DR/BDR
|
||||
5.point-to-point:一个子网;不选举DR/BDR;当只有2个路由器的接口要形成邻接关系的时候才使用;接口可以为LAN或WAN接口
|
||||
|
||||
Common OSPF Configuration for Frame Relay
|
||||
|
||||
先看看NBMA模式,如下图:
|
||||
|
||||
|
||||
1.OSPF会把NBMA当作broadcast网络进行处理(比如LAN)
|
||||
2.如图,所有的serial口处于同一子网
|
||||
3.ATM,X.25和帧中继默认为NBMA操作
|
||||
4.邻居手动指定
|
||||
5.洪泛LSU的时候,要对每条PVC进行洪泛
|
||||
6.RFC 2328兼容
|
||||
|
||||
对NBMA类型人工指定邻居使用如下命令:
|
||||
|
||||
Router(config-router)#neighbor [x.x.x.x] priority [number] poll-interval [number]
|
||||
|
||||
x.x.x.x为邻居的IP地址
|
||||
|
||||
priority [number]为优先级,如果设置为0的话将不能成为DR/BDR
|
||||
|
||||
poll-interval [number]是轮询的间隔时间,单位为秒.NBMA接口发送hello包给邻居之前等待的时间
|
||||
BIN
Zim/job/OSPF路由协议综述及其配置/1.jpg
Normal file
|
After Width: | Height: | Size: 93 KiB |
BIN
Zim/job/OSPF路由协议综述及其配置/2.jpg
Normal file
|
After Width: | Height: | Size: 65 KiB |
BIN
Zim/job/OSPF路由协议综述及其配置/3.jpg
Normal file
|
After Width: | Height: | Size: 86 KiB |
BIN
Zim/job/OSPF路由协议综述及其配置/4.jpg
Normal file
|
After Width: | Height: | Size: 76 KiB |
40
Zim/job/ext2-ext3-ext4区别.txt
Normal file
@@ -0,0 +1,40 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-04T10:51:12+08:00
|
||||
|
||||
====== ext2-ext3-ext4区别 ======
|
||||
Created Tuesday 04 September 2012
|
||||
http://ntt0729.blog.163.com/blog/static/11637756201021673352314/
|
||||
|
||||
ext2与ext3:
|
||||
Ext2 文件系统加上__日志支持__的下一个版本是 ext3 文件系统,它和 ext2 文件系统在硬盘布局上是一样的,其差别仅仅是 ext3 文件系统在硬盘上多出了__一个特殊的 inode__(可以理解为一个特殊文件),用来记录文件系统的日志,也即所谓的 journal。
|
||||
参考:http://bbs.chinaunix.net/thread-153696-1-1.html
|
||||
|
||||
ext3与ext4:
|
||||
Linux kernel 自 2.6.28 开始正式支持新的文件系统 Ext4。 Ext4 是 Ext3 的改进版,修改了 Ext3 中部分重要的数据结构,而不仅仅像 Ext3 对 Ext2 那样,只是增加了一个日志功能而已。Ext4 可以提供更佳的性能和可靠性,还有更为丰富的功能:
|
||||
|
||||
1. __与 Ext3 兼容__。 执行若干条命令,就能从 Ext3 在线迁移到 Ext4,而无须重新格式化磁盘或重新安装系统。原有 Ext3 数据结构照样保留,Ext4 作用于新数据,当然,整个文件系统因此也就获得了 Ext4 所支持的更大容量。
|
||||
|
||||
2. __更大的文件系统和更大的文件__。 较之 Ext3 目前所支持的最大 16TB 文件系统和最大 2TB 文件,Ext4 分别支持 1EB(1,048,576TB, 1EB=1024PB, 1PB=1024TB)的文件系统,以及 16TB 的文件。
|
||||
|
||||
3. __无限数量的子目录__。 Ext3 目前只支持 32,000 个子目录,而 Ext4 支持无限数量的子目录。
|
||||
|
||||
4. Extents。 Ext3 采用间接块映射,当操作大文件时,效率极其低下。比如一个 100MB 大小的文件,在 Ext3 中要建立 25,600 个数据块(每个数据块大小为 4KB)的映射表。而 Ext4 引入了现代文件系统中流行的 extents 概念,每个 extent 为一组连续的数据块,上述文件则表示为“该文件数据保存在接下来的 25,600 个数据块中”,提高了不少效率。
|
||||
|
||||
5. 多块分配。 当写入数据到 Ext3 文件系统中时,Ext3 的数据块分配器每次只能分配一个 4KB 的块,写一个 100MB 文件就要调用 25,600 次数据块分配器,而 Ext4 的多块分配器“multiblock allocator”(mballoc) 支持一次调用分配多个数据块。
|
||||
|
||||
6. 延迟分配。 Ext3 的数据块分配策略是尽快分配,而 Ext4 和其它现代文件操作系统的策略是尽可能地延迟分配,直到文件在 cache 中写完才开始分配数据块并写入磁盘,这样就能优化整个文件的数据块分配,与前两种特性搭配起来可以显著提升性能。
|
||||
|
||||
7. __快速 fsck__。 以前执行 fsck 第一步就会很慢,因为它要检查所有的 inode,现在 Ext4 给每个组的 inode 表中都添加了一份未使用 inode 的列表,今后 fsck Ext4 文件系统就可以跳过它们而只去检查那些在用的 inode 了。
|
||||
|
||||
8. __日志校验__。 日志是最常用的部分,也极易导致磁盘硬件故障,而从损坏的日志中恢复数据会导致更多的数据损坏。Ext4 的日志校验功能可以很方便地判断日志数据是否损坏,而且它将 Ext3 的两阶段日志机制合并成一个阶段,在增加安全性的同时提高了性能。
|
||||
|
||||
9. “无日志”(No Journaling)模式。 日志总归有一些开销,Ext4 允许关闭日志,以便某些有特殊需求的用户可以借此提升性能。
|
||||
|
||||
10. 在线碎片整理。 尽管延迟分配、多块分配和 extents 能有效减少文件系统碎片,但碎片还是不可避免会产生。Ext4 支持在线碎片整理,并将提供 e4defrag 工具进行个别文件或整个文件系统的碎片整理。
|
||||
|
||||
11. inode 相关特性。 Ext4 支持更大的 inode,较之 Ext3 默认的 inode 大小 128 字节,Ext4 为了在 inode 中容纳更多的扩展属性(如纳秒时间戳或 inode 版本),默认 inode 大小为 256 字节。Ext4 还支持快速扩展属性(fast extended attributes)和 inode 保留(inodes reservation)。
|
||||
|
||||
12. 持久预分配(Persistent preallocation)。 P2P 软件为了保证下载文件有足够的空间存放,常常会预先创建一个与所下载文件大小相同的空文件,以免未来的数小时或数天之内磁盘空间不足导致下载失败。 Ext4 在文件系统层面实现了持久预分配并提供相应的 API(libc 中的 posix_fallocate()),比应用软件自己实现更有效率。
|
||||
|
||||
13. 默认启用 barrier。 磁盘上配有内部缓存,以便重新调整批量数据的写操作顺序,优化写入性能,因此文件系统必须在日志数据写入磁盘之后才能写 commit 记录,若 commit 记录写入在先,而日志有可能损坏,那么就会影响数据完整性。Ext4 默认启用 barrier,只有当 barrier 之前的数据全部写入磁盘,才能写 barrier 之后的数据。(可通过 "mount -o barrier=0" 命令禁用该特性。)
|
||||
118
Zim/job/网络支架—BGP.txt
Normal file
@@ -0,0 +1,118 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-04T09:40:57+08:00
|
||||
|
||||
====== 网络支架—BGP ======
|
||||
Created Tuesday 04 September 2012
|
||||
http://www.cnpaf.net/Class/BGP/200501/1083.html
|
||||
|
||||
网络支架—BGP
|
||||
时间:2005-01-14 来源: 作者: 点击:7709次
|
||||
|
||||
|
||||
===== 1. 概述 =====
|
||||
因特网,在20世纪60年代末,作为一个实验,开始于DARPA(美国国防部的高级研究项目管理局)。随着研究机构、学院和政府加入,形成了最早的ARPANET。后来,美国国家科学基金会又开发了NSFNET(1995年4月停用)。发展到现在,__因特网成为了由商业提供者运营的的更分散的体系__。而下一代因特网(NGI)的计划已于1997年10月启动,目前已推出的主要方案有Internet2,Abilene等。
|
||||
|
||||
出于__管理和扩展__的目的,因特网可以被分割成许多不同的__自治系统__(Autonomous System)。换句话说,因特网是由自治系统汇集而成的。
|
||||
|
||||
BGPv4(Border gateway protocol Version 4)——边缘网关协议(定义于RFC1771),是现行因特网的实施标准,__就是用来连接自治系统,实现自治系统间的路由选择功能的。__
|
||||
|
||||
===== 2. IGP与EGP =====
|
||||
所有的路由选择协议可以被分成IGP和EGP两种。要了解IGP和EGP的概念,应该首先了解自治系统(AS)的概念。传统的AS定义(RFC1771):AS是同一个技术管理下的一组路由器,它们使用一种内部网关协议和一致的度量尺度来对数据包进行AS内部的路由,而使用外部网关协议来对发向其它AS的数据包进行路由选择。发展到现在,已经允许并且时常采用在一个自治系统AS中使用多个内部网关协议,甚至多个路由选择的度量标准。所以,现在的自治系统被扩展的定义为:__共享同一路由选择策略的一组路由器。__
|
||||
|
||||
IGP(Interior gateway protocols)——内部网关协议,定义为在一个自治系统内部使用的路由协议(包括动态路由协议和静态路由)。IGP的功能是完成数据包在AS内部的路由选择,或者说,是讲述数据包如何穿过本地AS的。RIPv1&v2,IGRP,EIGRP,OSPF,**ISIS**都是典型的IGP。
|
||||
|
||||
EGP(Exterior gateway protocols)——外部网关协议,定义为在多个自治系统之间使用的路由协议。它主要完成数据包在AS间的路由选择,或者说,讲述数据包为了到达目的IP,需要通过哪些AS。BGP4就是一种EGP。
|
||||
|
||||
IGP只作用于本地AS内部,而对其他AS一无所知。它负责将数据包发到主机所在的网段(segment)。EGP作用于各AS之间,它只了解AS的__整体结构__,而不了解各个AS内部的拓扑结构。它只负责将数据包发到相应的AS中,余下的工作便交给IGP来做。
|
||||
|
||||
每个自治系统AS都有唯一的标识,称为__AS号__(AS number),由IANA(Internet Assigned Numbers Authority)来授权分配。这是一个16位的二进制数,范围为1~65535,其中65412~65535为AS专用组(RFC2270)。
|
||||
{{./1.jpg}}
|
||||
图一
|
||||
|
||||
===== 3.BGP-4的基本概念 =====
|
||||
BGP-4是典型的外部网关协议,是现行的因特网实施标准。它完成了在自治系统AS间的路由选择。可以说,BGP协议是现代整个网络的支架。BGP-4在RFC1771中作出了规定,并且还涉及其他很多的RFC文档。在这一新版本中,BGP开始支持**CIDR**(Classless interdomains routing)和**AS路径聚合**(Aggregation),这种新属性的加入,可以减缓BGP表中条目的增长速度。
|
||||
{{./2.jpg}}
|
||||
图二
|
||||
|
||||
BGP协议是一种__距离矢量__(Distance vector)的路由协议,但是比起RIP等典型的距离矢量协议,又有很多增强的性能。BGP使用__TCP__作为传输协议,使用端口号__179__。在通信时,要先建立TCP会话,这样数据传输的可靠性就由TCP协议来保证,而在BGP的协议中就不用再使用差错控制和重传的机制,从而简化了复杂的程度。另外,BGP使用__增量的、触发性的路由更新__,而不是一般的距离矢量协议的__整个路由表的、周期性__的更新,这样节省了更新所占用的带宽。BGP还使用“保留”信号(Keepalive)来监视TCP会话的连接。而且,BGP还有多种__衡量路由路径的度量标准(称为路由属性)__,可以更加准确的判断出最优的路径。
|
||||
|
||||
与传统的内部路由协议相比,BGP还有一个有趣的特性,就是使用BGP的路由器之间,可以__被未使用BGP的路由器隔开__。这是因为BGP在独立的内部路由协议之上工作,所以__通过BGP会话连接的路由器能被多个运行内部路由协议的路由器分开__。
|
||||
|
||||
建立了BGP会话连接的路由器被称作__对等体__(peers or neighbors),对等体的连接有两种模式:IBGP(Internal BGP)和EBGP(External BGP)。IBGP是指单个AS内部的路由器之间的BGP连接,而EBGP则是指AS之间的路由器建立BGP会话。
|
||||
{{./3.jpg}}
|
||||
图三
|
||||
|
||||
前面已经提到,BGP是用来完成AS之间的路由选择的,所以对于BGP来说,每一个AS都是一个原子的跳度。那么,IBGP又起什么样的作用呢?__IBGP是用来在AS内部完成BGP更新信息的交换__。虽然这种功能也可以由“再分布” (Redistribution)技术来完成——将EBGP传送来的其他AS的路由“再分布”到IGP中,然后将其“再分布”到EBGP传送到其他AS。但是相比之下,IBGP提供了更高的扩展性、灵活性和管理的有效性。比如,IBGP提供了选择本地AS外出点的方式。
|
||||
|
||||
__IBGP的功能是维护AS内部连通性__。BGP规定,一个IBGP的路由器不能将来自另一IBGP路由器的路由发送给第三方IBGP路由器。这也可以理解为通常所说的Split-horizon规则。当路由器通过EBGP接收到更新信息时,它会对这个更新信息进行处理,并发送到所有的IBGP及余下的EBGP对等体;而当路由器从IBGP接收到更新信息时,它会对其进行处理__并仅通过__EBGP传送,而不会向IBGP传送。所以,在AS中,__BGP路由器必须要通过IBGP会话建立完全连接的网状连接,以此来保持BGP的连通性__。如果没有在物理上实现全网状(full meshed)的连接,就会出现连通性上的问题(这在大型网络中会遇到扩展性的问题,将在下面“扩展性”一节中作讨论。)
|
||||
|
||||
AS在BGP看来是一个整体,AS内部的BGP路由器都必须将相同的路由信息发送给边界的EBGP路由器。路由信息在通过IBGP链路时不会发生改变,只有通过EBGP链路时,路由信息才会发生变化。在AS内部,通过IBGP连接的路由器都有相同的BGP路由表(BGP路由表(BGP Routing Table)用于存放BGP路由信息,不同于IGP路由表,两个表之间的信息可以通过“再分布”(Redistribution)技术进行交换)。
|
||||
|
||||
===== 4. BGP的路由选择 =====
|
||||
BGP的消息报头由三个部分组成:标记、长度和类型。标记段占16个字节,用于安全检测和同步检测;长度段占2个字节,标明整个BGP消息的长度;类型段占一个字节,标明消息的类型。报头的后面可以不接数据部分,如Keepalive消息。
|
||||
|
||||
BGP消息有__四种类型__:OPEN,UPDATE,NOTIFICATION和KEEPALIVE,分别用于建立BGP连接,更新路由信息,差错控制和检测可到达性。
|
||||
|
||||
OPEN消息是在建立TCP连接后,向对方发出的第一条消息,它包括版本号、各自所在AS的号码(AS Number)、BGP标识符(BGP Identifier)、协议参数、会话保持时间(Hold timer)以及可选参数、可选参数长度。其中,**BGP标识符**用来标识本地路由器,在连接的所有路由器中应该是唯一的。这个标识符一般都使用接口上的最大的IP地址(常常使用loopback接口来防止地址失效)。而会话保持时间,是指在收到相继的Keepalive或者Update信号之间的最大间隔时间。如果超过这个时间路由器仍然没有收到信号,就会认为对应的连接中断了。如果把这个保持时间的值设为0,那么表示认为连接永远存在。
|
||||
|
||||
UPDATE消息由不可到达路由(Withdrawn Route)、__路由属性__(Route Attributes)和网络层可到达性(Network Layer Reachability Information—NLRI)组成。
|
||||
|
||||
BGP路由属性是BGP 路由的__核心概念__。它是一组参数,在UPDATE消息中被发给连接对等体。这些参数记录了BGP路由信息,**用于选择和过滤路由**。它可以被看作选择路由的__度量尺度(metric)__。
|
||||
|
||||
路由属性被分为四类:**公认强制**(Well-known mandatory attributes)、**公认自由选择**(Well-known discretionary attributes)、**可选传递**(Optional transitive attributes)和**可选非传递**(Optional nontransitive attributes)。
|
||||
|
||||
公认的(Well-known)属性对于所有的BGP路由器来说都是可辨别的;每个UPDATE消息中都__必须包含__强制(mandatory)属性,而自由选择的(discretionary)属性则是可选的,可包括也可不包括。对于可选的(Optional)属性,不是所有的BGP工具都支持它。当BGP不支持这个属性时,如果这个属性是过渡性的(transitive),则会被接受并传给其他的BGP对等体;如果这个属性是非传递性的(nontransitive),则被忽略,不传给其他对等体。
|
||||
|
||||
在技术文档RFC1771定义了1~7号的BGP路由属性,依次是:
|
||||
1,ORIGIN(产生该路由信息的AS);
|
||||
2,AS_PATH(包已通过的AS集或序列);
|
||||
3,NEXT_HOP(要到达该目的下一跳的IP地址,IBGP连接不会改变从EBGP发来的NEXT_HOP);
|
||||
4,MULTI_EXIT_DISC(本地路由器使用,区别到其他AS的多个出口);
|
||||
5,LOCAL-PREF(在本地AS内传播,标明各路径的优先级);
|
||||
6,ATOMIC_AGGREGATE;
|
||||
7,AGGREGATOR。
|
||||
8,COMMUNITY。
|
||||
|
||||
其中,1、2号属性是公认强制;3、5、6是公认可选;7、8是可选过渡;4是可选非过渡。
|
||||
|
||||
这些属性在路由的选择中,考虑的优先级是不同的,仅就这8个属性来说,其中优先级最高的是LOCAL-PREF,接下来是ORIGIN和AS_PATH。BGP所使用到的路由属性并不仅仅是这8个,其他的具体内容可以参阅RFC文档(RFC1771、1996、1997、1966、1863、2283)。
|
||||
|
||||
网络层可到达性(NLRI)包含了**<长度,前缀>**这样的二维数组,使用CIDR(Classless Interdomain Routing)技术,来__聚合路由__,以减缓BGP表的增长速度。
|
||||
|
||||
==== BGP工作流程如下: ====
|
||||
首先,在要建立BGP会话的路由器之间建立TCP会话连接,然后通过交换OPEN信息来确定**连接参数**,如运行版本等。建立**对等体连接**关系后,最开始的路由信息交换将包括**所有**的BGP路由,也就是交换BGP表中所有的条目。初始化交换完成以后,只有当路由条目发生改变或者失效的时候,才会发出**增量的触发性**的路由更新。所谓增量,就是指并不交换整个BGP表,而只更新发生变化的路由条目;而触发性,则是指只有在路由表发生变化时才更新路由信息,而并不发出周期性的路由更新。比起传统的全路由表的定期更新,这种__增量触发的更新__大大节省了带宽。路由更新都是由UPDATE消息来完成。UPDATE包含了**发送者可到达的目的列表和路由属性**。当没有路由更新传送时,BGP会话用**KEEPALIVE消息**来验证连接的可用性。由于KEEPALIVE包很小,这也可以大量节省带宽。在__协商发生错误__时,BGP会向双方发送NOTIFICATION消息来通知错误。
|
||||
|
||||
===== 5.BGP与IGP的互操作 =====
|
||||
BGP路由表是__独立__于IGP路由表的,但是这两个表之间可以进行信息的交换,这就是前面提到的__“再分布”__技术(Redistribution)。
|
||||
信息的交换有两个方向:从BGP注入IGP,以及从IGP注入BGP。前者是将AS外部的路由信息传给AS内部的路由器,而后者是将AS内部的路由信息传到外部网络,这也是路由更新的来源。
|
||||
|
||||
把路由信息从BGP注入IGP涉及到一个重要概念——__同步__(Synchronization)。同步规则,是指当一个AS为另一个AS提供了过渡服务时,只有当本地AS内部所有的路由器__都__通过IGP的路由信息的传播收到这条路由信息以后,BGP才能向外发送这条路由信息。当路由器从IBGP收到一条路由更新信息时,在转发给其他EBGP对等体转之前,路由器会对同步性进行验证。只有IGP认识这个更新的目的时(即IGP路由表中有相应的条目),路由器才会将其通过EBGP转发;否则,路由器不会转发该更新信息。
|
||||
|
||||
同步规则的主要目的是__为了保证AS内部的连通性,防止路由循环的黑洞__。但是在实际的应用中,一般都会将同步功能禁用,而使用__AS内IBGP的全网状连接结构__来保证连通性,这样即可以避免向IGP中注入大量BGP路由,加快路由器处理速度,又可以保证数据包不丢失。要安全的禁用同步,需要满足以下两个条件之一:1,所处的AS是单口的,或者说是末端AS(Stub AS)――即是指只有一个点与外界网络连接。2,虽然所处的AS是过渡型的(指一个AS可以通过本地AS,与第三方AS建立连接的),但是在AS内部的所有路由器都运行BGP。第2种情况是很常见的,因为AS内所有的路由器都有BGP信息,所以IGP只需要为本地AS 传送路由信息。大部分的网络设备在实现BGP时,都提供了禁用同步的开关。
|
||||
|
||||
将IGP路由信息注入BGP,是路由更新的来源。它直接影响到因特网的路由稳定性。信息注入有两种方式:动态和静态。
|
||||
__动态注入__又分为完全注入和选择性注入。完全动态注入是指将所有的IGP路由再分布(Redistribution)到BGP中。这种方式的优点是配置简单,但是可控性弱,效率低。选择性的动态注入则是将IGP路由表中的一部分路由信息注入BGP(如使用CiscoIOS中的network子命令)。这种方式会先验证地址及掩码,大大增强了可控性,提高了效率,可以防止错误的路由信息注入。
|
||||
|
||||
但是无论哪种动态注入方式,都会造成路由的不稳定。因为动态注入完全依赖于IGP信息,当IGP路由发生路由波动时,不可避免的会影响到BGP的路由更新。这种路由的不稳定会发出大量的更新信息,浪费大量的带宽。对于这种缺陷,可以使用在边界处使用__路由衰减和聚合__(BGP4的新增特性CIDR)来改善。
|
||||
|
||||
静态注入就可以有效解决路由不稳定的问题。它是将静态路由的条目注入到BGP中去。静态路由存在于IGP路由表中。由于静态路由条目是人为的加入的,不会受到IGP波动的影响,所以很稳定。它的稳定性防止了路由波动引起的反复更新。但是,静态注入也会产生数据流阻塞等问题。所以,在选择注入方式时,需要根据网络的实际状况来作出选择。
|
||||
|
||||
BGP还提供选择不同__路由策略__(Policy)的方法来控制BGP更新信息的数据流。具体的说,可以改变__管理距离__(Administrative distance)来确定使用哪一个路由协议的更新信息;可以使用__BGP过滤__(如route maps)来控制更新数据流;还可以用CIDR和地址聚合来改变更新信息;也可以使用路由反射器(Route Reflectors)来改变路由更新信息的转发方式,从而改变对BGP内部网络的物理拓扑的全网状要求。
|
||||
|
||||
6.针对大型网络的扩展特性
|
||||
前面已经提到,由于IBGP的Split-horizon特性,为了保证连续性,就__必须在AS内实现全网状的__(full meshed)连接。但是,对于一个X个节点的网络来说,全网状的拓扑要求((X-1)X/2)个连接。当X的值很大的时候,就会带来过多的花费,以及庞大的配置任务。这是很不经济和低效的。
|
||||
|
||||
为了减少对IBGP链路的要求,在RFC1966、RFC1965中,分别提出了__路由反射器__(Route Reflection)和__联盟__(Confederations)技术。路由器可以配置成路由反射器,路由反射器允许将一个IBGP传来的BGP路由传到另一个IBGP链路上。这样就缓解了对全网状拓扑的需求。但是并不是所有情况下,路由反射器都是一种最佳选择,因为这样会加大作为反射器的路由器的系统开销,从而减慢处理速度,甚至造成数据丢失。而且,如果路由反射器出现问题,那么所有的连接都会被中断。为了防止中断出现,增加冗余度,一般可以配置多个路由反射器。
|
||||
{{./4.jpg}}
|
||||
图四
|
||||
|
||||
联盟则是__将AS再划分成子AS__(sub AS),在sub AS中运行IBGP,使用全网状结构;而在sub AS之间使用EBGP,这样也可减少对IBGP连接的需求。联盟同样也存在缺陷,如路由选择等。所以和路由反射器一样,一般只在存在很多IBGP连接时才会使用联盟技术。
|
||||
|
||||
===== 7.BGP的使用 =====
|
||||
BGP的功能是在各AS之间完成路由选择。它主要用于ISP(Internet Service Provider)之间的连接和数据交换。但是,并不是所有情况下BGP都适用。使用BGP会大大增加路由器的开销,并且大大增加规划和配置的复杂性。所以,使用BGP协议需要先做好需求分析。
|
||||
|
||||
一般来说,如果本地的AS与多个外界AS建立了连接,并且有数据流从外部AS通过本地AS到达第三方的AS,那么可以考虑使用BGP来控制数据流。如果本地AS与外界只有一个连接(通常说的stub AS),而且并不需要对数据流进行严格控制,那就不必使用BGP协议,而可以简单的使用静态路由(Static route)来完成与外部AS的数据交换。另外,硬件和线路的原因也会影响到BGP的选择。如前所说,使用BGP会加大路由器的开销,并且BGP路由表也需要很大的存储空间,所以当路由器的CPU或者存储空间有限时,或者带宽太小时,不宜使用BGP路由协议。
|
||||
|
||||
===== 8.总结 =====
|
||||
BGP的主要功能是连接各个AS,提供AS之间的信息交换。各个BGP系统之间交换路由信息,来保证得到一个无环路的路由结构。BGP还提供了在AS的水平上的路由策略的选择方式,以优化路由选择。作为整个互联网的整体支架,BGP提供了一个功能强大的连接AS的工具,但是它在使用中也存在一定的局限性,所以在决定使用之前一定要作认真分析,以确定是否使用BGP以及具体实施方案。
|
||||
BIN
Zim/job/网络支架—BGP/1.jpg
Normal file
|
After Width: | Height: | Size: 21 KiB |
BIN
Zim/job/网络支架—BGP/2.jpg
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
Zim/job/网络支架—BGP/3.jpg
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
Zim/job/网络支架—BGP/4.jpg
Normal file
|
After Width: | Height: | Size: 16 KiB |
75
Zim/job/路由基础详解--OSPF路由协议.txt
Normal file
@@ -0,0 +1,75 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-09-03T20:32:02+08:00
|
||||
|
||||
====== 路由基础详解--OSPF路由协议 ======
|
||||
Created Monday 03 September 2012
|
||||
http://net.zdnet.com.cn/network_security_zone/2010/0813/1847831.shtml
|
||||
|
||||
本文主要给大家讲述了路由器使用时常用到的一种网络协议--OSPF协议,并且针对它的特性特点,给出了具体的介绍,希望看过此文之后能对你有所帮助。
|
||||
|
||||
鉴于早期__距离矢量协议__所造成的诸多网络问题,网络设计者又开发了更新、更先进的路由协议——__链路状态路由协议__。OSPF路由协议就是一种链路状态路由协议。随着Internet技术在全球范围的飞速发展,OSPF已成为目前Internet广域网和Intranet企业网采用最多、应用最广泛的路由协议之一。作为一种链路状态的路由协议,OSPF将__链路状态广播数据包LSA(Link State Advertisement)__传送给在某一区域内的__所有__路由器,这一点与距离矢量路由协议不同。运行距离矢量路由协议的路由器是将部分或全部的路由表传递给与其相邻的路由器。
|
||||
|
||||
OSPF路由协议也是一种IGP协议,它只能在工作在自治域系统内部,不能跨自治域系统工作。对比距离矢量路由协议,OSPF路由协议具有__快速收敛__与适用范围更广的优势。
|
||||
|
||||
在大型的网络拓扑结构中,如果每台路由器都将自己学到的整个网络的路由信息传送更新的话,将会严重地占用带宽资源,对于像荣新外企IT培训中心这样以传输音频、视频为主的数据时,更会出现音频、视频传输的不流畅。而OSPF路由协议很好地解决了这些问题。在刚刚开始工作的时候,首先和相邻的路由器建立__邻居关系__,形成邻居表,然后互相交换自己所了解的网络拓扑。路由器在没有学习到全部网络拓扑是__不会进行任何路由操作__的,因为这时候的路由表是空的。只有当路由器学到了__全部__的网络拓扑,建立了__拓扑表(也称链路状态数据库)__之后,他们会使用__最短路径优先(SPF)__算法,从拓扑表中计算出最佳路由。因为,所有运行OSPF路由协议的路由器都维护着相同的拓扑表,路由器可以自己从中计算路由。
|
||||
|
||||
在运行OSPF路由协议的网络里,当网络拓扑发生了变化,例如,一条物理线路出现了故障。发现该变化的邻居路由器会向其他路由器发送__触发的路由更新包__——__链路状态更新包(LSU)__。在LSU中包含了关于发生变化的网段信息,__链路状态通告(LSA)__。接收到该更新包的路由器,会继续向其他路由器发送更新,同时根据LSA中的信息,在拓扑表里重新计算发生变化网段的路由。由于没有对链路的保持时间,OSPF路由协议的收敛速度是相当快的,这一点对于大型的网络是非常关键的。
|
||||
|
||||
OSPF路由协议还可以__把一个大型的网络划分区域__,这点跟IS-IS相似,不同的是在OSPF中会有一个__唯一的骨干区域__。这样对于**路由信息的汇总**起了很好的作用,对于设备的采用也可以根据其所在区域的位置选择不同成本的设备,对工程成本有了很好的规划。这种特性也使OSPF路由协议能够在大规模的路由网络中正常高效地工作。
|
||||
|
||||
1.链路
|
||||
|
||||
将运行OSPF路由协议的路由器所连接的网络线路称为链路。
|
||||
|
||||
路由器会检查其所连接的网络状态,然后将该信息由自己的所有接口向邻居传送,这个过程称为“泛洪(Flooding)”。运行OSPF路由协议的路由器,由邻居处得到关于链路的信息,并且将该信息继续向其他邻居传送。
|
||||
|
||||
2.链路状态
|
||||
|
||||
链路的工作状态,是正常工作,还是发生故障,这些相关的信息称为链路状态。
|
||||
|
||||
在OSPF中,只需要发送链路状态信息就可以确保对网络的掌握。也是通过这种信息来交换学习路由信息。
|
||||
|
||||
3.区域
|
||||
|
||||
OSPF路由协议会把大规模的网络划分成多个小范围的区域,以避免大规模网络所带来的弊病,从而提高网络性能。OSPF中区域的划分是非常重要的内容,很多公司的面试题都会涉及到这个方面,后面会具体了解OSPF。
|
||||
|
||||
4.邻居
|
||||
|
||||
在同样运行OSPF路由协议的两台路由器__物理直连__时,就可以成为邻居关系。只有成为了邻居关系它们互相才能交换路由信息。当线路出现问题时,可以通过查看邻居状态来排除故障。
|
||||
|
||||
5.链路开销
|
||||
|
||||
OSPF路由协议通过计算链路的带宽来计算最佳路径的选择。每条链路根据带宽不同会有不同的度量值,这个度量值在OSPF路由协议中称作__“开销(Cost)”__。
|
||||
|
||||
10Mbps的以太网的链路开销是10,16Mbps令牌环网的链路开销是6,FDDI或快速以太网的开销是1,2M串行链路的开销是48,56KB串行线路的开销为1785。
|
||||
|
||||
路由器将整条路径的开销相加,所得之和最小的值为最佳路径。
|
||||
|
||||
6.邻居表
|
||||
|
||||
与EIGRP、IS-IS一样,OSPF路由协议也需要维系**3张表**。__最基础的就是邻居表__。
|
||||
|
||||
凡是与路由器同在OSPF路由协议中并且物理直连的路由器都会被收录在该表中,只有形成了邻居表,路由器才可能向其他的路由器学习拓扑结构。
|
||||
|
||||
7.拓扑表
|
||||
|
||||
当路由器建立了邻居表之后,运行OSPF路由协议的路由器会__互相通告自己所了解的网络拓扑建立拓扑表__。在一个区域里,所有的路由器应该形成**相同**的拓扑表。只有建立了拓扑表之后,路由器才能使用SPF算法从拓扑表里计算路由。
|
||||
|
||||
8.路由表
|
||||
|
||||
路由器依靠路由表来为数据包进行路由操作。在运行OSPF路由协议的路由器中,当完整的拓扑表建立起来之后,路由器便会按照链路带宽的不同,使用SPF算法从拓扑表例计算出路由,记入__路由表__。
|
||||
|
||||
9.路由器标识(Route ID)
|
||||
|
||||
路由器标识不是为路由器起的名称,而是路由器在OSPF路由协议操作中对自己的标识。一般来说,在没有配置回环接口(Loopback Interface:一种路由器上的虚拟接口,它是逻辑存在的,路由器上并没有这种物理接口,它是永久开启的。)时,路由器的所有物理接口上配置的__最大IP地址__就是这台路由器的标识。如果在路由器上配置了回环地址接口,则不论回环地址上的IP地址是多少,该地址都自动成为路由器的标识。当在路由器上配置了多个回环接口时,这些回环接口中最大的IP地址将作为路由器的标识。
|
||||
|
||||
在实际工程中,可能会遇到没有设置Route ID的情况,由路由器默认的最大物理接口地址为Route ID,而这个接口并没有连接任何设备,也就是Shut Down状态,在这时不管这台路由器的其他接口状态如何,OSPF都将随着Route ID的Shut Down而不启动。所以,在设置Route ID时一定要选用Loopback地址作为Route ID,因为这个地址是永久不关闭的,而且最好有完善的规划。
|
||||
|
||||
10.LSA与LSU
|
||||
|
||||
路由器对__某一条线路__的状态更新称为LSA,对__一组链路的状态__更新称为LSU,**LSU更新包里可包含多个LSA**。
|
||||
|
||||
11.DR与BDR
|
||||
|
||||
当几台路由器工作在同一网段上时,为了__减少网段中路由信息的交换数量__,OSPF路由协议定义了DR和BDR。负责__收集__网络中的链路状态通告,并将他们__集中__发给其他的路由器。BDR实际上是DR的备份,在DR本身没有问题时,BDR并不工作,一旦DR出现了问题BDR就会接替DR的工作。在实际工程中,DR与BDR的概念应用并不广泛。
|
||||
153
Zim/内核开发/Linux_Cache_机制探究.txt
Normal file
@@ -0,0 +1,153 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-25T09:13:07+08:00
|
||||
|
||||
====== Linux Cache 机制探究 ======
|
||||
Created Thursday 25 October 2012
|
||||
|
||||
本文内容遵从CC版权协议, 可以随意转载, 但必须以超链接形式标明文章原始出处和作者信息及版权声明
|
||||
网址: http://www.penglixun.com/tech/system/linux_cache_discovery.html
|
||||
|
||||
经过研究了下Linux相关代码,把对Linux Cache实现的方式做一些总结。
|
||||
相关源码主要在:
|
||||
./fs/fscache/cache.c Cache实现的代码
|
||||
./mm/slab.c SLAB管理器代码
|
||||
./mm/swap.c 缓存替换算法代码
|
||||
./mm/mmap.c 内存管理器代码
|
||||
./mm/mempool.c 内存池实现代码
|
||||
|
||||
===== 0. 预备:Linux内存管理基础 =====
|
||||
创建进程fork()、程序载入execve()、映射文件mmap()、动态内存分配malloc()/brk()等进程相关操作都需要__分配内存给进程__。不过这时进程申请和获得的还不是实际物理内存,而是虚拟内存,准确的说是__“内存区域”__。Linux除了内核以外,App都不能直接使用内存,因为Linux采用Memory Map的管理方式,App拿到的全部是内核映射自物理内存的一块虚拟内存。malloc分配很少会失败,因为malloc只是通知内存App需要内存,在没有正式使用之前,这段内存其实只在真正开始使用的时候才分配,所以malloc成功了并不代表使用的时候就真的可以拿到这么多内存。据说Google的tcmalloc改进了这一点。
|
||||
|
||||
进程对内存区域的分配最终多会归结到__do_mmap()函数__上来(brk调用被单独以系统调用实现,不用do_mmap())。内核使用do_mmap()函数创建一个新的线性地址区间,如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况, do_mmap()函数都会__将一个地址区间加入到进程的地址空间中__,无论是扩展已存在的内存区域还是创建一个新的区域。同样释放一个内存区域使用函数do_ummap(),它会销毁对应的内存区域。
|
||||
|
||||
mmap()函数通常有__文件映射和匿名映射__两种形式,前者是将磁盘着中的整个文件或部分文件映射到进程地址空间中,后者分配的内存区域不与任何文件关联;例如执行execlv时会将可执行文件的正文段、全局数据段、共享库分别映射到进行的不同内存区域中,并且每个区域都有自己的访问权限控制。在文件映射的过程中,内核会读取文件的inode节点信息,在内存中构建struct file, struce dentry, struct inode等__与该文件相关的VFS对象__,同时也会建立和该文件相关的__page cahe对象__struct address_space。对于堆、栈等使用的是匿名映射。
|
||||
|
||||
另一个重要的部分是SLAB分配器。在Linux中以页为最小单位分配内存对于内核管理系统物理内存来说是比较方便的,但内核自身最常使用的内存却往往是很小(远远小于一页)的内存块,因为大都是一些描述符。一个整页中可以聚集多个这种这些小块内存,如果一样按页分配,那么会被频繁的创建/销毁,开始是非常大的。
|
||||
|
||||
为了满足内核对这种小内存块的需要,Linux系统采用了SLAB分配器。Slab分配器的实现相当复杂,但原理不难,其核心思想就是__Memory Pool__。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到Memory Pool里,留做下次使用,这就避免了频繁创建与销毁对象所带来的额外负载。
|
||||
|
||||
Slab技术不但避免了内存内部分片带来的不便,而且可以很好利用硬件缓存提高访问速度。但Slab仍然是__建立在页面基础之上__,Slab将页面分成众多小内存块以供分配,Slab中的对象分配和销毁使用kmem_cache_alloc与kmem_cache_free。
|
||||
|
||||
关于SALB分配器有一份资料:http://lsec.cc.ac.cn/~tengfei/doc/ldd3/ch08s02.html
|
||||
关于内存管理的两份资料:http://lsec.cc.ac.cn/~tengfei/doc/ldd3/ch15.html
|
||||
http://memorymyann.javaeye.com/blog/193061
|
||||
|
||||
===== 1. Linux Cache的体系 =====
|
||||
在 Linux 中,当App需要读取Disk文件中的数据时,Linux先分配一些内存,将数据从Disk读入到这些内存中,然后再将数据传给App。当需要往文件中写数据时,Linux先分配内存接收用户数据,然后再将数据从内存写到Disk上。Linux Cache 管理指的就是对这些由Linux分配,并用来存储文件数据的内存的管理。
|
||||
|
||||
下图描述了 Linux 中文件 Cache 管理与内存管理以及文件系统的关系。从图中可以看到,在 Linux 中,具体的文件系统,如 ext2/ext3/ext4 等,负责在文件 Cache和存储设备之间交换数据,位于具体文件系统之上的虚拟文件系统VFS负责在应用程序和文件 Cache 之间通过 read/write 等接口交换数据,而内存管理系统负责文件 Cache 的分配和回收,同时虚拟内存管理系统(VMM)则允许应用程序和文件 Cache 之间通过 memory map的方式交换数据,FS Cache底层通过SLAB管理器来管理内存。
|
||||
{{./1}}
|
||||
|
||||
下图则非常清晰的描述了Cache所在的位置,磁盘与VFS之间的纽带。
|
||||
{{./2}}
|
||||
2.
|
||||
===== Linux Cache的结构 =====
|
||||
|
||||
在 Linux 中,文件 Cache 分为两层,一是 Page Cache,另一个 Buffer Cache,每一个 Page Cache 包含若干 Buffer Cache。内存管理系统和 VFS 只与 Page Cache 交互,__内存管理系统负责维护每项 Page Cache 的分配和回收__,同时在使用 memory map 方式访问时负责建立映射;__VFS 负责 Page Cache 与用户空间的数据交换__。 而具体文件系统则一般只与 Buffer Cache 交互,它们负责在外围存储设备和 Buffer Cache 之间交换数据。读缓存以Page Cache为单位,每次读取若干个Page Cache,回写磁盘以Buffer Cache为单位,每次回写若干个Buffer Cache。
|
||||
|
||||
Page Cache、Buffer Cache、文件以及磁盘之间的关系如下图所示。
|
||||
{{./3}}
|
||||
|
||||
|
||||
Page 结构和 buffer_head 数据结构的关系如下图所示。Page指向一组Buffer的头指针,Buffer的头指针指向磁盘块。在这两个图中,假定了 Page 的大小是 4K,磁盘块的大小是 1K。
|
||||
{{./4}}
|
||||
|
||||
在 Linux 内核中,__文件的每个数据块最多只能对应一个 Page Cache 项__,它通过两个数据结构来管理这些 Cache 项,__一个是 Radix Tree,另一个是双向链表__。Radix Tree 是一种搜索树,Linux 内核利用这个数据结构来通过文件内偏移快速定位 Cache 项,图 4 是 radix tree的一个示意图,该 radix tree 的分叉为4(22),树高为4,用来快速定位8位文件内偏移。Linux(2.6.7) 内核中的分叉为 64(26),树高为 6(64位系统)或者 11(32位系统),用来快速定位 32 位或者 64 位偏移,Radix tree 中的每一个到叶子节点的路径上的Key所拼接起来的字串都是一个地址,__指向文件内相应偏移所对应的Cache项__。
|
||||
{{./5}}
|
||||
|
||||
|
||||
查看Page Cache的核心数据结构struct address_space就可以看到上述结构(略去了无关结构):
|
||||
|
||||
struct address_space {
|
||||
struct inode *host; /* owner: inode, block_device */
|
||||
struct radix_tree_root page_tree; /* radix tree of all pages */
|
||||
unsigned long nrpages; /* number of total pages */
|
||||
struct address_space *assoc_mapping; /* ditto */
|
||||
......
|
||||
} __attribute__((aligned(sizeof(long))));
|
||||
下面是一个Radix Tree实例:
|
||||
{{./6}}
|
||||
另一个数据结构是双向链表__,Linux内核为每一片物理内存区域(zone) 维护active_list和inactive_list两个双向链表__,这两个list主要用来实现物理内存的回收。这两个链表上除了文件Cache之 外,还包括其它匿名(Anonymous)内存,如进程堆栈等。
|
||||
|
||||
相关数据结构如下:
|
||||
|
||||
truct page{
|
||||
struct list_head list; //通过使用它进入下面的数据结构free_area_struct结构中的双向链队列
|
||||
struct address_space * mapping; //用于内存交换的数据结构
|
||||
unsigned long index;//当页面进入交换文件后
|
||||
struct page *next_hash; //自身的指针,这样就可以链接成一个链表
|
||||
atomic t count; //用于页面交换的计数,若页面为空闲则为0,分配就赋值1,没建立或恢复一次映射就加1,断开映射就减一
|
||||
unsigned long flags;//反应页面各种状态,例如活跃,不活跃脏,不活跃干净,空闲
|
||||
struct list_head lru;
|
||||
unsigned long age; //表示页面寿命
|
||||
wait_queue_head_t wait;
|
||||
struct page ** pprev_hash;
|
||||
struct buffer_head * buffers;
|
||||
void * virtual
|
||||
struct zone_struct * zone; //指向所属的管理区
|
||||
}
|
||||
typedef struct free_area_struct {
|
||||
struct list_head free_list; //linux 中通用的双向链队列
|
||||
unsigned int * map;
|
||||
} free_area_t;
|
||||
|
||||
typedef struct zone_struct{
|
||||
spinlock_t lock;
|
||||
unsigned long offset; //表示该管理区在mem-map数组中,起始的页号
|
||||
unsigned long free pages;
|
||||
unsigned long inactive_clean_pages;
|
||||
unsigned long inactive_dirty_pages;
|
||||
unsigned pages_min, pages_low, pages_high;
|
||||
struct list_head inactive_clean_list; //用于页面交换的队列,基于linux页面交换的机制。这里存贮的是不活动“干净”页面
|
||||
free_area_t free_area[MAX_ORDER]; //一组“空闲区间”队列,free_area_t定义在上面,其中空闲下标表示的是页面大小,例如:数组第一个元素0号,表示所有区间大小为2的 0次方的页面链接成的双向队列,1号表示所有2的1次方页面链接链接成的双向队列,2号表示所有2的2次方页面链接成的队列,其中要求是这些页面地址连续
|
||||
char * name;
|
||||
unsigned long size;
|
||||
struct pglist_data * zone_pgdat; //用于指向它所属的存贮节点,及下面的数据结构
|
||||
unsigned long zone_start_paddr;
|
||||
unsigned long zone_start_mapnr;
|
||||
struct page * zone_mem_map;
|
||||
} zone_t;
|
||||
|
||||
===== 3. Cache预读与换出 =====
|
||||
Linux 内核中**文件预读**算法的具体过程是这样的:
|
||||
对于每个文件的第一个读请求,系统读入所请求的页面并读入紧随其后的少数几个页面(不少于一个页面,通常是三个页 面),这时的预读称为同步预读。对于第二次读请求,如果所读页面不在Cache中,即不在前次预读的group中,则表明文件访问不是顺序访问,系统继续 采用同步预读;如果所读页面在Cache中,则表明前次预读命中,操作系统把预读group扩大一倍,并让底层文件系统读入group中剩下尚不在 Cache中的文件数据块,这时的预读称为异步预读。无论第二次读请求是否命中,系统都要更新当前预读group的大小。
|
||||
此外,系统中定义了一个 window,它包括前一次预读的group和本次预读的group。任何接下来的读请求都会处于两种情况之一:
|
||||
第一种情况是所请求的页面处于预读 window中,这时继续进行异步预读并更新相应的window和group;
|
||||
第二种情况是所请求的页面处于预读window之外,这时系统就要进行同步 预读并重置相应的window和group。
|
||||
下图是Linux内核预读机制的一个示意图,其中a是某次读操作之前的情况,b是读操作所请求页面不在 window中的情况,而c是读操作所请求页面在window中的情况。
|
||||
|
||||
|
||||
|
||||
Linux内核中文件Cache替换的具体过程是这样的:刚刚分配的Cache项链入到inactive_list头部,并将其状态设置为active,当内存不够需要回收Cache时,系统首先从尾部开始反向扫描 active_list并将状态不是referenced的项链入到inactive_list的头部,然后系统反向扫描inactive_list,如果所扫描的项的处于合适的状态就回收该项,直到回收了足够数目的Cache项。其中Active_list的含义是热访问数据,及多次被访问的,inactive_list是冷访问数据,表示尚未被访问的。如果数据被访问了,Page会被打上一个Refrence标记,如果Page没有被访问过,则打上Unrefrence标记。这些处理在swap.c中可以找到。
|
||||
下图也描述了这个过程。
|
||||
|
||||
|
||||
|
||||
下面的代码描述了一个Page被访问它的标记为变化:
|
||||
|
||||
*
|
||||
* Mark a page as having seen activity.
|
||||
*
|
||||
* inactive,unreferenced -> inactive,referenced
|
||||
* inactive,referenced -> active,unreferenced
|
||||
* active,unreferenced -> active,referenced
|
||||
*/
|
||||
void mark_page_accessed(struct page *page)
|
||||
{
|
||||
if (!PageActive(page) && !PageUnevictable(page) &&
|
||||
PageReferenced(page) && PageLRU(page)) {
|
||||
activate_page(page);
|
||||
ClearPageReferenced(page);
|
||||
} else if (!PageReferenced(page)) {
|
||||
SetPageReferenced(page);
|
||||
}
|
||||
}
|
||||
参考文章:
|
||||
http://lsec.cc.ac.cn/~tengfei/doc/ldd3/
|
||||
http://memorymyann.javaeye.com/blog/193061
|
||||
http://www.cublog.cn/u/20047/showart.php?id=121850
|
||||
http://blog.chinaunix.net/u2/74194/showart_1089736.html
|
||||
关于内存管理,Linux有一个网页:http://linux-mm.org/
|
||||
|
||||
标签: Cache, Linux, Radix Tree
|
||||
BIN
Zim/内核开发/Linux_Cache_机制探究/1
Normal file
|
After Width: | Height: | Size: 36 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/2
Normal file
|
After Width: | Height: | Size: 28 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/3
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/4
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/5
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/6
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/7
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/8
Normal file
|
After Width: | Height: | Size: 5.7 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/9
Normal file
|
After Width: | Height: | Size: 23 KiB |
71
Zim/内核开发/Linux_Cache_机制探究/Page_Cache.txt
Normal file
@@ -0,0 +1,71 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-25T11:38:09+08:00
|
||||
|
||||
====== Page Cache ======
|
||||
Created Thursday 25 October 2012
|
||||
|
||||
http://baike.baidu.com/view/1111885.htm
|
||||
|
||||
===== 一、page cache简介 =====
|
||||
page cache,又称pcache,其中文名称为页高速缓冲存储器,简称页高缓。page cache的大小为一页,通常为4K。在linux读写文件时,它用于缓存文件的逻辑内容,从而加快对磁盘上映像和数据的访问。
|
||||
|
||||
===== 二、page cache的功能详解 =====
|
||||
在从外存的一页到内存的一页的映射过程中,__page cache与buffer cache、swap cache__共同实现了高速缓存功能,以下是其简单映射图:
|
||||
|
||||
外存的一页(分解为几块,可能不连续)
|
||||
|
|
||||
物理磁盘的磁盘块
|
||||
|
|
||||
内存的buffer cache
|
||||
|
|
||||
内存的一页(由一个页框划分的几个连续buffer cache构成)
|
||||
|
|
||||
页高缓系统
|
||||
{{./buffer_head.jpeg}}
|
||||
在这个过程中,内存管理系统和VFS与page cache交互,内存管理系统负责维护每项page cache的分配和回收,同时在使用memory map方式访问时负责建立映射;VFS负责page cache与用户空间的数据交换。
|
||||
bffer cache可以参考:[[内核开发:linux文件系统:块缓冲区和块缓冲区头]] 和 [[内核开发:linux文件系统:块缓冲区和块缓冲区头:buffer cache]]
|
||||
{{./1}}
|
||||
|
||||
===== 三、page cache的管理 =====
|
||||
在Linux内核中,文件的每个数据块最多只能对应一个page cache项,它通过两个数据结构来管理这些cache项,**一个是radix tree,另一个是双向链表**。
|
||||
__一个页高速缓存对象(struct address_space)通常和一个文件关联,而且每个address_space对象都有唯一的radix tree。Radix tree是一种搜索树__,只要指定了文件偏移量,就可以在Radix tree中快速检索到相应的page地址,从而对page cache中的page进行快速定位。图1是radix tree的一个示意图,该radix tree的分叉为4(22),树高为4,用来快速定位8位**文件内偏移**。
|
||||
另一个数据结构是双向链表,Linux内核为__每一片物理内存区域(zone)维护active_list和 inactive_list两个双向链表__,这两个list主要用来**实现物理内存的回收**。这两个链表上除了文件Cache之外,还包括其它匿名 (Anonymous)内存,如进程堆栈等。
|
||||
|
||||
===== 四、page cache相关API及其实现 =====
|
||||
Linux内核中与文件Cache操作相关的API有很多,按其使用方式可以分成两类:一类是以__拷贝方式__操作的相关接口,如read/write/sendfile等,其中sendfile在2.6系列的内核中已经不再支持;另一类是以__地址映射方式__操作的相关接口,如mmap等。
|
||||
第一种类型的API在不同文件的Cache之间或者Cache与应用程序所提供的用户空间buffer之间拷贝数据,其实现原理如图2所示。
|
||||
第二种类型的API__将Cache项映射到用户空间__,使得应用程序可以像使用内存指针一样访问文件,Memory map访问Cache的方式在内核中是__采用请求页面机制实现的__,其工作过程如图3所示。
|
||||
首先,应用程序调用mmap(图中1),陷入到内核中后调用do_mmap_pgoff(图中2)。该函数从应用程序的地址空间中分配一段区域作为映射的内存地址,并使用一个VMA(vm_area_struct)结构代表该区域,之后就返回到应用程序(图中3)。当应用程序访问mmap所返回的地址指针时(图中4),由于虚实映射尚未建立,会触发__缺页中断__(图中5)。之后系统会调用缺页中断处理函数(图中6),__在缺页中断处理函数中,内核通过相应区域的 VMA结构判断出该区域属于文件映射__,于是调用具体文件系统的接口读入相应的Page Cache项(图中7、8、9),并填写相应的虚实映射表。经过这些步骤之后,应用程序就可以正常访问相应的内存区域了。
|
||||
|
||||
===== 加入/离开page cache还涉及到如下几个函数: =====
|
||||
add_page_to_hash_queue /*加入page cache hash表*/
|
||||
add_page_to_inode_queue /*加入inode queue即address_space*/
|
||||
|
||||
remove_page_from_inode_queue
|
||||
remove_page_from_hash_queue
|
||||
|
||||
__remove_inode_page /*离开inode queue和hash 表*/
|
||||
remove_inode_page /*同上*/
|
||||
|
||||
add_to_page_cache_locked /*加入inode queue,hash 和lru cache*/
|
||||
__add_to_page_cache /*同上*/
|
||||
|
||||
仅罗列函数add_page_to_hash_queue,以示完整:
|
||||
static void add_page_to_hash_queue(struct page * page, struct page **p)
|
||||
{
|
||||
struct page *next = *p;
|
||||
*p = page; /* page->newNode */
|
||||
page->next_hash = next; /* +-----+ */
|
||||
page->pprev_hash = p; /* p--> |hashp|-->|oldNode| */
|
||||
if (next) /* next----+ */
|
||||
next->pprev_hash = &page->next_hash;
|
||||
if (page->buffers)
|
||||
PAGE_BUG(page); /*证明page 不会同时存在于page cache 和 buffer cache*/
|
||||
/*2.6 已经与此不同了*/
|
||||
atomic_inc(&page_cache_size);
|
||||
}
|
||||
|
||||
===== page cache 和 inode =====
|
||||
page cache 在代码中又称 __inode page cache__, 足以显示page cache 和inode__紧密关联__.加入page cache 和加入inode cache是同一个意思.加入page cache意味着同时加入page cache hash表和inode queue(也建立了page和addr sapce的关系). 见函数add_to_page_cache_locked,__add_to_page_cache即可取证.从page cache 删除在程序中叫__remove_inode_page,再次显示inode 和page cache的"一体化".
|
||||
{{~/linux地址空间相关的数据结构(取自深入理解Linux虚拟内存管理)}}
|
||||
BIN
Zim/内核开发/Linux_Cache_机制探究/Page_Cache/1
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
Zim/内核开发/Linux_Cache_机制探究/Page_Cache/buffer_head.jpeg
Normal file
|
After Width: | Height: | Size: 26 KiB |
49
Zim/内核开发/Linux_Cache_机制探究/The_Page_Cache_FAQ.txt
Normal file
@@ -0,0 +1,49 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-26T21:36:38+08:00
|
||||
|
||||
====== The Page Cache FAQ ======
|
||||
Created Friday 26 October 2012
|
||||
http://rdc.taobao.com/blog/cs/?p=13
|
||||
|
||||
The Page Cache FAQ(v0.1,欢迎补充与拍砖)
|
||||
The Page Cache FAQ
|
||||
|
||||
本FAQ中所有的操作均以磁盘文件系统为例.
|
||||
|
||||
page cache是linux中加快文件存取速度的重要组成部份。在Linux中,关于文件系统的缓存大致可以分为两类,
|
||||
一类用来缓存文件系统的元数据,比如indoe,dentry,另一类即是page cache,用来缓存文件本身。
|
||||
|
||||
|
||||
1.Q:同一个文件在page cache中是否有可能存在多个副本?
|
||||
A:可能。比如我们有一个文件a.txt,当打开并读取该文件的时候,page cache中会存一份,然后再打开该文件所在的磁盘(如/dev/sda)读取,
|
||||
此时,page cache中就有多个a.txt的副本(读取的内容)存在。
|
||||
|
||||
2.Q:读取磁盘上的文件时,具体是以什么为单位?
|
||||
A:以page size为单位。
|
||||
|
||||
3.Q:page cache里同一个页面里的内容在磁盘上是否连续?
|
||||
A:不一定连续,因为文件在磁盘上未必连续。
|
||||
|
||||
4.Q:page cache和buffer cache是什么关系?
|
||||
A:buffer cache用page cache来实现,主要用来作文件不连续时的缓存和缓存磁盘块(如裸盘读写)。
|
||||
|
||||
5.Q:同一个页面是否可会缓存多个文件的内容?
|
||||
A:不会。
|
||||
|
||||
6.Q:当一个文件被mmap之后再打开,它有几份副本存在于内存中?
|
||||
A:当使用shared映射时,只存在一份。
|
||||
|
||||
7.Q:page cache是否换出?
|
||||
A:不会,刷出(如果需要)后直接回收。
|
||||
|
||||
8.Q:page cache中的写操作以什么样的策略刷回磁盘?
|
||||
A:有几个因素:
|
||||
1) /proc/sys/vm/dirty_background_ratio
|
||||
2) /proc/sys/vm/dirty_ratio
|
||||
3) /proc/sys/vm/dirty_writeback_centisecs
|
||||
9.Q:如何查看当前系统中的磁盘操作?
|
||||
A:echo “1″ > /proc/sys/vm/block_dump
|
||||
|
||||
10.Q:主要有哪些文件系统/硬盘/iscsi/nas性能测试工具?
|
||||
A:我知道的有bonnie/iometer/iozone
|
||||
80
Zim/内核开发/linux中使用MMAP访问内核虚拟地址.txt
Normal file
@@ -0,0 +1,80 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-25T10:41:50+08:00
|
||||
|
||||
====== linux中使用MMAP访问内核虚拟地址 ======
|
||||
Created Thursday 25 October 2012
|
||||
|
||||
mmap是作为用户空间访问内核空间(或者设备空间)等的接口,本文今天讨论的是,使用mmap访问内核虚拟地址空间的内存。
|
||||
|
||||
===== 预备 =====
|
||||
内核地址空间的地址,无论是从kmalloc还是vmalloc获得的,都称为__内核虚拟地址(__即内核可以访问的地址),这些地址都对应着物理内存中的一个个页面(即page)。这里不讨论有关高端内存映射的问题,因为通过正常手段得到了一个虚拟地址后,内核必然已经建立好其中的映射关系以及其中的page结构。kmalloc和vmalloc分配内存最大的不同在于,kmalloc能分配到物理上连续的页,所以kmalloc得到的地址也称为“逻辑地址”(因为是连续的页,所以访问物理内存只需要一个偏移量计算即可,速度快)。系统运行久了以后,连续的地址当然变少,如果在这个时候,分配大片内存,kmalloc得不到满足,而可能需要内核进行移动页面等操作,无益于系统内存的利用和管理。vmalloc分配内存时,不考虑物理内存中是否连续,而使用一个表来转换虚拟地址与物理地址的关系。在分配大内存的时候,vmalloc成功率高,也很好地利用了内存空间。
|
||||
|
||||
为了以下的叙述,必须知道,kmalloc分配到连续的物理内存页,而vmalloc则不连续
|
||||
|
||||
===== mmap的实现 =====
|
||||
在标准的 file_operation 中,mmap的原型为:
|
||||
int mmap(struct file *file, struct vm_area_struct *vma);
|
||||
其中的 struct vm_area_struct 结构体,是__由内核初始化的,里面给出了内核赋予用户空间的虚拟地址范围等信息(所以驱动不用手动构造该结构)__。
|
||||
|
||||
mmap的操作实际上为,将内核空间的地址与页的对应关系,转化为用户空间中的对应关系。
|
||||
|
||||
==== 连续的页映射(kmalloc分配的空间) ====
|
||||
其中最常用到的函数为:
|
||||
int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr, unsigned long pfn, unsigned long size, pgprot_t prot);
|
||||
这个函数就完成“将内核空间的地址与页的对应关系,转化为用户空间中的对应关系”。pfn是Page Frame Number的缩写,即表示一个页的编号。
|
||||
|
||||
从函数名称便可以看出,它”remap”一个”range”的”pfn”,就是重新映射一个范围的页表。也就是只能映射连续的页。
|
||||
因此这个函数只适用于__连续的物理内存页__(即kmalloc或者__get_free_pages获得的)
|
||||
|
||||
调用方法:rempa_pfn_range(vma, vma->vm_start, **vma->vm_pgoff**, vma->vm_end - vm_start, vma->vm_page_prot)
|
||||
|
||||
==== 不连续的页映射(vmalloc分配的空间) ====
|
||||
这种情况可以使用内核提供的vm_operations_struct结构。其结构如下 :
|
||||
struct vm_operations_struct {
|
||||
void (*open)(struct vm_area_struct * area);
|
||||
void (*close)(struct vm_area_struct * area);
|
||||
int __(*fault)__(struct vm_area_struct *vma, struct vm_fault *vmf);
|
||||
/* ..... */
|
||||
}
|
||||
open和close方法不必多说,平常也很少使用。其中的fault原型,指出了内核在找不到某个地址对应的页时,调用的函数。
|
||||
|
||||
于是我们来理一理不连续的页mmap的思路:
|
||||
由于页不连续,不能使用remap_pfn_range,即没有建立地址和页的对应关系,所以在MMAP后,用户访问该范围的某地址时,
|
||||
肯定会发生__缺页异常__,即找不到页!这时会调用fault函数,由驱动来负责寻找这页!怎么找呢?
|
||||
|
||||
首先,我们可以计算一下,用户试图访问的这个地址,离映射起始地址的偏移 offset;
|
||||
然后,通过这个偏移 offset,我们可以得到内核空间中的地址(通过与vmalloc得出的地址相加即可);
|
||||
最后,通过vmalloc_to_page函数,得到我们找到的内核虚拟地址对应的页表。这就是这个用户地址所对应的页表。
|
||||
|
||||
**实例代码:**
|
||||
void * kernel_space_addr; /* 将来在某地分配 */
|
||||
unsigned long kernel_space_size; /* 指定分配空间的大小 */
|
||||
|
||||
static int vma_fault(struct vm_area_struct *vma, struct vm_fault *vmf) {
|
||||
unsigned long offset;
|
||||
void * our_addr;
|
||||
|
||||
offset = (unsigned long)vmf->virtual_address - (unsigned long)vma->vm_start; /* 计算PAGE_FAULT时的偏移量 */
|
||||
if (offset >= kernel_space_size) { return -VM_FAULT_NOPAGE; } /* 这次是真的页错误了 */
|
||||
our_addr = kernel_space_addr + offset; /* 得到该偏移量下的内核虚拟地址 */
|
||||
vmf->page = __vmalloc_to_page__(our_addr); /* 将得到的页面告知内核 */
|
||||
get_page(vmf->page); /* 别忘了增加其引用计数 */
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct vm_operations_struct vmops = {
|
||||
__.fault__ = vma_fault,
|
||||
};
|
||||
|
||||
int mmap(struct file *file, struct vm_area_struct *vma) {
|
||||
vma->vm_ops = &vmops; /* 指定vm_ops */
|
||||
vma->vm_flags |= VM_RESERVED; /* 声明这片内存区不能交换! */
|
||||
return 0;
|
||||
}
|
||||
|
||||
不连续页的另外一个实现:__remap_vmalloc_range__
|
||||
|
||||
这是2.6.18后的内核版本实现的,使用方法也很简单:
|
||||
int remap_vmalloc_range(struct vm_area_struct *vma, void *addr,unsigned long pgoff);
|
||||
addr为vmalloc分配的地址,pgoff为页偏移量(一般为0即可)。
|
||||
7
Zim/内核开发/linux文件系统.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-24T10:12:31+08:00
|
||||
|
||||
====== linux文件系统 ======
|
||||
Created Wednesday 24 October 2012
|
||||
|
||||
72
Zim/内核开发/linux文件系统/Filesystem_in_Userspace_(FUSE).txt
Normal file
@@ -0,0 +1,72 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-24T10:24:51+08:00
|
||||
|
||||
====== Filesystem in Userspace (FUSE) ======
|
||||
Created Wednesday 24 October 2012
|
||||
http://en.wikipedia.org/wiki/Filesystem_in_Userspace
|
||||
From Wikipedia, the free encyclopedia
|
||||
|
||||
{{./280px-FUSE_structure.svg.png?width=564}}
|
||||
A flow-chart diagram showing how FUSE works
|
||||
|
||||
Filesystem in Userspace (FUSE) is __a loadable kernel module__ for Unix-like computer operating systems that __lets non-privileged users create their own file systems__ without editing kernel code. This is achieved by running file system code in user space while the FUSE module provides only __a "bridge"__ to the actual kernel interfaces.
|
||||
|
||||
Released under the terms of the GNU General Public License and the GNU Lesser General Public License, FUSE is free software. The FUSE system was originally part of A Virtual Filesystem (AVFS), but has since split off into its own project on SourceForge.net.
|
||||
|
||||
FUSE is available for Linux, FreeBSD, NetBSD (as puffs), OpenSolaris, and OS X.[1] It was officially merged into the mainstream Linux kernel tree in kernel version 2.6.14.[2]
|
||||
|
||||
===== Virtual file system =====
|
||||
FUSE is particularly useful for writing virtual file systems. Unlike traditional file systems that essentially save data to and retrieve data from disk, virtual filesystems do not actually store data themselves. __They act as a view or translation of an existing file system or storage device__.
|
||||
|
||||
In principle, any resource available to a FUSE implementation can be exported as a file system.
|
||||
|
||||
===== Examples =====
|
||||
Wuala: A multi-platform, Java-based fully OS integrated distributed file system. Using FUSE, MacFUSE and Callback File System respectively for file system integration, in addition to a Java-based app accessible from any Java-enabled web browser.
|
||||
MyVDrive: Fully OS integrated Internet file system using FUSE.
|
||||
__WebDrive:__ A commercial filesystem implementing WebDAV, SFTP, FTP, FTPS and Amazon S3
|
||||
jSYS: A commercial software to create jails and virtual filesystems in user-space using FUSE.
|
||||
Transmit: A commercial FTP client that also adds the ability to mount WebDAV, SFTP, FTP and Amazon S3 servers as disks in Finder, via MacFUSE.
|
||||
ExpanDrive: A commercial filesystem implementing SFTP/FTP/FTPS using FUSE
|
||||
GlusterFS: Clustered Distributed Filesystem having ability to scale up to several petabytes.
|
||||
SSHFS: Provides access to a remote filesystem through SSH
|
||||
FTPFS
|
||||
__GmailFS:__ Filesystem which stores data as mail in Gmail
|
||||
GAEDrive: A Network Storage based on Google App Engine
|
||||
gae-filestore: Virtual File System library on Google App Engine
|
||||
__GVFS:__ The virtual filesystem for the GNOME desktop
|
||||
EncFS: Encrypted virtual filesystem
|
||||
__NTFS-3G__ and Captive NTFS, allowing access to NTFS filesystems
|
||||
exFAT: The Extended File Allocation Table file system from Microsoft
|
||||
WikipediaFS: View and edit Wikipedia articles as if they were real files
|
||||
Sun Microsystems's Lustre cluster filesystem will use FUSE to allow it to run in userspace, so that a FreeBSD port is possible.[3] However, the ZFS-Linux port of Lustre will be running ZFS's DMU (Data Management Unit) in userspace.[4]
|
||||
archivemount
|
||||
LoggedFS: Logging of file system access
|
||||
__HDFS:__ FUSE bindings exist for the open source **Hadoop distributed filesystem**
|
||||
mtpfs: mounting MTP devices like Creative Zen music players
|
||||
Sector File System: Sector is a distributed file system designed for large amount of commodity computers. Sector uses FUSE to provide a mountable local file system interface
|
||||
CurlFtpFS Filesystem to access FTP/SFTP locations.
|
||||
fuse-ext2 An open source ext2/ext3 file system. (Supports OS X 10.4 and later (Universal Binary), using MacFuse)
|
||||
Lessfs: inline data de-duplicating filesystem for Linux that includes support for lzo or QuickLZ compression and encryption.
|
||||
__CloudStore__ (formerly, Kosmos filesystem): By mounting via FUSE, existing Linux utilities can interface with CloudStore
|
||||
SoundCloudFS: An open source filesystem that allows Linux systems to mount SoundCloud streams so that they can be opened with your own choice of software.
|
||||
MooseFS: An open source distributed fault-tolerant file system available on every OS with FUSE implementation (Linux, FreeBSD, NetBSD, OpenSolaris, OS X), able to store petabytes of data spread over several servers visible as one resource
|
||||
NagusFS: Filesystem representation of Nagios services.
|
||||
NagiosFS (http://sourceforge.net/apps/mediawiki/fuse/index.php?title=NetworkFileSystems#NagiosFS): Filesystem representation of remote monitoring values
|
||||
CassandraFS (https://code.launchpad.net/cassandrafs): Filesystem over Cassandra (cassandra.apache.org)
|
||||
ZFS: ZFS-Fuse-Linux implementation
|
||||
fuse-zip: Allows to use zip files as a filesystem (supports writing)
|
||||
OWFS [1] One-Wire File System giving access to 1-Wire devices via a file system directory structure
|
||||
TrueCrypt: a software application used for on-the-fly encryption (OTFE). It can create a virtual encrypted disk within a file as well as encrypt a partition or entire storage device
|
||||
s3fs-FuseOverAmazonS3: A FUSE-based file system backed by Amazon S3. Mount a bucket as a local file system read/write. Store files/folders natively and transparently on AWS: Simple Storage Service
|
||||
s3fs-c: A file system backed by Amazon S3. Forked from s3fs and rewritten to be compatible with some other S3 clients such as AWS Management Console
|
||||
LR|FS [2]: An OS X file system for Adobe Lightroom catalogs. Requires MacFuse
|
||||
boxfs: A file system for accessing files on your box.net account
|
||||
remotefs: Network file system designed for use with home NAS
|
||||
virtualbox-fuse:virtualbox-fuse allows mounting of Virtualbox VDI images
|
||||
Fuse for FreeBSD
|
||||
Fuse4X is a port of Fuse to OS X
|
||||
MacFUSE is an old port of Fuse to Mac OS X; no longer maintained
|
||||
OSXFuse is a port of Fuse to OS X and a successor to MacFUSE
|
||||
Dokan Windows user mode file system library
|
||||
UsiFe A flexible file system that allows intra file encryption. It is possible to selectively encrypt/decrypt parts of a file and then display them.
|
||||
|
After Width: | Height: | Size: 14 KiB |
126
Zim/内核开发/linux文件系统/GIO.txt
Normal file
@@ -0,0 +1,126 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-24T10:20:15+08:00
|
||||
|
||||
====== GIO ======
|
||||
Created Wednesday 24 October 2012
|
||||
http://en.wikipedia.org/wiki/GIO_(GNOME)
|
||||
From Wikipedia, the free encyclopedia
|
||||
|
||||
GIO is a software library from the GNOME software stack, designed to __present programmers with a modern and usable interface to a virtual file system__. It allows applications to access local and remote files __with a single consistent API__, which was designed "to overcome the shortcomings of GnomeVFS" and be "so good that developers prefer it over raw POSIX calls."[1] It ships as a separate library, libgio-2.0, bundled with GLib.
|
||||
|
||||
===== Features =====
|
||||
* The abstract file system model of GIO consists of a number of interfaces and base classes for I/O and files.
|
||||
* There is a number of stream classes, similar to the input and output stream hierarchies that can be found in frameworks like Java.
|
||||
* There are interfaces related to applications and the types of files they handle.
|
||||
* There is a framework for storing and retrieving application settings.
|
||||
* There is support for network programming, including name resolution, lowlevel socket APIs and highlevel client and server helper classes.
|
||||
* There is support for connecting to __D-Bus__, sending and receiving messages, owning and watching bus names, and making objects available on the bus.
|
||||
Beyond these, GIO provides facilities for **file monitoring**, asynchronous I/O and filename completion. In addition to the interfaces, GIO provides implementations for the local case. Implementations for various network file systems are provided by the GVFS package as loadable modules.
|
||||
|
||||
===== Example =====
|
||||
A simple example illustrating local file handling using GIO in C:
|
||||
|
||||
#include <glib/gprintf.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#define _g_free0(var) (var = (g_free (var), NULL))
|
||||
#define _g_object_unref0(var) ((var == NULL) ? NULL : (var = (g_object_unref (var), NULL)))
|
||||
|
||||
void log_g_error(GError * e, int line) {
|
||||
g_printerr ("** ERROR **: %s (domain: %s, code: %d) at %d\n",
|
||||
e->message, g_quark_to_string (e->domain), e->code, line);
|
||||
}
|
||||
|
||||
int main (int argc, char ** argv) {
|
||||
g_type_init ();
|
||||
if (argc < 2) {
|
||||
g_message ("Usage: %s file-name", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
GFile* file = NULL;
|
||||
GFileInfo* fileinfo = NULL;
|
||||
GFileType filetype = G_FILE_TYPE_UNKNOWN;
|
||||
const char* contenttype = NULL;
|
||||
gchar* contenttype_desc = NULL;
|
||||
GFileInputStream* fis = NULL;
|
||||
GDataInputStream* dis = NULL;
|
||||
gchar* line = NULL;
|
||||
GError* inner_error = NULL;
|
||||
int error_line = 0;
|
||||
file = g_file_new_for_path (argv[1]);
|
||||
fileinfo = g_file_query_info (file, "standard::*", G_FILE_QUERY_INFO_NONE, NULL, &inner_error);
|
||||
if (inner_error == NULL) {
|
||||
filetype = g_file_info_get_file_type(fileinfo);
|
||||
contenttype = g_file_info_get_attribute_string (fileinfo, G_FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE);
|
||||
contenttype_desc = g_content_type_get_description(contenttype);
|
||||
g_printf ("\
|
||||
=== file-description ===\n\
|
||||
Content type: %s (%s)\n\
|
||||
Size: %lluB\n\
|
||||
=== description-end ===\n\
|
||||
", contenttype, contenttype_desc, g_file_info_get_attribute_uint64 (fileinfo, G_FILE_ATTRIBUTE_STANDARD_SIZE));
|
||||
_g_free0 (contenttype_desc);
|
||||
//_g_object_unref0 (fileinfo); //if you do this here, 'contenttype' gets destroyed.
|
||||
switch (filetype) {
|
||||
case G_FILE_TYPE_REGULAR:
|
||||
case G_FILE_TYPE_SYMBOLIC_LINK:
|
||||
if (!g_content_type_is_a(contenttype, "text/*")) {
|
||||
g_printerr ("Not a text-based document.\n");
|
||||
break;
|
||||
}
|
||||
fis = g_file_read (file, NULL, &inner_error);
|
||||
if (inner_error != NULL) {
|
||||
_g_object_unref0 (fis);
|
||||
error_line = __LINE__;
|
||||
break;
|
||||
}
|
||||
dis = g_data_input_stream_new (G_INPUT_STREAM(fis));
|
||||
_g_object_unref0 (fis);
|
||||
while (TRUE) {
|
||||
line = g_data_input_stream_read_line (dis, NULL, NULL, &inner_error);
|
||||
if (inner_error != NULL) {
|
||||
_g_free0 (line);
|
||||
error_line = __LINE__;
|
||||
break;
|
||||
}
|
||||
if (line == NULL)
|
||||
break;
|
||||
g_printf ("%s\n", line);
|
||||
_g_free0 (line);
|
||||
}
|
||||
_g_object_unref0 (dis);
|
||||
break;
|
||||
case G_FILE_TYPE_DIRECTORY:
|
||||
g_printerr ("** INFO: Can't read a directory.\n");
|
||||
break;
|
||||
case G_FILE_TYPE_SPECIAL:
|
||||
g_printerr ("** INFO: It's a \"special\" file.\n");
|
||||
break;
|
||||
case G_FILE_TYPE_SHORTCUT:
|
||||
g_printerr ("** INFO: The given file is a shortcut.\n");
|
||||
break;
|
||||
case G_FILE_TYPE_MOUNTABLE:
|
||||
g_printerr ("** INFO: File is a mountable location.\n");
|
||||
break;
|
||||
case G_FILE_TYPE_UNKNOWN:
|
||||
g_printerr ("** INFO: Unknown file type.\n");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_g_object_unref0 (fileinfo);
|
||||
_g_object_unref0 (file);
|
||||
if (inner_error != NULL) {
|
||||
log_g_error (inner_error, error_line);
|
||||
g_clear_error (&inner_error);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
The above program takes a filename as an argument at runtime, prints its content-type and size, and if the input file is text-based, prints its contents too. To compile the above program in Linux using GCC and pkg-config, use:
|
||||
|
||||
gcc -Wall -g gio-example.c -o gio-example $(pkg-config --cflags --libs gio-2.0)
|
||||
assuming the source file is gio-example.c. To execute the output file, use:
|
||||
|
||||
./gio-example filename
|
||||
280
Zim/内核开发/linux文件系统/Linux文件系统FAQ.txt
Normal file
@@ -0,0 +1,280 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-09-09T23:57:18+08:00
|
||||
|
||||
====== Linux文件系统FAQ ======
|
||||
Created Friday 09 September 2011
|
||||
http://blog.chinaunix.net/space.php?uid=20196318&do=blog&id=28767
|
||||
|
||||
|
||||
Q: 文件系统如何看待底层物理块设备?
|
||||
|
||||
Ÿ 文件系统把块设备简单的看做线性的组合,即对文件系统而言,块设备是一系列可以读写的块。文件系统不需要知道这些物理设备的实际布局及如何读写,这些是设备驱动的工作。
|
||||
|
||||
|
||||
|
||||
Q: 跟文件系统相关的系统调用主要有那些?
|
||||
|
||||
Ÿ 打开文件open,读取文件read,写文件write,关闭文件close,删除文件unlink,创建目录mkdir,删除目录rmdir等,linux通过VFS提供了符合POSIX标准的接口。
|
||||
|
||||
|
||||
|
||||
Q: 如何实现一个文件系统?
|
||||
|
||||
Ÿ 实现VFS提供的一组文件系统操作接口,向内核注册;
|
||||
|
||||
Ÿ 实现用户空间文件系统或堆叠式文件系统;
|
||||
|
||||
|
||||
|
||||
Q: VFS如何管理super_block,inode,dentry,file,vfsmount等主要数据结构?
|
||||
|
||||
Ÿ 参见http://blog.chinaunix.net/u2/87570/showart_2126000.html;
|
||||
|
||||
|
||||
|
||||
Q: 哪些接口必须实现?
|
||||
|
||||
Ÿ VFS实现了很多通用接口,如基本所有的读写操作都可直接使用generic_file_aio_read,generic_file_aio_write接口(我的内核版本为2.6.19),ext2的读写就是使用该接口,但各个文件系统必须实现自己的read_page方法,用于从磁盘读取一页的数据(还可实现read_pages,一次读取多页,以提高效率),如果要实现direct_io访问,必须实现direct_IO接口。
|
||||
|
||||
Ÿ read_page的实现需要基于文件系统实际的数据组织,它将用户的文件请求位置(逻辑页号)转换为物理块号,并向通用块层发送请求。
|
||||
|
||||
|
||||
|
||||
Q: ext2文件系统如何组织文件的数据?
|
||||
|
||||
Ÿ ext2使用长度为15的数组(ext2_inode的一个字段),其中前12个数组元素记录直接块映射,即其内容即为文件前12个块的地址;第13个元素,记录一级索引关系,即该元素的内容为一个块的地址,这个块的内容为一系列块的地址;第14,15个元素分别为二级索引,三级索引。
|
||||
|
||||
|
||||
|
||||
Q: 内核如何根据用户态传递的路径名得到文件的inode,dentry信息?
|
||||
|
||||
Ÿ 通过路径名查找可以通过路径名得到inode,dentry的信息;
|
||||
|
||||
Ÿ Linux提供了path_lookup接口来实现路径名的解析,其具体实现以下工作:
|
||||
|
||||
1. 获取路径名查找的起点,当前目录或是根目录;
|
||||
|
||||
2. 以/为分隔符,解析每个目录项。
|
||||
|
||||
3. 针对每个目录项,首先查找目录项高速缓存,判断当前的目录项对象是否在缓存中,如果在,则直接从缓存中获取结果;如果不在,则需要在上一级目录中调用实际文件系统实现的lookup方法查找,并读取目录项对应的inode信息,填充dentry结构,并将该结构加入到高速缓存。
|
||||
|
||||
|
||||
|
||||
Q: 内核如何根据路径名查找的结果得到file结构?
|
||||
|
||||
Ÿ 通过dentry_open实现,具体执行以下工作:
|
||||
|
||||
1. 分配一个文件对象;
|
||||
|
||||
2. 根据传递进来的dentry信息,vfsmnt信息,初始化file对象的f_fentry,f_vfsmnt;
|
||||
|
||||
3. 以索引节点的i_fop填充f_op。
|
||||
|
||||
4. 将文件对象插入到文件系统超级块的s_files字段所指向的链表中。
|
||||
|
||||
|
||||
|
||||
Q: 索引节点的i_fop,i_op,i_mapping的a_ops字段何时被初始化?
|
||||
|
||||
Ÿ 具体文件系统读取索引节点时初始化,如ext2的ext2_read_inode方法;
|
||||
|
||||
Ÿ 在ext2_read_inode中,该方法根据get_ext2_inode从磁盘上读取ext2_inode,并根据ext2_inode的信息初始化vfs的inode,如mode,uid,gid,timestamp等,并根据文件的类型不同,将i_fop,i_op,i_mapping的a_ops初始化为相应的方法。
|
||||
|
||||
1. 对于普通的文件,三者的值分别为ext2_file_operations,ext2_file_inode_operations,ext2_aops;
|
||||
|
||||
2. 对于目录三者的值分别为ext2_dir_operations,ext2_dir_inode_operations,ext2_aops;
|
||||
|
||||
3. 对于链接文件i_op被赋值为ext2_symlink_inode_operations;
|
||||
|
||||
4. 对于其他的类型的文件,如块设备,字符设备,fifo管道,socket,则通过init_special_inode进行初始化。其相应的被初始化为def_blk_fops,def_chr_fops,def_fifo_fops,bad_sock_fops;
|
||||
|
||||
|
||||
|
||||
Q: 对于打开的文件,在用户态以fd标识,在内核态以file结构标识,fd与 file如何对应?
|
||||
|
||||
Ÿ 每一个进程由一个task_struct结构描述,其中的files字段是一个files_struct的结构,主要描述文件打开的文件信息,包括fd使用位图,files对象数组fd_array,其中fd_array的下标即对应着该file对象对应的fd。
|
||||
|
||||
Ÿ 当进程通过路径名获取到file对象后,会将file对象的指针放入fd_array数组的相应位置;
|
||||
|
||||
|
||||
|
||||
Q: direct io是怎么回事?
|
||||
|
||||
Ÿ 直接IO(direct io)是指读写文件系统数据时绕过页高速缓存。
|
||||
|
||||
Ÿ 具体文件系统支持直接IO需要实现a_ops中的direct_IO方法;
|
||||
|
||||
Ÿ 不管是直接IO,还是经过页高速缓存的IO操作,都是将请求通过bio发到通用块层来实现的。
|
||||
|
||||
|
||||
|
||||
Q: 高速缓存页分哪些类型?
|
||||
|
||||
Ÿ 含有普通文件数据或目录的页;
|
||||
|
||||
Ÿ 含有直接从块设备文件,跳过文件系统层,读出来的数据的页;
|
||||
|
||||
Ÿ 含有用户态进程数据的页,但页中的数据已经被交换到磁盘;
|
||||
|
||||
Ÿ 属于特殊文件系统的页,如共享内存的IPC所使用的特殊文件系统shm;
|
||||
|
||||
|
||||
|
||||
Q: 页高速缓存中页的数据都是不同的么?
|
||||
|
||||
Ÿ 页高速缓存可以包含同一磁盘数据的多个副本,可以一下两种方式可以访问普通文件的同一块:
|
||||
|
||||
1. 读文件,此时,数据包含在普通文件索引节点所拥有的页中;
|
||||
|
||||
2. 从文件所在的设备文件(磁盘分区)读取块,此时,数据就包含在块设备文件的主索引节点所拥有的块中。
|
||||
|
||||
|
||||
|
||||
Q: 页高速缓存如何组织?
|
||||
|
||||
Ÿ Linux支持TB级的文件,访问大文件时,页高速缓存中可能充满太多的文件页,因此需要对这些页进行高效的组织,使得内核能迅速高效的查找页。
|
||||
|
||||
Ÿ Linux采用基树(radix tree)对页高速缓存进行组织,添加,删除,查找页的操作的时间复杂度都为O(1)。Linux提供一组方法方法用于处理页高速缓存:
|
||||
|
||||
find_get_page()接受address_space对象指针及偏移量,返回对应的页描述符;
|
||||
|
||||
find_get_pages()查找一组具有相邻索引的页;
|
||||
|
||||
add_to_page_cache()把一个新页的描述符插入到页高速缓存;
|
||||
|
||||
remove_from_page_cache()将页从高速缓存中移除;
|
||||
|
||||
read_cache_page()确保高速缓存中包含最新版本的指定页;
|
||||
|
||||
|
||||
|
||||
Q: 缓冲区页于页内缓冲区的关系?
|
||||
|
||||
Ÿ 如下图所示:page结构的private字段指向第一个缓冲区首部,各个缓冲区首部通过b_this_page链接,并通过b_page指向包含自己的page结构,b_data为其相对于页的位置。当页在高端内存时,b_data为缓冲区块在业内的偏移量;否则,b_data为缓冲区的线性地址,因高端内存页没有固定的映射。
|
||||
{{~/sync/notes/zim/内核开发/Linux文件系统FAQ/100325203524.jpg}}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Q: 什么情况下内核会创建缓冲区页?
|
||||
|
||||
Ÿ 当读或写的文件页在磁盘块上不相邻时,即文件系统为文件分配了非连续的块,或者因为文件有洞;在块大小与页大小相等的情况下,这种情况不会出现。
|
||||
|
||||
Ÿ 当访问一个单独的磁盘块时(如读超级块或索引节点块)。
|
||||
|
||||
|
||||
|
||||
Q: 如何创建和释放缓冲区页?
|
||||
|
||||
Ÿ 调用grow_buffers(),其具体执行如下步骤:
|
||||
|
||||
1. 如果对应的页不在块设备的基树中,需创建新的页。
|
||||
|
||||
2. 调用alloc_page_buffer()为页创建缓冲区,一次创建页能容纳的所有缓冲区,并建立好链接关系,进行必要的初始化。
|
||||
|
||||
Ÿ 调用try_to_free_page()释放缓冲区页。
|
||||
|
||||
|
||||
|
||||
Q: 如何在页高速缓存中搜索块?
|
||||
|
||||
Ÿ 将块号转换成页号索引,并通过基树提供的接口进行查找。
|
||||
|
||||
Ÿ __find_get_block(), __getblk()接口提供搜索块的接口,根据给定的设备信息及块号,块大小,返回块对应的buffer_head结构,后者在块所在的缓冲区页不存在时会分配缓冲区页,创建缓冲区块,并返回对应块的buffer_head结构。
|
||||
|
||||
Ÿ 为了提高系统性能,内核维持了一个小的磁盘高速缓存数组bh_lrus(每CPU变量),数组包含8个元素,指向最近访问过的缓冲区首部。
|
||||
|
||||
|
||||
|
||||
Q: 如何向通用块层提交缓冲区首部?
|
||||
|
||||
Ÿ 使用submit_bh()向通用块层传递一个缓冲区首部,使用ll_rw_block可向通用块层传递一组缓冲区首部;两者都附带读写传输方向标志。
|
||||
|
||||
Ÿ sumbit_bh()根据缓冲区首部内容创建一个bio,具体执行如下步骤:
|
||||
|
||||
1. 调用bio_alloc分配一个bio描述符;
|
||||
|
||||
2. 将bi_sector字段赋值为bh->b_blocknr * bh->b_size / 512;
|
||||
|
||||
3. 将bi_bdev字段赋值为bh->b_bdev;
|
||||
|
||||
4. 把bi_size设置为块大小bh->b_size;
|
||||
|
||||
5. 初始化bi_io_vec的第一个元素以使该段对应于块缓冲区;
|
||||
|
||||
bi_io_vec[0].bv_page = bh->b_page;
|
||||
|
||||
bi_io_vec[0].bv_len = bh->b_size;
|
||||
|
||||
bi_io_vec[0].bv_offset = bh->b_data;
|
||||
|
||||
6. 将bi_cnt字段置1,并把bi_idx置为0;
|
||||
|
||||
7. 将bi_end_io字段赋值为end_bh_bio_sync,bi_private字段赋值为缓冲区首部地址。
|
||||
|
||||
作为数据传输完毕后的执行方法,数据传输完后,通过bi_private获取buffer_head结构,执行期bi_end_io字段的方法。
|
||||
|
||||
8. 调用submit_bio将bio提交到通用块层。
|
||||
|
||||
Ÿ ll_rw_block对数组中每个buffer_head调用submit_bh。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Q: 页高速缓存何时被刷新?
|
||||
|
||||
Ÿ 基于性能考虑,linux系统采用延迟写策略,即将把脏缓冲区写入块设备的操作延迟执行;基于延迟写,几次写操作可能只需要一次物理更新。从而使得物理块设备平均为读请求服务的时间多于写请求。
|
||||
|
||||
Ÿ 以下条件会触发把脏页写到磁盘:
|
||||
|
||||
1. 页高速缓存变得太满,但还需要更多的页,或者脏页的数量已经太多;
|
||||
|
||||
2. 从页变成脏页的时间太长,超过某一阈值;
|
||||
|
||||
3. 进程请求或者特定文件系统特定的变化。如同过sync(),fsync(),fdatasync()系统调用实现;
|
||||
|
||||
Ÿ Linux通过pdflush内核线程系统地扫描页高速缓存以刷新脏页,pdflush线程的数量随着系统运行动态调整,具体原则如下:
|
||||
|
||||
1. 必须有至少两个,之多八个pdflush内核线程;
|
||||
|
||||
2. 如果到最近的1s期间没有空闲的pdflush,就应该创建新的pdflush;
|
||||
|
||||
3. 如果最近一次pdflush变为空闲的时间超过1s,就应该删除一个pdflush;
|
||||
|
||||
4. 通过定期唤醒的pdflush保证陈旧的页及时写回,页保持脏状态的最长时间为30s;
|
||||
|
||||
|
||||
|
||||
Q: sync(), fsync(),fdatasync()系统调用区别?
|
||||
|
||||
Ÿ sync()把所有的脏缓冲区刷新到磁盘;
|
||||
|
||||
Ÿ fsync()把属于特定打开文件的所有块刷新到磁盘;
|
||||
|
||||
Ÿ fdatasync()与fsync()类似,但不刷新文件的索引节点块;
|
||||
|
||||
|
||||
|
||||
Q: linux文件系统如何预读取?
|
||||
|
||||
Ÿ 为了保证预读命中率,linux只对顺序读进行预读,内核通过如下条件判断read()是否为顺序读:
|
||||
|
||||
1. 这是文件被打开后的第一次读,并且从文件头开始读;
|
||||
|
||||
2. 当前的读请求与前一次读请求在文件内的位置是连续的;
|
||||
|
||||
否则为随机读,任何一次随机读将终止预读,而不是缩减预读窗口。
|
||||
|
||||
Ÿ 当确定了需要进行预读时,就需要确定合适的预读大小,预读粒度太小,效果提升不明显;预读太多,可能载入太多不需要的页面而造成资源浪费;linux的策略是:
|
||||
|
||||
1. 首次预读:readahead_size = read_size * 2; // or *4;
|
||||
|
||||
2. 后续预读:readahead_size = readahead_size * 2;
|
||||
|
||||
3. 系统设定的最大预读大小为128K,该值可配置;
|
||||
|
||||
|
||||
BIN
Zim/内核开发/linux文件系统/Linux文件系统FAQ/100325203524.jpg
Normal file
|
After Width: | Height: | Size: 34 KiB |
260
Zim/内核开发/linux文件系统/The_Virtual_File_System_in_Linux.txt
Normal file
@@ -0,0 +1,260 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-24T12:57:07+08:00
|
||||
|
||||
====== The Virtual File System in Linux ======
|
||||
Created Wednesday 24 October 2012
|
||||
http://www.linux.it/~rubini/docs/vfs/vfs.html
|
||||
(May 1997)
|
||||
Reprinted with permission of Linux Journal
|
||||
|
||||
This article outlines the VFS idea and gives an overview of the how the Linux kernel accesses its file hierarchy. The information herein refers to Linux 2.0.x (for any x) and 2.1.y (with y up to at least 18). The sample code, on the other hand, is for 2.0 only.
|
||||
by Alessandro Rubini
|
||||
|
||||
The main data item in any Unix-like system is the __``file''__, and __an unique pathname__ identifies each file within a running system. Every file appears like any other file in the way is is accessed and modified: the same system calls and the same user commands apply to every file. This applies independently of both the physical medium that holds information and the way information is laid out on the medium. __Abstraction from the physical storage of information__ is accomplished by dispatching data transfer to different device drivers; __abstraction from the information layout__ is obtained in Linux through the VFS implementation.
|
||||
|
||||
===== The Unix way =====
|
||||
Linux looks at its file-system in the way Unix does: it adopts the concepts of __super-block, inode, directory and file__ in the way Unix uses them. The tree of files that can be accessed at any time is determined by how the different parts are assembled together, each part being a partition of the hard driver or another physical storage device that is ``mounted'' to the system.
|
||||
|
||||
While the reader is assumed to be confident with the idea of mounting a file-system, I'd better detail the concepts of super-block, inode, directory and file.
|
||||
|
||||
* The **super-block** owes its name to its historical heritage, from when **the first data block of a disk or partition** was used to hold **meta-information about the partition itself**. The super-block is now detached from the concept of data block, but still is __the data structure that holds information about each mounted file-system__. The actual data structure in Linux is called **struct super_block** and hosts various housekeeping information, like //mount flags, mount time and device blocksize//. The 2.0 kernel keeps a static array of such structures to handle up to 64 mounted file-systems.
|
||||
* An **inode** is __associated to each file__. Such __an ``index node'' encloses all the information about a named file except its name and its actual data__. The owner, group, permissions and access times for a file are stored in its inode, as well as the size of the data it holds, the number of links and other information. The idea of detaching file information from filename and data is what __allows to implement hard-links__ -- and to use the `dot' and `dot-dot' notations for directories without any need to treat them specially. An inode is described in the kernel by a **struct inode**.
|
||||
* The **directory** is a file that __associates inodes to filenames__. The kernel has no special data strcture to represent a directory, which is treated like a normal file in most situations. Functions specific to each filesystem-type are used to read and modify the contents of a directory independently of the actual layout of its data.
|
||||
* The **file** itself is something that is //associated to an inode//. Usually files are data areas, but they can also be directories, devices, FIFO's or sockets. An ``open file'' is described in the Linux kernel by a **struct file** item; the structure encloses a pointer to the inode representing the file. file structures are created by system calls like open, pipe and socket, and are shared by father and child across fork.
|
||||
|
||||
===== Object Orientedness =====
|
||||
While the previous list describes the theoretical organization of information, an operating system must be able to deal with different ways to __layout information on disk__. While it is theoretically possible to __look for an optimum layout of information on disks and use it for every disk partition__, most computer users need to access all of their hard drives without reformatting, mount NFS volumes across the network, and sometimes even access those funny CDROM's and floppy disks whose filenames can't exceed 8+3 characters.
|
||||
|
||||
The problem of handling different data formats in a transparent way has been addresses by __making super-blocks, inodes and files into ``objects''__: an object declares a set of operations that must be used to deal with it. The kernel won't be stuck into big switch statements to be able to access the different physical layouts of data, and new ``filesystem types'' can be added and removed at run time.
|
||||
|
||||
All the VFS idea, therefore, is implemented around sets of operations to act on the objects. Each object includes a structure declaring its own operations, and most operations receive a pointer to the ``self'' object as first argument, thus allowing modification of the object itself.
|
||||
|
||||
In practice, a super-block structure, encloses a field ``**struct super_operations *s_op**'', an inode encloses ``**struct inode_operations *i_op**'' and a file encloses ``**struct file_operations *f_op**''.
|
||||
|
||||
All the data handling and buffering that is performed by the Linux kernel is independent of the actual format of the stored data: every communication with the storage medium passes through one of the operations structures. The ``**file-system type**'', then, is the software module that is in charge of mapping the operations to the actual storage mechanism -- either a block device, a network connection (NFS) or virtually any other mean to store/retrieve data. These software modules implementing filesystem types can either be linked to the kernel being booted or actually compiled in the form of loadable modules.
|
||||
|
||||
The current implementation of Linux allows to use loadable modules for all the filesystem types but __the root filesystem__ (the root filesystem must be mounted before loading a module from it). Actually, __the initrd machinery__ allows to load a module before mounting the root filesystem, but this technique is usually only exploited in installation floppies.
|
||||
|
||||
In this article I use the phrase ``filesystem module'' to refer independently to a loadable module or a filesystem decoder linked to the kernel.
|
||||
|
||||
This is in summary how all the file handling happens for any given file-system type, and is depicted in figure 1.
|
||||
{{./lj-vfs.png}}
|
||||
Figure 1: VFS data structure (available as PostScript here
|
||||
|
||||
**struct file_system_type** is a structure that declares only its own name and **a read_super function**. At mount time, the function is passed information about the storage medium being mounted and is asked to fill a super-block structure, as well al loading **the inode of the root directory** of the filesystem as **sb-">"s_mounted** (where sb is the super-block just filled). The additional field **requires_dev** is used by the filesystem type to state if it will access __a block device or not__: for example, the NFS and proc types don't require a device, while ext2 and iso9660 do. After the superblock is filled, struct file_system_type is not used any more; only the superblock just filled will hold a pointer to it in order to be able to give back status information to the user (**/proc/mounts** is an example of such information). The structure is shown in panel 1.
|
||||
|
||||
struct file_system_type中的read_super函数的功能是:
|
||||
1.在mount含有该文件系统的存储媒介时,读取该媒介的信息用以填充该媒介对应的文件系统的struct super_block。
|
||||
2.从媒介中读取其文件系统根目录的inode信息用以填充文件系统根目录的struct inode,同时将sb->s_mounted指向该inode。
|
||||
|
||||
**Panel 1**
|
||||
struct file_system_type {
|
||||
struct super_block *(*read_super) (struct super_block *, void *, int);
|
||||
const char *name;
|
||||
int requires_dev;
|
||||
struct file_system_type * next; /* there's a linked list of types */
|
||||
};
|
||||
|
||||
The __structure super_operations__ is used by the kernel to read/write inodes, write superblock information back to disk and collect statistics (to deal with the **statfs** and **fstatfs** system calls). When a filesystem is eventually unmounted, the put_super operation is called -- in standard kernel wording ``get'' means ``allocate and fill'', ``read'' means ``fill'' and ``put'' means ``release''. **The super_operations declared by each filesystem type** are shown in panel 2.
|
||||
|
||||
每个文件系统类型module还要声明一个与之相关的读取、写入、释放inode和supter block相关的super_operations 结构
|
||||
|
||||
**Panel 2**
|
||||
struct super_operations {
|
||||
void (*read_inode) (struct inode *); /* fill the structure */
|
||||
int (*notify_change) (struct inode *, struct iattr *);
|
||||
void (*write_inode) (struct inode *);
|
||||
void (*put_inode) (struct inode *);
|
||||
void (*put_super) (struct super_block *);
|
||||
void (*write_super) (struct super_block *);
|
||||
void (*statfs) (struct super_block *, struct statfs *, int);
|
||||
int (*remount_fs) (struct super_block *, int *, char *);
|
||||
};
|
||||
|
||||
After a memory copy of the inode has been created, the kernel will act on it using its own operations. **struct inode_operations** is the second set of operations declared __by filesystem modules__, and are listed below: they deal __mainly with the directory tree__. Directory-handling operations are part of the inode operations because the implementation of a dir_operations structure would bring in extra conditionals in filesystem access. Instead, inode operations that only make sense for directories will do their own error checking. The first field of the inode operations defines the file operations for regular files: if the inode is a FIFO, a socket or a device specific file operations will be used. Inode operations appear in panel 3, note that the definition of rename was changed in 2.0.1.
|
||||
|
||||
**Panel 3**
|
||||
struct inode_operations { //由文件系统module提供,主要是一些和目录树处理相关的函数。
|
||||
__struct file_operations__ * default_file_ops; //非目录文件的操作方法
|
||||
int (*create) (struct inode *,const char *,int,int,struct inode **);
|
||||
int (*lookup) (struct inode *,const char *,int,struct inode **);
|
||||
int (*link) (struct inode *,struct inode *,const char *,int);
|
||||
int (*unlink) (struct inode *,const char *,int);
|
||||
int (*symlink) (struct inode *,const char *,int,const char *);
|
||||
int (*mkdir) (struct inode *,const char *,int,int);
|
||||
int (*rmdir) (struct inode *,const char *,int);
|
||||
int (*mknod) (struct inode *,const char *,int,int,int);
|
||||
//int (*rename)// (struct inode *,const char *,int, struct inode *,
|
||||
const char *,int, int); /* this from 2.0.1 onwards */
|
||||
int (*readlink) (struct inode *,char *,int);
|
||||
int (*follow_link) (struct inode *,struct inode *,int,int,struct inode **);
|
||||
int (*readpage) (struct inode *, struct page *);
|
||||
int (*writepage) (struct inode *, struct page *);
|
||||
int (*bmap) (struct inode *,int);
|
||||
void (*truncate) (struct inode *);
|
||||
int (*permission) (struct inode *, int);
|
||||
int (*smap) (struct inode *,int);
|
||||
};
|
||||
|
||||
__The file_operations, finally, specify how data in the actual file is handled__: the operations implement the low-level details of read, write, lseek and the other data-handling system calls. Since the same file_operations structure is used to act on devices, it also encloses some fields that only make sense for char or block devices. It's interesting to note that the structure shown here is the one declared in the 2.0 kernels, while 2.1 chenged the prototypes of read, write and lseek to allow a wider range of file offsets. The file operations (as of 2.0) are shown in panel 4.
|
||||
|
||||
**Panel 4**
|
||||
struct file_operations {
|
||||
struct module *owner;
|
||||
loff_t (*llseek) (struct file *, loff_t, int);
|
||||
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
|
||||
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
|
||||
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
|
||||
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
|
||||
int (*readdir) (struct file *, void *, filldir_t);
|
||||
unsigned int (*poll) (struct file *, struct poll_table_struct *);
|
||||
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
|
||||
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
|
||||
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
|
||||
int (*mmap) (struct file *, struct vm_area_struct *);
|
||||
int (*open) (struct inode *, struct file *);
|
||||
int (*flush) (struct file *, fl_owner_t id);
|
||||
int (*release) (struct inode *, struct file *);
|
||||
int (*fsync) (struct file *, struct dentry *, int datasync);
|
||||
int (*aio_fsync) (struct kiocb *, int datasync);
|
||||
int (*fasync) (int, struct file *, int);
|
||||
int (*lock) (struct file *, int, struct file_lock *);
|
||||
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
|
||||
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,
|
||||
unsigned long, unsigned long);
|
||||
int (*check_flags)(int);
|
||||
int (*flock) (struct file *, int, struct file_lock *);
|
||||
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t,
|
||||
unsigned int);
|
||||
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t,
|
||||
unsigned int);
|
||||
int (*setlease)(struct file *, long, struct file_lock **);
|
||||
};
|
||||
|
||||
===== Typical Implementation Problems =====
|
||||
The mechanisms to access filesystem data described above are detached from the physical layout of data and are designed to account for all the Unix semantics as far as filesystems are concerned.
|
||||
|
||||
Unfortunately, however, not all the filesystem types support all of the functions just described -- in particular, not all the types have to concept of ``inode'', even though the kernel identifies every file by means of its unsigned long inode number. If the physical data being accessed by a filesystem type has no physical inodes, the code implementing readdir and read_inode must invent an inode number for each file in the storage medium.
|
||||
|
||||
A typical technique to choose an inode number is **using the offset of the control block for the file within the filesystem data area**, assuming the files are identified by something that can be called `control block'. The iso9660 type, for example, uses this technique to create an inode number for each file in the device.
|
||||
|
||||
The /proc filesystem, on the other hand, has no physical device to extract its data from, and therefore uses **hardwired numbers for files** that always exist, like /proc/interrupts, and dynamically allocated inode numbers for other files. The inode numbers are stored in the data structure associated to each dynamic file.
|
||||
|
||||
Another typical problem to face when implementing a filesystem type is dealing with limitations in the actual storage capabilities. For example, how to react when the user tries to rename a file to a name longer than the maximum allowed length for the particular filesystem, or when she tries to modify the access time of a file within a filesystem that doesn't have the concept of access time.
|
||||
|
||||
In these cases, the standard is to return **-ENOPERM,** which means ``Operation not permitted''. Most VFS functions, like all the system calls and a number of other kernel functions, return 0 or a positive number in case of success, and a negative number in case of errors. Error codes returned by kernel functions are always one of the integer values defined in "<"asm/errno.h">".
|
||||
|
||||
===== Dynamic /proc Files =====
|
||||
I'd like to show now a little code to play with the VFS idea, but it's quite hard to conceive a small enough filesystem type to fit in the article. Writing a new filesystem type is surely an interesting task, but a complete implementation includea 39 ``operation'' functions. In practice, is there the need to build yet another filesystem type just for the sake of it?
|
||||
|
||||
Fortunately enough, the /proc filesystem as defined in the Linux kernel lets modules play with the VFS internals without the need to register a whole-new filesystem type. Each file within /proc can define its own inode operations and file operations, and is therefore able to exploit all the features of the VFS. The interface to creating /proc files is easy enough to be introduced here, although not in too much detail. `Dynamic /proc files' are called that way __because their inode number is dynamically allocated at file creation__ (instead of being extracted from an inode table or generated by a block number).
|
||||
|
||||
In this section we'll build a module called burp, for ``Beautiful and Understandable Resource for Playing''. Not all of the module will be shown because the innards of each dynamic file is not related with the VFS idea.
|
||||
|
||||
The main structure used in building up the file tree of /proc is **struct proc_dir_entry**: one such structure is associated to each node within /proc and it is used to keep track of the file tree. The default readdir and lookup inode operations for the filesystem access a tree of struct proc_dir_entry to return information to the user process.
|
||||
|
||||
The burp module, once equipped with the needed structures, will create three files: /proc/root is the block device associated the current root partition; /proc/insmod is an interface to load/unload modules without the need to become root; proc/jiffies reads as the current value of the jiffy counter (i.e., the number of clock ticks since system boot). These three files have no real value and are just meant to show how the inode and file operations are used. As you see, burp is really a ``Boring Utility Relying on Proc''. To avoid making the utility too boring I won't tell here the details about module loading and unloading: they have been described in previous Kernel Korner articles which are now accessible through the web. The whole burp.c file is available as well.
|
||||
|
||||
Creation and desctruction of /proc files is performed by calling the following functions:
|
||||
|
||||
proc_register_dynamic(struct proc_dir_entry *where,
|
||||
struct proc_dir_entry *self);
|
||||
proc_unregister(struct proc_dir_entry *where, int inode);
|
||||
In both functions, where is the directory where the new file belongs, and we'll use &proc_root to use the root directory of the filesystem. The self structure, on the other hand, is declared inside burp.c for each of the three files. The definition of the structure is reported in panel 5 for your reference; I'll show the three burp incantations of the structure in a while, after discussing their role in the game.
|
||||
|
||||
Panel 5
|
||||
|
||||
struct proc_dir_entry {
|
||||
unsigned short low_ino; /* inode number for the file */
|
||||
unsigned short namelen; /* lenght of filename */
|
||||
const char *name; /* the filename itself */
|
||||
mode_t mode; /* mode (and type) of file */
|
||||
nlink_t nlink; /* number of links (1 for files) */
|
||||
uid_t uid; /* owner */
|
||||
gid_t gid; /* group */
|
||||
unsigned long size; /* size, can be 0 if not relevant */
|
||||
struct inode_operations * ops; /* inode ops for this file */
|
||||
int (*get_info)(char *, char **, off_t, int, int); /* read data */
|
||||
void (*fill_inode)(struct inode *); /* fill missing inode info */
|
||||
struct proc_dir_entry *next, *parent, *subdir; /* internal use */
|
||||
void *data; /* used in sysctl */
|
||||
};
|
||||
The `synchronous' part of burp reduces therefore to three lines within init_module() and three within cleanup_module(). Everything else is dispatched by the VFS interface and is `event-driven' as far as a process accessing a file can be considered an event (yes, this way to see things is etherodox, and you should never use it with professional people).
|
||||
|
||||
The three lines in ini_module() look like:
|
||||
|
||||
proc_register_dynamic(&proc_root, &burp_proc_root);
|
||||
and the ones in cleanup_module() look like:
|
||||
|
||||
proc_unregister(&proc_root, burp_proc_root.low_ino);
|
||||
The low_ino field here is the inode number for the file being unregistered, and has been dynamically assigned at load time.
|
||||
|
||||
But how will these three files respond to user access? Let's look at each of them independently.
|
||||
|
||||
/proc/root is meant to be a block device. Its `mode' should therefore have the S_IBLK bit set, its inode operations should be those of block devices and its device number should be the same as the root device currently mounted. Since the device number associated to the inode is not part of the proc_dir_entry structure, the fill_inode field must be used. The inode number of the root device will be extracted from the table of mounted filesystems.
|
||||
/proc/insmod is a writable file: it needs own file_operations to declare its own ``write'' method. Therefore it declares its own inode_operations that point to its file operations. Whenever its write() implementation is called, the file asks to kerneld to load or unload the module whole name has been written. The file is writable by anybody: this is not a big problem as loading a module doesn't mean accessing its resources; and what is loadable is still controlled by root via /etc/modules.conf.
|
||||
/proc/jiffies is much easier: the file is only read from. Kernel version 2.0 and later ones offer a simplified interface for read-only files: the get_info function poiinter, if set, will be asked to fell a page of data each time the file is read. Therefore /proc/jiffies doesn't need own file operations nor inode operations: it just uses get_info. The function uses sprintf() to convert the integer jiffies value to a string.
|
||||
The snapshot of tty session in panel 6 shows how the files appear and how two of them work. Panel 7, finally, shows the three structures used to declare the file entries in /proc. The structures have not been completely defined, because the C compiler fills with zeroes any partially-defined structure without issuing any warning (feature, not bug).
|
||||
|
||||
The module has been compiled and run on a PC, an Alpha and a Sparc, all of them running Linux version 2.0.x
|
||||
|
||||
Panel 6
|
||||
|
||||
morgana% ls -l /proc/root /proc/insmod /proc/jiffies
|
||||
--w--w--w- 1 root root 0 Feb 4 23:02 /proc/insmod
|
||||
-r--r--r-- 1 root root 11 Feb 4 23:02 /proc/jiffies
|
||||
brw------- 1 root root 3, 1 Feb 4 23:02 /proc/root
|
||||
morgana% cat /proc/jiffies
|
||||
0002679216
|
||||
morgana% cat /proc/modules
|
||||
burp 1 0
|
||||
morgana% echo isofs ">" /proc/insmod
|
||||
morgana% cat /proc/modules
|
||||
isofs 5 0 (autoclean)
|
||||
burp 1 0
|
||||
morgana% echo -isofs ">" /proc/insmod
|
||||
morgana% cat /proc/jiffies
|
||||
0002682697
|
||||
morgana%
|
||||
Panel 7
|
||||
|
||||
struct proc_dir_entry burp_proc_root = {
|
||||
0, /* low_ino: the inode -- dynamic */
|
||||
4, "root", /* len of name and name */
|
||||
S_IFBLK | 0600, /* mode: block device, r/w by owner */
|
||||
1, 0, 0, /* nlinks, owner (root), group (root) */
|
||||
0, &blkdev_inode_operations, /* size (unused), inode ops */
|
||||
NULL, /* get_info: unused */
|
||||
burp_root_fill_ino, /* fill_inode: tell your major/minor */
|
||||
/* nothing more */
|
||||
};
|
||||
|
||||
struct proc_dir_entry burp_proc_insmod = {
|
||||
0, /* low_ino: the inode -- dynamic */
|
||||
6, "insmod", /* len of name and name */
|
||||
S_IFREG | S_IWUGO, /* mode: REGular, Write UserGroupOther */
|
||||
1, 0, 0, /* nlinks, owner (root), group (root) */
|
||||
0, &burp_insmod_iops, /* size - unused; inode ops */
|
||||
};
|
||||
|
||||
struct proc_dir_entry burp_proc_jiffies = {
|
||||
0, /* low_ino: the inode -- dynamic */
|
||||
7, "jiffies", /* len of name and name */
|
||||
S_IFREG | S_IRUGO, /* mode: regular, read by anyone */
|
||||
1, 0, 0, /* nlinks, owner (root), group (root) */
|
||||
11, NULL, /* size is 11; inode ops unused */
|
||||
burp_read_jiffies, /* use "get_info" instead */
|
||||
};
|
||||
The /proc implementation has other interesting features to offer, the most interesting being the sysctl implementation. The idea is so interesting that it doesn't fit here, and the kernel-korner article of Sptember 1997 will fill the gap.
|
||||
|
||||
===== Interesting Examples to Look at =====
|
||||
My discussion is over now, but there are many interesting places where interesting source code is on show. Interesting implementations of filesystem types are:
|
||||
|
||||
Obviously, the ``/proc'' filesystem: it is quite easy to look at because it is neither performance-critical nor particularly full-featured (except the sysctl idea). Enough said.
|
||||
The ``umsdos'' filesystem: it is part of the mainstream kernel and runs piggy-back on the ``msdos'' filesystem. It implements only a few of the operations of the VFS to add new capabilities to an old-fashioned filessytem format.
|
||||
The ``userfs'' module: it is available from both tsx-11 and sunsite under ALPHA/userfs; version 0.9.3 will load to Linux 2.0. The module defines a new filesystem type which uses external programs to retrieve data; interesting applications are the ftp filesystem and a read-only filesystem to mount compressed tar files. Even though reverting to user programs to get filesystem data is dangerouus and might lead to unexpected deadlocks, the idea is quite interesting.
|
||||
``supermount'': the filesystem is available on sunsite and mirrors. This filesystem type is able to mount removable devices like floppies of cdrom and handle device removal without forcing the user to umount/mount the device. The module works by controlling another filesystem type while arranging to keep the device unmounted when it is not used; the operation is transparent to the user.
|
||||
``ext2'': the extended-2 filesystem has been the standard Linux filesystem for a few years now. It is difficult code, but really worth reading for who is interested in looking at how a real filesystem is implemented. It also has hooks for interesting security features like the immutable-flag and the append-only-flag. Files marked as immutable or append-only can only be deleted when the system is in single-user mode, and are therefore secured from network intruders.
|
||||
Alessandro is a wild soul with an attraction for source code. He's the author of "Writing Linux Device Drivers": an O'Reilly book due out in summer. He is a fan of Linus Torvalds and Baden Powell and enjoys the two communities of volunteer workers they happened to build. He can be reached as rubini@linux.it.
|
||||
Verbatim copying and distribution of this entire article is permitted in any medium, provided this notice is preserved
|
||||
BIN
Zim/内核开发/linux文件系统/The_Virtual_File_System_in_Linux/lj-vfs.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
35
Zim/内核开发/linux文件系统/Virtual_file_system.txt
Normal file
@@ -0,0 +1,35 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-24T09:48:16+08:00
|
||||
|
||||
====== Virtual file system ======
|
||||
Created Wednesday 24 October 2012
|
||||
http://en.wikipedia.org/wiki/Virtual_file_system
|
||||
From Wikipedia, the free encyclopedia
|
||||
|
||||
A virtual file system (VFS) or virtual filesystem switch is __an abstraction layer on top of a more concrete file system__. The purpose of a VFS is to allow client applications __to access different types of concrete file systems in a uniform way__. A VFS can, for example, be used to access local and network storage devices __transparently__ without the client application noticing the difference. It can be used to bridge the differences in Windows, Mac OS and Unix filesystems, so that applications can access files on local file systems of those types without having to know what type of file system they are accessing.
|
||||
|
||||
__A VFS specifies an interface (or a "contract") between the kernel and a concrete file system__. Therefore, it is easy to add support for new file system types to the kernel simply __by fulfilling the contract__. The terms of the contract might change incompatibly from release to release, which would require that concrete file system support be recompiled, and possibly modified before recompilation, to allow it to work with a new release of the operating system; or the supplier of the operating system might make only backward-compatible changes to the contract, so that concrete file system support built for a given release of the operating system would work with future versions of the operating system.
|
||||
|
||||
===== Implementations =====
|
||||
One of the first virtual file system mechanisms on **Unix-like systems** was introduced by Sun Microsystems in **SunOS 2.0 in 1985**. It allowed Unix system calls to access local UFS file systems and remote NFS file systems transparently. For this reason, Unix vendors who licensed the NFS code from Sun often copied the design of Sun's VFS.
|
||||
|
||||
Other file systems could be plugged into it also: there was an implementation of the **MS-DOS FAT file system** developed at Sun that plugged into the SunOS VFS, although it wasn't shipped as a product until **SunOS 4.1**. The SunOS implementation was the basis of the VFS mechanism in System V Release 4.
|
||||
|
||||
John Heidemann developed __a stacking VFS__ under SunOS 4.0 for the experimental Ficus file system. This design provided for code reuse among file system types with differing but similar semantics (e.g., an encrypting file system could reuse all of the naming and storage-management code of a non-encrypting file system). Heidemann adapted this work for use in 4.4BSD as a part of his thesis research; descendants of this code underpin the file system implementations in modern BSD derivatives including Mac OS X.
|
||||
|
||||
Other Unix virtual file systems include the File System Switch in System V Release 3, the Generic File System in Ultrix, and __the VFS in Linux__. In OS/2 and Microsoft Windows, the virtual file system mechanism is called the Installable File System.
|
||||
|
||||
The __Filesystem in Userspace (FUSE) mechanism __allows userland code to plug into the virtual file system mechanism in Linux, NetBSD, FreeBSD, OpenSolaris, and Mac OS X.
|
||||
|
||||
In Microsoft Windows, virtual filesystems can also be implemented through userland Shell namespace extensions; however, they do not support the lowest-level file system access application programming interfaces in Windows, so not all applications will be able to access file systems that are implemented as namespace extensions.
|
||||
|
||||
__KIO and GVFS/GIO__ provide similar mechanisms in the KDE and GNOME desktop environments (respectively), with similar limitations, although they can be made to use FUSE techniques and therefore integrate smoothly into the system.
|
||||
|
||||
===== Single-file virtual file systems =====
|
||||
Sometimes Virtual File System refers to a file or a group of files (not necessarily inside a concrete file system) that acts as __a manageable container__ which should provide the functionality of a concrete file system through the usage of a **software**. Examples of such containers are SolFS or a single-file virtual file system in an emulator like PCTask or so-called WinUAE, Oracle's VirtualBox, Microsoft's Virtual PC, VMware.
|
||||
|
||||
The primary benefit for this type of file system is that __it is centralized and easy to remove__. A single-file virtual file system may include all the basic features expected of any file system (virtual or otherwise), but access to the internal structure of these file systems is often limited to programs specifically written to make use of the single-file virtual file system (instead of implementation through a driver allowing universal access). Another major drawback is that performance is relatively low when compared to other virtual file systems. Low performance is mostly due to the cost of shuffling virtual files when data is written or deleted from the virtual file system.
|
||||
|
||||
===== Implementation of single-file virtual filesystems =====
|
||||
Direct examples of single-file virtual file systems include emulators, such as PCTask and WinUAE, which encapsulate not only the filesystem data but also emulated disk layout. This makes it easy to treat an OS installation like any other piece of software—transferring it with removable media or over the network.
|
||||
38
Zim/内核开发/linux文件系统/块缓冲区和块缓冲区头.txt
Normal file
@@ -0,0 +1,38 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-24T15:19:39+08:00
|
||||
|
||||
====== 块缓冲区和块缓冲区头 ======
|
||||
Created Wednesday 24 October 2012
|
||||
http://edsionte.com/techblog/archives/4085
|
||||
|
||||
在详细说明__块缓冲区__和__块缓冲区头__之前,我们先来看一下**块设备**中的两个基本概念:扇区和块。
|
||||
|
||||
__扇区是块设备传输数据的基本单元(设备的物理属性)__,也就是说**它是块设备中最小的寻址单位**,扇区通常的大小为**512B**。__块是内核对文件系统的一种抽象__,也就是说**内核执行的所有磁盘操作都是以块为基本单位的(不同的文件系统,其块大小可能不同)**。
|
||||
|
||||
可以简单的将扇区和块理解为:**扇区是硬件设备传输数据的最小单位,而块是操作系统传输数据的最小单位**。一个块通常对应一个或多个**相邻**的扇区,由于__内核将块作为对文件系统操作的最小单位__,因此__VFS将其看作是单一的数据单元__。
|
||||
|
||||
当内核从磁盘读入数据后或者即将写数据到磁盘时,它需要将数据写入一些块缓冲区。__buff缓冲区其实就是物理页框(page)的一部分__,因此一个物理页框可能包含一个或多个块缓冲区。根据上述描述的关系,包含磁盘数据的物理页框构造如下图:
|
||||
|
||||
正如上面所说,块缓冲区是页框的一部分,因此**不用特别描述块缓冲区中的数据**。__每个块缓冲区都对应一个块缓冲区头buffer_head__,他们的关系如同物理页框和物理页框描述符,前者用来存储数据,后者是对前者的属性以及控制信息的描述。块缓冲区头、块缓冲区以及页框的关系如下:
|
||||
{{./buffer_head.jpeg}}
|
||||
内核中使__用buffer_head结构来描述缓冲区头__,该结构中的部分字段解释如下:
|
||||
struct buffer_head {
|
||||
unsigned long b_state;
|
||||
struct buffer_head *b_this_page;
|
||||
struct page *b_page;
|
||||
atomic_t b_count;
|
||||
u32 b_size;
|
||||
sector_t b_blocknr;
|
||||
char *b_data;
|
||||
struct block_device *b_bdev;
|
||||
bh_end_io_t *b_end_io;
|
||||
void *b_private;
|
||||
struct list_head b_assoc_buffers;
|
||||
}
|
||||
b_state:对块缓冲区状态的描述。
|
||||
b_this_page:在一个页框中,可能包含多个块缓冲区。__一个页框内的所有缓冲区形成循环链表__,该字段指向下一个块缓冲区。
|
||||
b_page:指向缓冲区所在**页框的描述符**。
|
||||
b_size:块缓冲区大小。
|
||||
b_data:当前块在作为缓冲的**页框内的位置**。
|
||||
b_bdev:指向块设备的指针。
|
||||
109
Zim/内核开发/linux文件系统/块缓冲区和块缓冲区头/buffer_cache.txt
Normal file
@@ -0,0 +1,109 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2012-10-24T15:33:13+08:00
|
||||
|
||||
====== buffer cache ======
|
||||
Created Wednesday 24 October 2012
|
||||
http://oss.org.cn/kernel-book/ch08/8.3.1.htm
|
||||
|
||||
8.3.1 块高速缓存
|
||||
Linux支持的__文件系统大多以块的形式组织文件__,为了减少对物理块设备的访问,在文件以块的形式调入内存后,使用__块高速缓存(buffer_cache)__对它们进行管理。每个缓冲区由两部分组成,第一部分称为**缓冲区首部**,用数据结构buffer_head表示,第二部分是真正的**缓冲区内容**(即所存储的数据)。由于缓冲区首部不与数据区域相连,数据区域独立存储。因而在缓冲区首部中,有一个指向数据的指针和一个缓冲区长度的字段。图8.6给出了一个缓冲区的格式。
|
||||
|
||||
缓冲区首部包含:
|
||||
·用于描述缓冲区内容的信息,包括:所在设备号、起始物理块号、包含在缓冲区中的字节数。
|
||||
·缓冲区状态的域:是否有有用数据、是否正在使用、重新利用之前是否要写回磁盘等。
|
||||
·用于管理的域。
|
||||
{{./image002.gif}}
|
||||
|
||||
图8.6 缓冲区格式
|
||||
buffer-head数据结构在include\linux\fs.h中定义如下:
|
||||
/*
|
||||
* Try to keep the most commonly used fields in single cache lines (16
|
||||
* bytes) to improve performance. This ordering should be
|
||||
* particularly beneficial on 32-bit processors.
|
||||
*
|
||||
* We use the first 16 bytes for the data which is used in searches
|
||||
* over **the block hash lists** (ie. getblk() and friends).
|
||||
*
|
||||
* The second 16 bytes we use for **lru buffer** scans, as used by
|
||||
* sync_buffers() and refill_freelist(). -- sct
|
||||
*/
|
||||
__struct buffer_head__ {
|
||||
/* First cache line: */
|
||||
struct buffer_head *b_next; /* 哈希队列链表t */
|
||||
unsigned long b_blocknr; /* 逻辑块号 */
|
||||
unsigned short b_size; /* 块大小 */
|
||||
unsigned short b_list; /* 本缓冲区所出现的链表 */
|
||||
kdev_t b_dev; /* 虚拟设备标示符(B_FREE = free) */
|
||||
atomic_t b_count; **/* 块引用计数器 */**
|
||||
kdev_t b_rdev; **/* 实际设备标识符*/**
|
||||
unsigned long b_state; **/* 缓冲区状态位图 */**
|
||||
unsigned long b_flushtime; /* 对脏缓冲区进行刷新的时间*/
|
||||
struct buffer_head *b_next_free;/* 指向lru/free 链表中的下一个元素 */
|
||||
struct buffer_head *b_prev_free;/* 指向链表中的上一个元素*/
|
||||
struct buffer_head ***b_this_page**;/* 每个页面中的缓冲区链表*/(单个page中可以存多个连续的buff)
|
||||
struct buffer_head *b_reqnext; /*请求队列 */
|
||||
|
||||
struct buffer_head **b_pprev; /* 哈希队列的双向链表 */
|
||||
char * b_data; __/* 指向数据块 */__
|
||||
struct page *b_page; /* 这个bh所映射的页面*/
|
||||
void (*b_end_io)(struct buffer_head *bh, int uptodate); /* I/O结束方法*/
|
||||
void *b_private; /* 给b_end_io保留 */
|
||||
|
||||
unsigned long b_rsector; /* 缓冲区在磁盘上的实际位置,扇区号*/
|
||||
wait_queue_head_t b_wait; /* 缓冲区等待队列 */
|
||||
|
||||
struct inode * b_inode;
|
||||
struct list_head b_inode_buffers; /* __inode脏缓冲区的循环链表__*/
|
||||
};
|
||||
|
||||
其中缓冲区状态在fs.h中定义为枚举类型:
|
||||
/* bh state bits */
|
||||
enum bh_state_bits {
|
||||
BH_Uptodate, /* 如果缓冲区包含有效数据则置1 */
|
||||
BH_Dirty, /* 如果**缓冲区数据被改变**则置1 */
|
||||
BH_Lock, /* 如果缓冲区被锁定则置1*/
|
||||
BH_Req, /* 如果缓冲区数据无效则置0 */
|
||||
BH_Mapped, /* 如果缓冲区有一个**磁盘映射**则置1 */
|
||||
BH_New, /* 如果缓冲区为新且还没有被写出则置1 */
|
||||
BH_Async, /* 如果缓冲区是进行end_buffer_io_async I/O 同步则置1 */
|
||||
BH_Wait_IO, /* 如果我们应该把这个缓冲区写出则置1 */
|
||||
BH_launder, /* 如果我们应该“清洗”这个缓冲区则置1 */
|
||||
BH_JBD, /* 如果与journal_head相连接则置1 */
|
||||
|
||||
BH_PrivateStart,/* 这不是一个状态位,但是,第一位由其他实体用于私有分配*/
|
||||
}
|
||||
显然一个缓冲区可以同时具有上述状态的几种。
|
||||
|
||||
块高速缓存的管理很复杂,下面先对空缓冲区、空闲缓冲区、正使用的缓冲区、缓冲区的大小以及缓冲区的类型作一个简短的介绍:
|
||||
缓冲区可以分为两种,一种是包含了有效数据的,另一种是没有被使用的,即空缓冲区。
|
||||
|
||||
具有有效数据并不能表明某个缓冲区正在被使用,毕竟,在同一时间内,被进程访问的缓冲区(即处于使用状态)只有少数几个。__当前没有被进程访问的有效缓冲区和空缓冲区称为空闲缓冲区__。其实,buffer_head结构中的b_count就可以反映出缓冲区是否处于使用状态。如果它为0,则缓冲区是空闲的。大于0,则缓冲区正被进程访问。
|
||||
|
||||
缓冲区的大小不是固定的,当前Linux支持5种大小的缓冲区,分别是512、1024、2048、4096、8192字节。Linux所支持的文件系统都使用共同的块高速缓存,在同一时刻,块高速缓存中存在着来自不同物理设备的数据块,为了支持这些不同大小的数据块,Linux使用了几种不同大小的缓冲区。
|
||||
|
||||
当前的Linux缓冲区有3种类型,在include/linux/fs.h中有如下的定义:
|
||||
#define BUF_CLEAN 0 /*未使用的、干净的缓冲区(含有有效数据但是引用计数为0即没有正在被进程访问,或则空缓冲区)*/
|
||||
#define BUF_LOCKED 1 /*__被锁定的缓冲区,正等待写入__*/
|
||||
#define BUF_DIRTY 2 /*脏的缓冲区,其中有有效数据,需要写回磁盘*/
|
||||
|
||||
VFS使用了多个链表来管理块高速缓存中的缓冲区。
|
||||
* 首先,对于包含了**有效数据**的缓冲区,用一个哈希表来管理,用__hash_table__来指向这个哈希表。哈希索引值**由数据块号以及其所在的设备标识号**计算(散列)得到。所以在buffer_head这个结构中有一些用于哈希表管理的域。__使用哈希表可以迅速地查找到所要寻找的数据块所在的缓冲区__。
|
||||
* 对于每一种类型的**未使用的有效缓冲区**,系统还使用一个LRU(最近最少使用)双链表管理,即lru-list链。由于共有三种类型的缓冲区,所以__有三个这样的LRU链表__。
|
||||
|
||||
当需要访问某个数据块时,系统采取如下算法:
|
||||
1. 首先,根据数据块号和所在设备号在块高速缓存中查找,如果找到,则将它的b-count域加1,因为这个域正是反映了**当前使用这个缓冲区的进程数**。如果这个缓冲区同时又处于某个LRU链中,则将它从LRU链中解开。
|
||||
2. 如果数据块还没有调入缓冲区,则系统必须进行磁盘I/O操作(**以块为单位**),将数据块调入块高速缓存,同时将空缓冲区分配一个给它。如果块高速缓存已满(即没有空缓冲区可供分配),则从某个LRU**链首**取下一个,先看是否置了“脏”位,如已置,则将它的内容写回磁盘。然后清空内容,将它分配给新的数据块。
|
||||
在缓冲区使用完了后,将它的b_count域减1,如果b_count变为0,则将它放在某个LRU**链尾**,表示该缓冲区已可以重新利用。
|
||||
|
||||
为了配合以上这些操作,以及其它一些多块高速缓存的操作,系统另外使用了几个链表,主要是:
|
||||
* 对于**每一种**大小的空闲缓冲区,系统使用一个链表管理,即__free_list链__。
|
||||
* 对于空缓冲区,系统使用一个__unused_list链__管理。
|
||||
以上几种链表都在fs/buffer.c定义。
|
||||
|
||||
Linux中,__用bdflush守护进程__完成对块高速缓存的一般管理。bdflush守护进程是一个简单的内核线程,在系统启动时运行,它在系统中注册的进程名称为 __kflushd__,你可以使用ps命令看到此系统进程。它的一个作用是**监视块高速缓存中的“脏”缓冲区**,在分配或丢弃缓冲区时,将对“脏”缓冲区数目作一个统计。通常情况下,该进程处于休眠状态,当块高速缓存中“脏”缓冲区的数目达到一定的比例,默认是60%,该进程将被唤醒。但是,如果系统急需,则在任何时刻都可能唤醒这个进程。使用update命令可以看到和改变这个数值。
|
||||
# update -d
|
||||
|
||||
当有数据写入缓冲区使之变成“脏”时,所有的“脏”缓冲区被连接到一个__BUF_DIRTY_LRU链表__中,bdflush会将适当数目的缓冲区中的数据块写到磁盘上。这个数值的缺省值为500,可以用update命令改变这个值。
|
||||
|
||||
另一个与块高速缓存管理相关的是update命令,它不仅仅是一个命令,还是一个后台进程。当以超级用户的身份运行时(在系统初始化时),它将周期性调用系统服务例程将老的“脏”缓冲区中内容“冲刷”到磁盘上去。它所完成的这个工作与bdflush类似,不同之处在于,当一个“脏”缓冲区完成这个操作后, 它将把写入到磁盘上的时间标记到buffer_head结构中。update每次运行时它将在系统的所有“脏”缓冲区中查找那些“冲刷”时间已经超过一定期限的,这些过期缓冲区都要被写回磁盘。
|
||||
BIN
Zim/内核开发/linux文件系统/块缓冲区和块缓冲区头/buffer_cache/image002.gif
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
Zim/内核开发/linux文件系统/块缓冲区和块缓冲区头/buffer_head.jpeg
Normal file
|
After Width: | Height: | Size: 26 KiB |
496
Zim/内核开发/linux文件系统/如何实现一个文件系统.txt
Normal file
@@ -0,0 +1,496 @@
|
||||
Content-Type: text/x-zim-wiki
|
||||
Wiki-Format: zim 0.4
|
||||
Creation-Date: 2011-09-09T23:54:37+08:00
|
||||
|
||||
====== 如何实现一个文件系统 ======
|
||||
Created Friday 09 September 2011
|
||||
http://blog.csdn.net/kanghua/article/details/1849404
|
||||
|
||||
===== 摘要 =====
|
||||
本章目的是分析在Linux系统中如何实现新的文件系统。在介绍文件系统具体实现前先介绍文件系统的概念和作用,抽象出了文件系统概念模型。熟悉文件系统的内涵后,我们再近一步讨论Linux系统中和文件系统的特殊风格和具体文件系统在Linux中组成结构,逐步为读者勾画出Linux中文件系统工作的全景图。最后在事例部分,我们将以romfs文件系统作实例分析实现文件系统的普遍步骤。
|
||||
|
||||
===== 什么是文件系统 =====
|
||||
|
||||
==== 别混淆“文件系统” ====
|
||||
首先要谈的概念就是什么是文件系统,它的作用到底是什么。
|
||||
|
||||
文件系统的概念虽然许多人都认为是再清晰不过的了,但其实我们往往在谈论中或多或少地夸大或片缩小了它的实际概念(至少我时常混淆),或者说,有时借用了其它概念,有时说的又不够全面。
|
||||
|
||||
比如在操作系统中,文件系统这个术语往往既被用来描述磁盘中的物理布局,比如有时我们说磁盘中的“文件系统”是EXT2或说把磁盘格式化成FAT32格式的“文件系统”等——这时所说的“文件系统”是__指磁盘数据的物理布局格式__;另外,文件系统也被用来__描述内核中的逻辑文件结构__,比如有时说的“文件系统”的接口或内核支持Ext2等“文件系统”——这时所说的文件系统都是内存中的数据组织结构而并非磁盘物理布局(后面我们将称呼它为逻辑文件系统);还有些时候说“文件系统”负责管理用户读写文件——这时所说的“文件系统”往往描述操作系统中的“文件管理系统”,也就是文件子系统。
|
||||
|
||||
虽然上面我们列举了混用文件系统的概念的几种情形,但是却也不能说上述说法就是错误的,因为文件系统概念本身就囊括众多概念,几乎可以说在操作系统中自内存管理、系统调度到I/O系统、设备驱动等各个部分都和文件系统联系密切,有些部分和文件系统甚至未必能明确划分——所以不能只知道文件系统是系统中数据的存储结构,一定要全面认识文件系统在操作系统中的角色,才能具备自己开发新文件系统的能力。
|
||||
|
||||
==== 文件系统的体系结构 ====
|
||||
为了澄清文件系统的概念,必须先来看看文件系统在操作系统中处于何种角色,分析文件系统概念的内含外延。我们先抛开Linux文件系统的实例,而来看看操作系统中文件系统的普遍体系结构,从而增强对文件系统的理论认识。
|
||||
|
||||
下面以软件组成的结构图[1][1]的方式描述文件系统所涉及的内容。
|
||||
|
||||
用户程序
|
||||
----------------------
|
||||
|
|
||||
|
|
||||
------- | -------------
|
||||
访问方法
|
||||
---------------------
|
||||
逻辑I/O
|
||||
----------------------
|
||||
基础I/O监督(管理)
|
||||
---------------------
|
||||
物理I/O
|
||||
---------------------
|
||||
设备驱动
|
||||
--------------------
|
||||
|
||||
图 1 : 文件系统体系结构层次图
|
||||
|
||||
针对各层做以简要分析:
|
||||
|
||||
1.首先我们来分析最低层——设备驱动层,该层负责与外设——磁盘等——通讯。文件系统都需要和存储设备打交道,而系统操作外设离不开驱动程序。所以内核对文件的最后操作行为就是调用设备驱动程序__完成从主存(内存)到辅存(磁盘)的数据传输__。
|
||||
|
||||
文件系统相关的多数设备都属于块设备,常见的块设备驱动程序有磁盘驱动,光驱驱动等,之所以称它们为块设备,一个原因是它们读写数据都是成块进行的,但是更重要的原因是它们管理的数据能够被随机访问——不需要向字符设备那样必须顺序访问。
|
||||
|
||||
2.设备驱动层的上一层是物理I/O层,该层主要作为**计算机外部环境和系统的接口**,负责系统和磁盘交换数据块。它要知道据块在磁盘中存储位置,也要知道文件数据块在内存缓冲中的位置,另外它不需要了解数据或文件的具体结构。可以看到这层最主要的工作是__标识别磁盘扇区和内存缓冲块[2][2]之间的映射关系__。
|
||||
|
||||
3.再上层是基础I/O监督层,该层主要负责选择文件 I/O需要的设备,调度磁盘请求等工作,另外分配I/O缓冲和磁盘空间也在该层完成。由于块设备需要随机访问数据,而且对速度响应要求较高,所以操作系统不能向对字符设备那样简单、直接地发送读写请求,而必须**对读写请求重新优化排序**,以能节省磁盘寻址时间,另外也必须对请求提交采取__异步调度__(尤其写操作)的方式进行。总而言之,内核必须管理块设备请求,而这项工作正是由该层负责的。
|
||||
|
||||
4.倒数第二层是逻辑I/O层,该层允许用户和应用程序访问记录。它提供了通用的记录(record)I/O操作,同时还维护基本文件数据。由于为了方便用户操作和管理文件内容,文件内容往往被组织成记录形式,所以操作系统**为操作文件记录**提供了一个通用逻辑操作层。
|
||||
|
||||
5.和用户最靠近的是访问方法层,该层提供了一个从用户空间到文件系统的标准接口,不同的访问方法反映了不同的文件结构,也反映了不同的访问数据和处理数据方法。这一层我们可以简单地理解为文件系统给用户提供的访问接口——不同的文件格式(如顺序存储格式、索引存储格式、索引顺序存储格式和哈希存储格式等)对应不同的文件访问方法。该层要负责将用户对文件结构的操作转化为对记录的操作。
|
||||
|
||||
===== 文件处理流程 =====
|
||||
对比上面的层次图我们再来分析一下数据流的处理过程,加深对文件系统的理解。
|
||||
|
||||
假如用户或应用程序操作文件(创建/删除),首先需要通过文件系统给用户空间提供的访问方法层进入文件系统,接着由使用逻辑I/O层对记录进行给定操作,然后**记录将被转化为文件块**,等待和磁盘交互。这里有两点需要考虑——第一,磁盘管理(包括在磁盘空闲区分配文件和组织空闲区);第二,调度块I/O请求——这些由基础I/O监督层的工作。再下来文件块被物理I/O层传递给磁盘驱动程序,最后磁盘驱动程序真正把数据写入具体的扇区。至此文件操作完毕。
|
||||
|
||||
当然上面介绍的层次结构是理想情况下的理论抽象,实际文件系统并非一定要按照上面的层次或结构组织,它们往往简化或合并了某些层的功能(比如Linux文件系统因为所有文件都被看作字节流,所__以不存在记录__,也就没有必要实现逻辑I/O层,进而也不需要在记录相关的处理)。但是大体上都需要经过类似处理。如果从处理对象上和系统独立性上划分,文件系统体系结构可以被分为两大部分:——文件管理部分和操作系统I/O部分。文件管理系统负责操作内存中__文件对象__,并按文件的逻辑格式将对文件对象的操作转化成对文件块的操作;而操作系统I/O部分负责内存中的块与物理磁盘中的数据交换。
|
||||
|
||||
数据表现形式在文件操作过程中也经历了几种变化:在用户访问文件系统看到的是字节序列,而在字节序列被写入磁盘时看到的是内存中文件块(在缓冲中),在最后将数据写入磁盘扇区时看到的是磁盘数据块[3][3]。
|
||||
|
||||
本文所说的实现文件系统主要针对最开始讲到第二种情况——内核中的逻辑文件结构(但其它相关的文件管理系统和文件系统磁盘存储格式也必须了解),我们用数据处理流图来分析一下逻辑文件系统主要功能和在操作系统中所处的地位。
|
||||
{{./fs2.jpg}}
|
||||
图 2 文件系统操作流程
|
||||
|
||||
其中文件系统接口与物理布局管理是逻辑文件系统要负责的主要功能。
|
||||
* 文件系统接口为用户提供对文件系统的操作,比如open、close、read、write和访问控制等,同时也负责处理//文件的逻辑结构//。
|
||||
* 物理存储布局管理,如同虚拟内存地址转化为物理内存地址时,必须处理段页结构一样,逻辑文件结构必须转化到物理磁盘中,所以也要处理物理分区和扇区的实际存储位置,分配磁盘空间和内存中的缓冲也要在这里被处理。
|
||||
所以说要实现文件系统就必须提供上面提到的两种功能,缺一不可。
|
||||
|
||||
在了解了文件系统的功能后,我们针对Linux操作系统分析具体文件系统如何工作,进而掌握实现一个文件系统需要的步骤。
|
||||
|
||||
===== Linux 文件系统组成结构 =====
|
||||
Linux 文件系统的结构除了我们上面所提到的概念结构外,最主要有两个特点,一个是文件系统抽象出了一个通用文件表示层——虚拟文件系统或称做VFS。另外一个重要特点是它的文件系统支持动态安装(或说挂载等),大多数文件系统都可以作为根文件系统的叶子接点被挂在到根文件目录树下的子目录上。另外Linux系统在文件读写的I/O操作上也采取了一些先进技术和策略。
|
||||
|
||||
我们先从虚拟文件系统入手分析linux文件系统的特性,然后介绍有关文件系统的安装、注册和读写等概念。
|
||||
|
||||
===== 虚拟文件系统 =====
|
||||
虚拟文件系统为用户空间程序提供了文件系统接口。系统中所有文件系统不但依赖VFS共存,而且也依靠VFS系统协同工作。通过虚拟文件系统我们可以利用标准的UNIX文件系统调用对不同介质上的不同文件系统进行读写操作[4][4]。
|
||||
|
||||
__虚拟文件系统的目的是为了屏蔽各种各样不同文件系统的相异操作形式,使得异构的文件系统可以在统一的形式下,以标准化的方法访问、操作__。实现虚拟文件系统利用的主要思想是引入一个通用文件模型——该模型抽象出了文件系统的所有基本操作(该通用模型源于Unix风格的文件系统),比如读、写操作等。同时实际文件系统如果希望利用虚拟文件系统,既被虚拟文件系统支持,也必须将自身的诸如,“打开文件”、“读写文件”等操作行为以及“什么是文件”,“什么是目录”等概念“修饰”成虚拟文件系统所要求的(定义的)形式,这样才能够被虚拟文件系统支持和使用。
|
||||
|
||||
我们可以借用面向对象的一些思想来理解虚拟文件系统,虚拟文件系统好比一个抽象类或接口,它定义(但不实现)了文件系统最常见的操作行为。而具体文件系统好比是具体类,它们是特定文件系统的实例。具体文件系统和虚拟文件系统的关系类似具体类继承抽象类或实现接口。而在用户看到或操作的都是抽象类或接口,但实际行为却发生在具体文件系统实例上。至于如何将对虚拟文件系统的操作转化到对具体文件系统的实例,__就要通过注册具体文件系统到系统,然后再安装具体文件系统才能实现转化__,这点可以想象成面向对象中的多态概念。
|
||||
|
||||
我们个实举例来说明具体文件系统如何通过虚拟文件系统协同工作。
|
||||
|
||||
例如:假设一个用户输入以下shell命令:
|
||||
|
||||
$ cp /hda/test1 /removable/test2
|
||||
|
||||
其中 /removable是MS-DOS磁盘的一个安装点,而 /hda 是一个标准的第二扩展文件系统( Ext2)的目录。cp命令不用了解test1或test2的具体文件系统,__它所看到和操作的对象是VFS__。cp首先要从ext3文件系统读出test1文件,然后写入MS-DOS文件系统中的test2。VFS会将找到ext3文件系统实例的读方法,对test1文件进行读取操作;然后找到MS-DOS(在Linux中称VFAT)文件系统实例的写方法,对test2文件进行写入操作。可以看到 __VFS是读写操作的统一界面__,只要具体文件系统符合VFS所要求的接口,那么就可以毫无障碍地透明通讯了。
|
||||
|
||||
下图给出Linux系统中VFS文件体统和具体文件系统能的层次示意图
|
||||
{{./fs3.jpg}}
|
||||
图3 linux系统中VFS和具体文件系统关系图
|
||||
|
||||
===== Unix风格的文件系统 =====
|
||||
虚拟文件系统的通用模型源于Unix风格的文件系统,所谓Unix风格是指Unix传统上文件系统传统上使用了四种和文件系统相关的抽象概念:文件(file)、目录项(dentry)、索引节点(inode)和安装点(mount point)。
|
||||
|
||||
1.文件——在Unix中的文件都被看做是一**有序字节串**,它们都有一个方便用户或系统识别的名称。另外典型的文件操作有读、写、创建和删除等。
|
||||
|
||||
2.目录项——不要和目录概念搞混淆,在Linux中目录被看作文件。而**目录项是文件路径中的一部分**。一个文件路径的例子是“/home/wolfman/foo”——根目录是/,目录home,wolfman和文件foo都是目录项。
|
||||
|
||||
3.索引节点——Unix系统将文件的相关信息(如访问控制权限、大小、拥有者、创建时间等等信息),有时被称作文件的__元数据__(也就是说,数据的数据)被存储在一个单独的数据结构中,该结构被称为索引节点(inode)。
|
||||
|
||||
4.安装点——在Unix中,文件系统被安装在一个特定的安装点上,所有的已安装文件系统都作为根文件系统树中的叶子出现在系统中。
|
||||
|
||||
上述概念是Unix文件系统的__逻辑数据结构__,但相应的Unix文件系统(Ext2等)磁盘布局也实现了部分上述概念,比如文件信息(文件数据元)存储在磁盘块中的索引节点上。当文件被载如内存时,内核需要使用磁盘块中的索引点来装配内存中的索引接点。类似行为还有超级块信息等。
|
||||
|
||||
对于非Unix风格文件系统,如FAT或NTFS,要想能被VFS支持,它们的文件系统代码必须提供这些概念的虚拟形式。比如,即使一个文件系统不支持索引节点,它也必须在内存中装配起索引节点结构体——如同本身固有一样。或者,如果一个文件系统将目录看作是一种特殊对象,那么要想使用VFS,必须将目录重新表示为文件形式。通常,这种转换需要在使用现场引入一些特殊处理,使得非Unix文件系统能够兼容Unix文件系统的使用规则和满足VFS的需求。通过这些处理,非Unix文件系统便可以和VFS一同工作了,是性能上多少会受一些影响[5][5]。这点很重要,__我们实现自己文件系统时必须提供(模拟)Unix风格文件系统的抽象概念__。
|
||||
|
||||
|
||||
|
||||
===== Linux文件系统中使用的对象 =====
|
||||
Linux文件系统的对象就是指一些数据结构体,之所以称它们是对象,是因为这些数据结构体不但包含了相关属性而且还包含了__操作自身结构的函数指针__,这种将数据和方法进行封装的思想和面向对象中对象概念一致,所以这里我们就称它们是对象。
|
||||
|
||||
Linux文件系统使用大量对象,我们简要分析以下VFS相关的对象,和除此还有和进程相关的一些其它对象。
|
||||
|
||||
==== VFS相关对象 ====
|
||||
这里我们不展开讨论每个对象,仅仅是为了内容完整性,做作简要说明。
|
||||
|
||||
VFS中包含有四个主要的对象类型,它们分别是:
|
||||
* 超级块对象,它代表特定的已安装文件系统。
|
||||
* 索引节点对象,它代表特定文件。
|
||||
* 目录项对象,它代表特定的目录项。
|
||||
* 文件对象,它代表和进程打开的文件。
|
||||
|
||||
每个主要对象中都包含一个操作对象,这些操作对象描述了内核针对主要对象可以使用的方法。最主要的几种操作对象如下:
|
||||
* super_operations对象,其中包括内核针对**特定文件系统**所能调用的方法,比如read_inode()和sync_fs()方法等。
|
||||
* inode_operations对象,其中包括内核针对特定文件系统所能调用的方法,比如create()和link()方法等。
|
||||
* dentry_operations对象,其中包括内核针对特定目录所能调用的方法,比如d_compare()和d_delete()方法等。
|
||||
* file_operations对象,其中包括,进程针对已打开文件所能调用的方法,比如read()和write()方法等。
|
||||
|
||||
除了上述的四个主要对象外,VFS还包含了许多对象,比如每个注册文件系统都是由**file_system_type**对象表示——描述了文件系统及其能力(如比如ext3或XFS);另外每一个__安装点__也都利用**vfsmount**对象表示——包含了关于安装点的信息,如位置和安装标志等。
|
||||
|
||||
==== 其它VFS对象 ====
|
||||
系统上的每一进程都有__自己的__打开文件,根文件系统,当前工作目录,安装点等等。另外还有几个数据结构体将VFS层和文件的进程紧密联系,它们分别是:__file_struct 和fs_struct__
|
||||
|
||||
file_struct结构体由进程描述符中的files项指向。所有包含进程和它的文件描述符的信息都包含在其中。第二个和进程相关的结构体是fs_struct,该结构由进程描述符的fs项指向。它包含文件系统和进程相关的信息。每种结构体的详细信息不在这里说明了。
|
||||
|
||||
==== 缓存对象 ====
|
||||
除了上述一些结构外,为了缩短文件操作响应时间,提高系统性能,Linux系统采用了许多缓存对象,例如目录缓存、页面缓存和缓冲缓存(已经归入了页面缓存),这里我们对缓存做简单介绍。
|
||||
|
||||
页高速缓存(cache)是 Linux内核实现的一种主要磁盘缓存。其目的是减少磁盘的I/O操作,具体的讲是通过把磁盘中的数据缓存到物理内存中去,把对磁盘的I/O操作变为对物理内存的I/O操作。页高速缓存是由RAM中的物理页组成的,__缓存中每一页都对应着磁盘中的多个块__。每当内核开始执行一个页I/O操作时(通常是对普通文件中页大小的块进行磁盘操作),首先会检查需要的数据是否在高速缓存中,如果在那么内核就直接使用高速缓存中的数据,从而避免了访问磁盘。
|
||||
|
||||
但我们知道文件系统只能以每次访问数个块的形式进行操作。__内核执行所有磁盘操作都必须根据块进行,一个块包含一个或多个磁盘扇区__。为此,内核提供了一个专门结构来管理缓冲**buffer_head**。缓冲头[6][6]的目的**是描述磁盘扇区和内存中块缓冲之间的映射关系和做I/O操作的容器**。但是缓冲结构并非独立存在,而是__被包含在页高速缓存中(参考:__[[../块缓冲区和块缓冲区头.txt]]。),而且一个页高速缓存可以包含多个缓冲。我们将在文件后面的文件读写部分看到数据如何被从磁盘扇区读入页高速缓存中的缓冲中的。
|
||||
|
||||
===== 文件系统的注册和安装 =====
|
||||
使用文件系统前必须对文件系统进行注册和安装,下面分别对这两种行为做简要介绍。
|
||||
|
||||
==== 文件系统的注册 ====
|
||||
VFS要想能将自己定义的接口映射到实际文件系统的专用方法上,必须能够让内核识别实际的文件系统,实际文件系统通过将代表自身属性的文件类型对象(file_system_type)注册(通过register_filesystem()函数)到内核,也就是挂到**内核中的文件系统类型链表**上,来达到使文件系统能被内核识别的目的。反过来内核也正是通过这条链表来跟踪系统所支持的各种文件系统的。
|
||||
|
||||
我们简要分析一下注册步骤:
|
||||
struct file_system_type {
|
||||
const char *name; /*文件系统的名字*/
|
||||
int fs_flags; /*文件系统类型标志*/
|
||||
/*下面的函数用来从磁盘中读取超级块*/
|
||||
struct super_block * (*read_super) (struct file_system_type *, int,
|
||||
const char *, void *);
|
||||
struct file_system_type * next; /*链表中下一个文件系统类型*/
|
||||
struct list_head fs_supers; /*超级块对象链表*/
|
||||
};
|
||||
|
||||
其中最重要的一项是read_super()函数,它用来从磁盘上读取超级块,并且当文件系统被装载时在内存中组装超级块对象。要实现一个文件系统首先需要实现的结构体便是file_system_type结构体。
|
||||
|
||||
注册文件系统只能保证文件系统能被系统识别,但此刻文件系统尚不能使用,因为__它还没有被安装到特定的安装点上__。所以在使用文件系统前必须将文件系统安装到安装点上。
|
||||
注意:
|
||||
1. 文件系统类型(struct file_system_type)代表对某种文件数据的组织方式,__这种组织是逻辑上的__,而将按该类型的文件数据写到磁盘中是由磁盘驱动完成的。__磁盘驱动看不到数据__
|
||||
__的逻辑组织方式(也就是说不关心其中数据的组织方式。)__,它只是将数据写到磁盘的某个位置上。
|
||||
3. 装载(mount)将某种文件系统类型的磁盘设备安装到挂载点上,这个过程__将设备驱动和文件系统类型进行了绑定__。对挂载点下所有文件的读写都将转化为对该设备块的读写。
|
||||
|
||||
文件系统被实际安装时,将__在安装点创建一个vfsmount结构体__。该结构体用来__代表文件系统的一个实例(不同设备可以有相同的文件类型,所以当它们被挂载时,__
|
||||
就说实例化了一个文件系统类型__)__——换句话说,代表一个安装点。
|
||||
|
||||
vfsmount结构被定义在<linux/mount.h>中,下面是具体结构
|
||||
|
||||
―――――――――――――――――――――――――――――――――――――――
|
||||
struct vfsmount
|
||||
{
|
||||
struct list_head mnt_hash; /*哈希表*/
|
||||
struct vfsmount *mnt_parent; /*__父文件系统,注意不是父目录__*/
|
||||
struct dentry *mnt_mountpoint; __/*安装点的目录项对象*/__
|
||||
struct dentry *mnt_root; /*__该文件系统的根目录项对象__*/
|
||||
struct super_block *mnt_sb; __/*该文件系统的超级块*/__
|
||||
struct list_head mnt_mounts; **/*子文件系统链表*/**
|
||||
struct list_head mnt_child; /*和父文件系统相关的子文件系统*/
|
||||
atomic_t mnt_count; /*使用计数*/
|
||||
int mnt_flags; **/*安装标志*/**
|
||||
char *mnt_devname; /*设备文件名字*/
|
||||
struct list_head mnt_list; /*描述符链表*/
|
||||
};
|
||||
|
||||
――――――――――――――――――――――――――――――――――――――
|
||||
|
||||
文件系统如果仅仅注册,那么还不能被用户使用。要想使用它还必须将文件系统安装到特定的安装点后才能工作。下面我们接着介绍文件系统的安装[7][7]过程。
|
||||
|
||||
===== 安装过程 =====
|
||||
用户在用户空间调用mount()命令——指定安装点、安装的设备、安装的文件系统类型等——安装指定文件系统到指定目录。__mount(__)系统调用在内核中的实现函数为__sys_mount()__,该函数调用的主要例程是__do_mount()__,它会取得**安装点的目录项对象**,然后调用__do_add_mount()__例程。
|
||||
|
||||
do_add_mount()函数主要做的是首先使用__do_kern_mount()__函数创建一个安装点,再使用__graft_tree()__将安装点作为叶子与根目录树挂接起来。
|
||||
|
||||
整个安装过程中最核心的函数就是do_kern_mount()了,为了创建一个新安装点(vfsmount),该函数需要做一下几件事情:
|
||||
1 检查安装设备的权利,只有root权限才有能力执行该操作。
|
||||
2 __Get_fs_type()__在文件链表中取得相应文件系统类型(注册时被填加到链表中)。
|
||||
3 __Alloc_vfsmnt()__调用slab分配器为vfsmount结构体分配存储空间,并把它的地址存放在mnt局部变量中。
|
||||
4 初始化mnt->mnt_devname域
|
||||
5 分配新的**超级块**并初始化它。
|
||||
|
||||
do_kern_mount( )检查file_system_type描述符中的标志以决定如何进行如下操作:
|
||||
根据文件系统的标志位,选择相应的方法读取超级块(比如对Ext2,romfs这类文件系统调用get_sb_dev();
|
||||
对于这种没有实际设备的虚拟文件系统如 ramfs调用get_sb_nodev())——读取超级块最终要使用文件系统类型中的read_super方法。
|
||||
|
||||
安装过程做的最主要工作是创建安装点对象,挂接给定文件系统到根文件系统的指定接点下,然后初始化超级快对象,从而获得文件系统基本信息和相关操作方法(比如读取系统中某个inode的方法)。
|
||||
|
||||
总而言之,__注册过程是告之内核给定文件系统存在于系统内;而安装是请求内核对给定文件系统进行支持,使文件系统真正可用__。
|
||||
|
||||
===== 文件系统的读写 =====
|
||||
要自己创建文件系统必须知道文件系统需要那些操作,各种操作的功能范围,所以我们下面内容就是分析Linux文件系统文件读写过程,从中获得文件系统的基本功能函数信息和作用范围。
|
||||
|
||||
==== 打开文件(普通文件,字符、块等设备文件、FIFO、Socket文件除外) ====
|
||||
在对文件进行写前,必须先打开文件。__打开文件的目的是为了能使得目标文件能和当前进程关联,同时需要将目标文件的索引节点从磁盘载入内存,并初始化__。
|
||||
|
||||
open操作主要包含以下几个工作要做(实际多数工作由sys_open()完成):
|
||||
l 1分配文件描述符号。
|
||||
l 2获得新文件对象(struct file)。
|
||||
l 3获得目标文件的目录项对象(struct dentry)和其索引节点对象(struct inode,主要通过open_namei()函数)——具体的讲是通过调用__父目录的索引节点对象__(该索引节点或是安装点或是当前目录)的__lookup方法__找到__目录项对应的索引节点号ino__(目录项__先于__索引节点对象建立和初始化,因为在查找前我们以及知道了要查找的文件名称。注意目录项对象是一种逻辑对象,不存在与实际的磁盘中。),然后调用__iget(sb,ino)__从磁盘读入相应索引节点并在内核中建立起相应的索引节点(inode)对象(其实还是通过调用__sb->s_op->read_inode()超级块提供的方法__),最后还要使用__d_add(dentry,inode)函数__将目录项对象与inode对象连接起来。
|
||||
l 4初始化目标文件对象的域,特别是**把f_op域设置成索引节点中i_fop指向文件对象操作表**——以后对文件的所有操作将调用该表中的实际方法。
|
||||
l 5如果定义了文件操作的open方法(缺省),就调用它。
|
||||
|
||||
到此可以看到打开文件后,文件相关的“上下文”即文件对象、目录项对象、索引节点等都已经生成就绪,下一步就是实际的文件读写操作了。
|
||||
|
||||
==== 文件读写 ====
|
||||
用户空间通过read/write系统调用进入内核执行文件操作,read操作通过sys_read内核函数完成相关读操作,write通过sys_write内核函数完成相关写操作。简而言之,sys_read( ) 和sys_write( )几乎执行相同的步骤,请看下面:
|
||||
l1 调用fget( )从fd获取相应文件对象file,并把引用计数器file->f_count加1。
|
||||
l2 检查file->f_mode中的标志是否允许请求访问(读或写操作)。
|
||||
l3 __调用locks_verify_area( )检查对要访问的文件部分是否有强制锁。__
|
||||
l4 调用file->f_op->read 或file->f_op->write来传送数据。两个函数都返回实际传送的字节数。
|
||||
l5 调用fput( )以减少引用计数器file->f_count的值
|
||||
l6 返回实际传送的字节数。
|
||||
|
||||
搞清楚大体流程了吧?但别得意,现在仅仅看到的是文件读写的皮毛。因为这里的读写方法__仅仅是VFS提供的抽象方法(并不涉及到与物理磁盘相关的驱动操作)__,具体文件系统的读写操作可不是表面这么简单,接下来我们试试看能否用比较简洁的方法把从这里开始到数据被写入磁盘的复杂过程描述清楚。
|
||||
|
||||
现在我们要进入文件系统最复杂的部分——实际读写操作了。f_op->read/f_op->write两个方法__属于实际文件系统的读写方法__,但是对于基于磁盘的文件系统(必须有I/O操作),比如EXT2等, 所使用的实际的读写方法都是利用Linux系统业以提供的通用函数——generic_file_read/generic_file_write完成的,这些通用函数的作用是__确定正被访问的数据所在物理块的位置,并激活块设备驱动程序开始数据传送__,它们针对Unix风格的文件系统都能很好的完成功能 ,所以**没必要自己再实现专用函数**了。下面来分析这些通用函数。
|
||||
|
||||
先说读方法:
|
||||
第一部分利用给定的文件偏移量(ppos)和读写字节数(count)计算出数据所在页[8][8]的逻辑号(index,即**页帧号**)。
|
||||
第二 然后开始传送数据页。(可见__VFS对文件的操作是以page为单位的__。)
|
||||
第三 更新文件指针,记录时间戳等收尾工作。
|
||||
|
||||
其中最复杂的是第二部,首先内核会检查数据是否已经驻存在__页高速缓存page cache__(page= __find_get_page__(mmaping ,index),其中mammping为__struct address_space *类型的页高速缓存对象__,index为逻辑页号),如果在高速缓存中发现所需数据而且数据是有效的(通过检查一些标志位,如,PG_uptodate),那么内核就可以从缓存中快速返回需要的页;否则如果页中的数据是无效的,那么内核将分配一个新页面,然后将其加入到页高速缓存中,随即__使用address_space对象的readpage方法(mapping->a_ops->readpage(file,page))激活相应的函数使磁盘到页的I/O数据传送__。完成之后[9][9]还要调用file_read_actor( )方法把页中的数据拷贝到用户态缓冲区,最好进行一些收尾等工作,如更新标志等。
|
||||
{{./4}}
|
||||
图4 linux地址空间相关的数据结构(取自深入理解Linux虚拟内存管理)
|
||||
|
||||
到此为止,我们才要开始涉及和磁盘系统I/O层打交道了,下面我们就来分析readpage函数具体如何激活磁盘到页的I/O传输。
|
||||
|
||||
address_space对象的readpage方法存放的是函数地址,**该函数激活从物理磁盘到页高速缓存的I/O数据传送**。对于普通文件,该函数指针指向block_read_full_page( )函数的封装函数。例如,REISEFS文件系统的readpage方法指向下列函数实现:
|
||||
|
||||
int reiserfs _readpage(struct file *file, struct page *page)
|
||||
{
|
||||
return block_read_full_page(page, reiserfs _get_block);
|
||||
}
|
||||
|
||||
需要封装函数是因为block_read_full_page( )函数接受的参数为待填充页的页描述符及有助于block_read_full_page()找到正确块的__函数get_block__的地址。该函数依赖于具体文件系统,作用是__把相对于文件开始位置的块号转换为相对于磁盘分区中块位置的逻辑块号__。在这里它指向reiserfs _get_block( )函数的地址。
|
||||
|
||||
block_read_full_page( )函数目的是**对页所在的缓冲区(一个页中一般包含多个连续的磁盘块缓冲区buffer cache)**启动页I/O操作,具体将要完成这几方面工作:
|
||||
1. 调用create_empty_buffers( )为页中包含的所有缓冲区(block buffer)[10][10]分配**异步缓冲区首部(**__buffer_head__**)。参考:**[[块缓冲区和块缓冲区头]]
|
||||
2. 从页所对应的文件偏移量(page->index域)导出页中第一个块的文件块号
|
||||
3. 初始化缓冲区首部,最主要的工作是通过get_block函数进行磁盘寻址,找到**缓冲区的逻辑块号**(相对于磁盘分区的开始而不是普通文件的开始)
|
||||
4. __对于页中的每个缓冲区首部,对其调用submit_bh( )函数__,指定操作类型为READ。
|
||||
|
||||
接下来的工作就该交给I/O传输层处理了,I/O层负责磁盘访问请求调度和管理传输动作。我们简要分析submit_bt()函数,该函数总体来说目的是向**tq_disk任务队列**[11][11]提交请求,但它所做工作颇多,下面就简要分析该函数的行为:
|
||||
1. 从b_blocknr(逻辑块号)和b_size(块大小)两个域确定磁盘上第一个块的__扇区号__,即b_rsector域的值
|
||||
2. 调用__generic_make_request()__函数向低级别驱动程序[12][12]发送请求,它接受的参数为缓冲区首部bh和操作类型rw。而该函数从低级驱动程描述符__blk_dev[maj]__中获得设备驱动程序请求队列的描述符,接着调用__请求队列描述符的make_request_fn方法__
|
||||
|
||||
make_request_fn方法是**请求队列定义的合并相临请求,排序请求的主要执行函数**。它将首先__创建请求__(实际上就是缓冲头和磁盘扇区的映射关系);然后检查请求队列是否为空:
|
||||
1. 如果请求队列为空,则把新的请求描述符插入其中,而且还要将请求队列描述符插入**tq_disk任务队列**,随后再调度低级驱动程序的策略例程的活动。
|
||||
2. 如果请求队列不为空,则把新的请求描述符插入其中,试图把它与其他已经排队的请求进行组合(使用电梯调度算法)。
|
||||
|
||||
低级驱动程序的活动策略函数是request_fn方法。
|
||||
策略例程通常在新请求插入到空列队后被启动。随后队列中的所有请求要依次进行处理,直到队列为空才结束。
|
||||
策略例程request_fn(定义在请求结构中)的执行过程如下:
|
||||
|
||||
1. 策略例程处理队列中的第一个请求并设置__块设备控制器__,使数据传送完成后产生一个中断。然后策略例程就终止。
|
||||
2. 数据传送完毕后块设备控制器产生中断,中断处理程序就激活下半部分。这个下半部分的处理程序把这个请求从队列中删除(end_request( ))并重新执行策略例程来处理队列中的下一个请求。
|
||||
|
||||
好了,写操作说完了,是不是觉得不知所云呀,其实上面仅仅是抽取写操作的骨架简要讲解,具体操作还要复杂得多,下面我们将上面的流程总结一便。
|
||||
|
||||
粗略地分,读操作依次需要经过:
|
||||
1. 用户界面层——负责从用户函数经过系统调用进入内核;
|
||||
2. 基本文件系统层——负责调用文件写方法,从高速缓存中搜索数据页,返回给用户。
|
||||
3. I/O调度层——负责对请求排队,从而提高吞吐量。
|
||||
4. I/O传输层——__利用任务列队异步操作设备控制器__完成数据传输。
|
||||
|
||||
请看下图4给出的逻辑流程。
|
||||
{{./fs4.jpg}}
|
||||
图 4 读操作流
|
||||
|
||||
写操作和读操作大体相同,不同之处主要在于写页面高速缓存时,稍微麻烦一些,因为写操作不象__读操作那样必须和用户空间同步[13][13]执行__,所以用户写操作更新了数据内容后往往先存先存储在页高速缓存中,然后等__页回写后台例程bdflush和kupdate__[14][14]等来完成写如磁盘的工作。当然写入请求处理还是要通过上面提到的submit_bh函数[15][15]进行I/O处理的。下面简要介绍写过程:
|
||||
|
||||
page = __grab_cache_page(mapping,index,&cached_page,&lru_pvec);
|
||||
status = a_ops->prepare_write(file,page,offset,offset+bytes);
|
||||
page_fault = filemap_copy_from_user(page,offset,buf,bytes);
|
||||
status = a_ops->commit_write(file,page,offset,offset+bytes);
|
||||
|
||||
首先,在页高速缓存中搜索需要的页,如果需要的页不在高速缓存中,那么内核在高速缓存中新分配一空闲项;下一步,prepare_write()方法被调用,**为页分配异步缓冲区首部**;接着数据被**从用户空间拷贝到了内核缓冲**;最后通过commit_write()函数将对应的函数__把基础缓冲区标记为脏,以便随后它们被页回写例程写回到磁盘__。
|
||||
|
||||
好累呀,到此总算把文件读写过程顺了一便,大家明白了上述概念后,我门进入最后一部分:Romfs事例分析。
|
||||
|
||||
===== 实例—romfs文件系统的实现 =====
|
||||
文件系统实在是个庞杂的“怪物”,我很难编写一个恰当的例子来演示文件系统的实现。开始我想写一个纯虚文件系统,但考虑到它几乎没有实用价值,而且更重要的是虚文件系统不涉及I/O操作,缺少现实文件系统中置关重要的部分,所以放弃了;后来想写一个实际文件系统,但是那样工程量太大,而且也不容易让大家简明扼要的理解文件系统的实现,所以也放弃了。最后我发现内核中提供的romfs文件系统是个非常理想的实例,它即有实际应用结构也清晰明了,我们以romfs为实例分析文件系统的实现。
|
||||
|
||||
**Linux文件系统实现要素**
|
||||
编写新文件系统自己需要一些基本对象[16][16]。具体的讲创建文件系统需要建立“一个结构四个操作表”:
|
||||
1. 文件系统类型结构(file_system_type)、
|
||||
2. 超级块操作表(super_operations)、
|
||||
3. 索引节点操作表(inode_operations)、
|
||||
4. 页高速缓存(address_space_operations)、
|
||||
5. 文件操作表(file_operations)。
|
||||
|
||||
对上述几种结构的处理贯穿了文件系统的主要操作过程,理清晰这几种结构之间的关系是编写文件系统的基础,下来我们具体分析这几个结构和文件系统实现的要点。
|
||||
{{./fs5.jpg}}
|
||||
|
||||
你必须首先建立一个文件系统类型结构来“描述”文件系统,它含有文件系统的名称、类型标志以及get_sb等操作。当你安装文件系统时(mount)时,系统会解析“文件系统类型结构”,然后使用get_sb函数来建立超级节点“sb”,注意对于基于块的文件系统,如ext2、romfs等,需要从文件系统的宿主设备读入超级块来在内存中建立对应的超级节点,如果是虚文件系统的话,则不是读取宿主设备的信息(因为它没有宿主设备),而是在现场创建一个超级节点,这项任务由get_sb完成。
|
||||
|
||||
超级节点是一切文件操作的鼻祖,因为__超级块是我们寻找索引节点——索引节点对象包含了内核在操作文件或目录时需要的全部信息——的唯一源头__,我们操作文件必然需要获得其对应的索引节点(这点和建立超级节点一样或从宿主设备读取或现场建立),而__获取索引节点是通过超级块操作表提供的read_node函数完成的__,同样操作索引节点的底层次任务,如创建一个索引节点、释放一个索引节点,也都是通过超级块操作表提供的有关函数完成的。所以超级块操作表是我们第二个需要创建的数据类型。
|
||||
|
||||
除了派生或释放索引节点等操作是由超级块操作表中的函数完成外,索引节点还需要许多自操作函数,比如lookup搜索索引节点,建立符号连接等,这些函数都包含在索引节点操作表中,因此我们下一个需要创建的数据类型就是索引节点操作表。
|
||||
|
||||
为了提高文件系统的读写效率,Linux内核设计了I/O缓存机制。所有的数据无论出入都会经过系统管理的高速缓存——对于非基于块的文件系统则可跳过该机制。处于操作数据的目的,__页高速缓存同样提供了一函数操作表__,其中包含有readpage()、writepage()等函数负责操作高速缓存中的页读写。
|
||||
|
||||
文件系统最终和用户交互还需要实现文件操作表,该表中包含有关用户读写文件、打开、关闭、影射等用户接口。
|
||||
|
||||
对于基于块的文件系统实现的一般方式而言,都离不开以上5种数据结构。但根据文件系统的特点(如有的文件系统只可读、有的没有目录),并非要实现操作表中的全部的函数,换句话说,你只需要实现部分函数,而且很多函数系统都已经存在现成的通用方法,因此留给你做的事情其实不多。
|
||||
|
||||
===== Romfs文件系统是什么 =====
|
||||
Romfs是基于块的只读文件系统,它使用块(或扇区)访问存储设备驱动(比如磁盘,CD,ROM盘)。由于它小型、轻量,所以常常用在嵌入系统和系统引导时。
|
||||
|
||||
Romfs是种很简单的文件系统,它的文件布局和Ext2等文件系统相比要简单的得多。它比ext2文件系统要求更少的空间。空间的节约来自于两个方面,首先内核支持romfs文件系统比支持ext2文件系统需要更少的代码,其次romfs文件系统相对简单,在建立文件系统超级块(superblock)需要更少的存储空间。Romfs文件系统不支持动态擦写保存,对于系统需要动态保存的数据采用虚拟ram盘的方法进行处理(ram盘将采用ext2文件系统)。
|
||||
|
||||
下面我们来分析一下它的实现方法,为读者勾勒出编写新文件系统的思路和步骤。希望能为大家自己设计文件系统起到抛砖引玉的效果。
|
||||
|
||||
==== Romfs文件系统布局与文件结构 ====
|
||||
__文件系统简单理解就是数据的分层存储结构__,**数据由文件组织,而**__文件又由文件系统安排存储形式__,所以首先__必须设计信息和文件组织形式__——也就是这里所说的文件布局。
|
||||
在Linux内核源代码种得Document/fs/romfs中介绍了romfs(下载romfs)文件系统得布局和文件结构。现面我们简要说明一下它们。
|
||||
{{./fs6.jpg}}
|
||||
图 5 romfs文件系统布局
|
||||
|
||||
上图是romfs布局图,可以看到文件系统中每部分信息都是16位对其的,也就是说存储的偏移量必须最后4位为0,这样作是为了提高访问速度。随意如果信息不足时,需要填充0以保证所有信息的开始位置都为16为对其[17][17]。
|
||||
|
||||
文件系统的开始8个字节存储文件系统的ASCII形式的名称,比如“romfs”;接着4个字节记录文件大小;然后的4个字节存储的是文件系统开始处512字节的检验和;接下来是卷名;最后是第一个文件的文件头,从这里开始依次存储的信息就是文件本身了。
|
||||
|
||||
Romfs的文件结构也非常简单,我们看下图
|
||||
{{./fs7.jpg}}
|
||||
|
||||
===== 具体需要实现的对象 =====
|
||||
Romfs文件系统定义针对**文件系统布局和文件结构**定义了一个磁盘超级块结构和磁盘inode(对应于文件)结构:
|
||||
|
||||
struct romfs_super_block {
|
||||
__u32 word0;
|
||||
__u32 word1;
|
||||
__u32 size;
|
||||
__u32 checksum;
|
||||
char name[0];
|
||||
|
||||
};
|
||||
|
||||
struct romfs_inode {
|
||||
__u32 next;
|
||||
__u32 spec;
|
||||
__u32 size;
|
||||
__u32 checksum;
|
||||
char name[0];
|
||||
};
|
||||
|
||||
上述两种结构分别描述了**文件系统结构**与**文件结构**,它们将在内核装配超级块对象和索引节点对象时被使用。
|
||||
Romfs文件系统首先要定义的对象是**文件系统类型romfs_fs_type**。定义该对象同时还要**定义读取超级块的函数r**omfs_read_super。
|
||||
|
||||
ramfs_read_super()作用是从磁盘读取磁盘超级块给超级块对象,具体行为如下
|
||||
1 装配超级块。
|
||||
1.1 初始化超级块对象某些域。
|
||||
1.2 从设备中读取__磁盘第0块__到内存bread(dev,0,ROMBSIZE),其中dev是文件系统安装时指定的设备,0指设备的首块,也就是磁盘超级块,ROMBSIZE是读取的大小。
|
||||
1.3 检验磁盘超级块中的校验和
|
||||
1.4 继续初始化超级块对象某些域
|
||||
2 给超级块对象的操作表赋值(__s->s_op = &romfs_ops__)
|
||||
3 为根目录(即**文件系统的根目录**)分配目录项 s->s_root = d_alloc_root(iget(s,sz)), sz为文件系统开始偏移。
|
||||
|
||||
超级块操作表中romfs文件系统实现了两个函数
|
||||
static struct super_operations romfs_ops = {
|
||||
read_inode: romfs_read_inode,
|
||||
statfs: romfs_statfs,
|
||||
};
|
||||
|
||||
第一个函数read_inode(inode)是用磁盘上的数据填充参数指定的索引节点对象的域;索引节点对象的i_ino域标识从磁盘上要读取的具体文件系统的索引节点。
|
||||
1 根据inode参数寻找对应的索引节点。
|
||||
2 初始化索引节点某些域
|
||||
3 根据文件的访问权限(类别)设置索引节点的相应操作表
|
||||
3.1 如果是__目录文件__则将索引节点表设为i->i_op = &romfs_dir_inode_operations;文件操作表设置为->i_fop = &romfs_dir_operations; 如果索引节点对应目录的话,那么需要的操作仅仅会是__lookup操作__(因为romfs是个功能很有限的文件系统);对于文件操作表中的两个方法一个为read,另一个为readdir。前者利用通用函数generic_read_dir,返回用户错误消息。后者是针对readdir/getdents等系统调用实现的返回目录中文件的函数。
|
||||
3.2 如果是__常规文件__,则将文件操作表设置为i->i_fop = &generic_ro_fops;
|
||||
|
||||
将页高诉缓存表设置为**i->i_data.a_ops = &romfs_aops; **
|
||||
|
||||
由于romfs是只读文件系统,它在对正规文件操作时不需要索引节点操作,如mknod,link等,因此不用管索引节点操作表。
|
||||
|
||||
对常规文件的操作也只需要使用__内核提供的通用函数表struct generic_ro_fops__ ,它包含基本的三种常规文件操作:
|
||||
|
||||
* llseek: generic_file_llseek,
|
||||
* read: generic_file_read,
|
||||
* mmap: generic_file_mmap,
|
||||
|
||||
利用这几种通用函数,完全能够满足romfs文件系统的的文件操作要求,具体函数请自己阅读源代码。
|
||||
|
||||
回忆前面我们提到过的页高速缓存,显然__常规文件访问__需要经过它,因此有必要实现页高诉缓存操作。因为只需要读文件,所以只用实现**romfs_readpage函数**,这里readpage函数使用辅助函数romfs_copyfrom完成将数据从设备读入页高速缓存,该函数根据文件格式从设备读取需要的数据。设备读取操作需要使用__bread块I/O例程__,它的作用是从设备读取指定的块[18][18]。
|
||||
|
||||
3.3 如果是链接文件,则将索引节点操作表设置为:i->i_op=&page_symlink_inode_operations;
|
||||
|
||||
将页高速缓存操作表设置为:
|
||||
i->i_data.a_ops = &romfs_aops;
|
||||
符号连接文件需要使用通用符号连接操作page_symlink_inode_operations实现,同时也需要使用页高速缓存方法。
|
||||
|
||||
3.4 如果是套接字或管道则,进行特殊文件初始化操作init_special_inode(i, ino, nextfh);
|
||||
|
||||
到此,我们已经遍例了romfs文件系统使用的几种对象结构:romfs_super_block、romfs_inode、romfs_fs_type、super_operations romfs_ops、address_space_operations romfs_aops 、file_operations romfs_dir_operations、inode_operations romfs_dir_inode_operations 。实现上述对象是设计一个新文件系统的最低要求。
|
||||
|
||||
|
||||
|
||||
最后要说明的是为了使得romfs文件系统作为模块挂载,需要实现static int __init init_romfs_fs(void)
|
||||
|
||||
{
|
||||
return register_filesystem(&romfs_fs_type);
|
||||
}
|
||||
|
||||
和
|
||||
|
||||
static void __exit exit_romfs_fs(void)
|
||||
|
||||
{
|
||||
unregister_filesystem(&romfs_fs_type);
|
||||
}
|
||||
|
||||
两个在安装romfs文件系统模块时使用的例程。
|
||||
|
||||
安装和卸载
|
||||
|
||||
module_init(init_romfs_fs)
|
||||
module_exit(exit_romfs_fs)
|
||||
|
||||
到此,romfs文件系统的关键结构都已介绍,至于细节还是需要读者仔细推敲。Romfs是最简单的基于块的只读文件系统,而且没有访问控制等功能。所以很多访问权限以及写操作相关的方法都不必去实现。
|
||||
|
||||
使用romfs首先需要genromfs来制作romfs文件系统镜象(类似于使用mke2fs格式化文件系统),然后安装文件系统镜象 mount –t romfs ********。 Romfs可以在编译内核时制定编译成模块或编如内核,如果是模块则需要首先加载它。
|
||||
|
||||
===== 小结: =====
|
||||
实现文件系统必须想上要清楚文件系统和系统调用的关系,想下要了解文件系统和I/O调度、设备驱动等的联系。另外还必须了解关于缓存、进程、磁盘格式等概念。在这里并没有对这些问题进行是深入分析,仅仅提供给大家一个文件系统全景图,希望能对自己设计文件系统有所帮助。文件系统内容庞大、复杂,许多问题我也不确定,有文件系统经验的朋友希望能够广泛交流。
|
||||
|
||||
[1][1] 请参见 OPERATION SYSTEMS INTERNALS AND DESIGN PRINCIPLES 一书第12章
|
||||
|
||||
[2][2] 扇区是磁盘的最小寻址单元单元,而文件块是内核操作文件的最小单位,一个块可以包含一个或数个扇区。这些磁盘块被读入内存后即刻被存入缓冲中,同样文件块被写出是也要通过缓冲。
|
||||
|
||||
[3][3] 如果文件按记录形式组织,那么在数据在成为文件块前,还要经过记录形式的阶段。
|
||||
|
||||
[4][4] 摘自 Linux 内核开发 中第 11 章中文件系统抽象层一节
|
||||
|
||||
[5][5] 请看Linux 内核开发 一书第11章
|
||||
|
||||
[6][6] 在2.6内核以后,缓冲头的作用比不象以前那么重要了。因为2.6中缓冲头仅仅作为内核中的I/O操作单元,而在2.6以前缓冲头不但是磁盘块到物理内存的映射,而且还是所有块I/O操作的容器。
|
||||
|
||||
[7][7] 这里安装的文件系统属于非根文件系统的安装方法。根文件系统安装方法有所区别,请查看相关资料。
|
||||
|
||||
[8][8] 无论读文件或写文件,文件中的数据都是必须经过内存中的页高速缓存做中间存储才能够被使用。高速缓存由一个叫做address_space的特殊数据结构表示,其中含有对页高速缓存宿主(address_space->host)的操作表。
|
||||
|
||||
[9][9] 这期间要要处理一些预读,以此提高未来访问的速度。
|
||||
|
||||
[10][10] 缓冲与相应的块一一对应,它的作用相当于是磁盘块在内存中的表示。
|
||||
|
||||
[11][11] tq_disk是专门负责磁盘请求的任务队列,任务队列是用来推后异步执行的一种机制。2.6内核中已经用工作队列代替了工作队列。
|
||||
|
||||
[12][12] 块设备驱动程序可以划分为两部分:底级驱动程序(blk_dev_struct)和高级设备驱动(block_device)。底级设备驱动程序作用是记录每个高级驱动程序送来的请求组成的队列。
|
||||
|
||||
[13][13] 文件读区操作必须同步进行,在读取的数据返回前,工作无法继续进行。而且如果结果在30秒内回不来,则用户必需将无法忍受,所以读操作执行紧迫。而对于写操作,则可以异步执行,因为写入操作一般不会影响下一步的执行,所以紧迫性也低。
|
||||
|
||||
[14][14] bdflush和kupdate 分别是当空闲内存过低时释放脏页和当脏缓冲区在内存中存在时间过长时刷新磁盘的。而在2.6内核中,这两个函数的功能已经被pdflush统一完成。
|
||||
|
||||
[15][15] 实际是从block_read_full_page( )函数中调用submit_bh()函数的。
|
||||
|
||||
[16][16] 对象指的是内存中的结构体实例,而不是物理上的存储结构。
|
||||
|
||||
[17][17] 创建romfs文件系统可使用genromfs格式化工具。
|
||||
|
||||
[18][18] 索引节点结构描述了从物理块(块设备上的存取单位,是每次I/O操作最小传输的数据大小)到逻辑块(文件实际操作基本单元)的映射关系。
|
||||
BIN
Zim/内核开发/linux文件系统/如何实现一个文件系统/4
Normal file
|
After Width: | Height: | Size: 103 KiB |