add lots file of APUE

This commit is contained in:
geekard
2012-10-30 20:31:20 +08:00
parent 05e8ae5877
commit 6642e173f9
113 changed files with 4954 additions and 181 deletions

Binary file not shown.

View File

@@ -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]

View 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 Stallmans 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 Cant 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 doesnt swear, he recurses.
锁定供应商是指供应商把自己锁在建筑物中以远离Richard Stallman的愤怒。
Vendor lock-in is when vendors lock themselves inside of a building out of fear of Richard Stallmans 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 doesnt look at a Windows computer, it segfaults.
Richard Stallman 用0和1记笔记
Richard Stallman takes notes in binary.
Richard Stallman的左手叫做”(“,右手叫做”)”.
zerd: lisp语言书写出来有大量的”()”
Richard Stallmans 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 写了一个非常强大的程序,他知道宇宙的终极秘密。
zerd42是科幻小说中”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 Universes first segfault.
Richard Stallman 的理想还未实现
zerd这句没看懂。wget是linux下的一个程序。wget对应wdemandStallman的思想是free software目前还没有实现。
Richard Stallman doesnt wget, Richard Stallman wdemands!
Richard Stallman 能用正则表达式解析HTML。
Richard Stallman can parse HTML with regex.
Richard Stallman 不需要sudo我反正要给他做一个三明治
zerd: 真的没看懂
Richard Stallman doesnt need sudo. I will make him a sandwich anyway.
Stallman 家里没有windows即使门上也没有
zerdstallman家里没有窗户呵呵
There is no Windows in Stallmans 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 doesnt 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.

View File

@@ -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
###################################################################

View 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”这一栏困惑过到底什么是LoadLoad代表了什么含义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的时候就表明系统在超负荷运转了。

View 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;
}

View 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。相比于selectepoll最大的好处在于它**不会随着监听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-connectionepoll的效率并不会比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;
}

View File

@@ -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之后,保证重新建立的所得到的数据绝对不会是发往就连接的数据.

View File

@@ -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) 任务队列:线程池的概念具体到实现则可能是队列,链表之类的数据结构,其中保存执行线程。
  我们实现的通用线程池框架由五个重要部分组成CThreadManageCThreadPoolCThreadCJobCWorkerThread除此之外框架中还包括线程同步使用的类CThreadMutex和CCondition。

View File

@@ -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值非00 = 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值非0l_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中,是允许的.

View File

@@ -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
以上解释了测试12的现象,write一个已经接受到RST的socket系统内核会发送SIGPIPE给发送进程如果进程catch/ignore这个信号write都返回EPIPE错误.
因此,UNP建议应用根据需要处理SIGPIPE信号至少不要用系统缺省的处理方式处理这个信号系统缺省的处理方式是退出进程这样你的应用就很难查处处理进程为什么退出。
原文地址 http://blog.chinaunix.net/u/31357/showart_242605.html

View 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)

View File

@@ -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为非0l_linger为0则套接口关闭时TCP夭折连接TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个__RST__给对方而不是通常的四分组终止序列这避免了**TIME_WAIT**状态;
3、设置 l_onoff 为非0l_linger为0套接口关闭时内核将拖延一段时间由l_linger决定。如果套接口缓冲区中仍残留数据**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到a所有数据发送完且被对方确认之后进行正常的终止序列描述字访问计数为0b延迟时间到。此种情况下应用程序检查close的返回值是非常重要的如果在数据发送完并被确认前时间到(超时)close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据和FIN已由对方TCP确认它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的它将不等待close完成。
2、设置 l_onoff为非0l_linger为0套接口关闭时TCP夭折连接TCP将**丢弃保留**在套接口发送缓冲区中的任何数据并发送一个__RST__给对方而不是通常的四分组终止序列这避免了**TIME_WAIT**状态;
3、设置 l_onoff 为非0l_linger为非0当套接口关闭时内核将拖延一段时间由l_linger决定。如果套接口缓冲区中仍残留数据**进程将处于睡眠状态(注意close调用不是立即返回)**,直 到a所有数据发送完且__被对方确认__之后进行正常的终止序列描述字访问计数为0b延迟时间到。此种情况下应用程序__检查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 非零 优雅 是

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

View File

@@ -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}}
正常输出。
做一个小结:

View File

@@ -122,13 +122,3 @@ Base64编码可用于在HTTP环境下传递较长的__标识信息__。例如
MIME::Base64 Perl module
Firefox extension
emacs函数
===== 参见 =====
Radix-64
ASCII85
Quoted-printable
uuencode
yEnc
8BITMIME
URL

View File

@@ -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>

View 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

View 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__
>>>

View 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
>>>

View 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'
>>>

View 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]

View 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])
>>>

View 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.

View 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
>>>

View 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

View 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 的作者) 在 PyCon2008 上报告的幻灯片,强烈推荐!!这篇文章的很多内容都来自或者受这份幻灯片的启发而来。
在上一篇文章里介绍了 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 把所有文件内容合并输出到 stdoutgrep 从 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因为篇幅比较长具体做法放到下一篇文章中。

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -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 __wordswords是一组连续的字符用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).

View File

@@ -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的标准输出相连。

View File

@@ -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

View File

@@ -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

View File

@@ -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=

View File

@@ -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.

View File

@@ -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

View File

@@ -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[*]}

View File

@@ -8,7 +8,7 @@ Created Thursday 22 December 2011
===== 条件测试命令: =====
* test 和[ 是**等价**的条件测试命令都可以单独执行但__无输出值只有退出状态0表示测试为真非0表示测试为假。__
* 为**关键字**而非命令,是[ ]的**增强版本**,里面还可以用&&、||、<、>等逻辑和关系运算符但__不能有算术运算符__类似语言的语法~~因此返回值0表示测试为假返回值1表示测试为真这与test和[的返回状态值意义恰好相反,~~一般只用在条件测试中。
* [ [ 和 ] ] 为**关键字**而非命令,是[ ]的**增强版本**,里面还可以用&&、||、<、>等逻辑和关系运算符但__不能有算术运算符__类似语言的语法,一般只用在条件测试中。
* __((...))比较特殊先对__**算术、关系、逻辑表达式**__计算(求值),如果结果非空或非零,则返回状态为真(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文件名 文件存在且为字符类型特殊文件为真

View File

@@ -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
View 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 则表示需要限定的资源,可以有很多候选值,如 stackcpunofile 等等,分别表示最大的堆栈大小,占用的 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
Zim/Utils/ulimit/2 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
Zim/Utils/ulimit/3 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
Zim/Utils/ulimit/4 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
Zim/Utils/ulimit/5 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
Zim/Utils/ulimit/6 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
Zim/Utils/ulimit/7 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
Zim/Utils/ulimit/8 Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View 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有 softhard 和 -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
View 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
View 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协议后者使用11的确认方式效率低下。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
View 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

View 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 systemAS
AS处于同一技术掌控和政策掌控之下的连续的路由范围。为了解决Internet的扩展性并且降低管理成本增加公网Internet流量选路策略的灵活性。
路由协议根据运算机制进行分类:
1 静态路由
2 动态路由
动态路由根据部署的边界进行分类:
1 域内路由intra-domain routing也叫做内部网关协议Interior gateway protocolIGP
2 域间路由inter-domain routing也叫做外部网关协议Exterior gateway protocolEGP
这里的网关等同于AS。
常见的IGPRIPOSPFIS-IS__EIGRPIGRP__
常见的EGPEGPBGPv1, BGPv2, BGPv3, BGPv4
EGP被淘汰是因为他没有一个度量的标准。
BGPv1, BGPv2, BGPv3完全被淘汰
域间路由解决方案要考虑到的问题:
1 扩展性
2 安全性
3 路由策略
BGP对应上述问题衍生出的特点
1 触发式增量更新
2 可靠连接TCP
3 没有度量Metric的概念采用__属性Attribute__来实现选路功能并且有一套非常强大的选路策略工具集。
TCP 179端口作为通信端口滑动窗口三次握手。
三次握手来建立连接:
1 源设备向目标设备发送一个TCP的SYNS————DSYN
2 目标设备向源设备反回一个TCP的SYN ACKd————SSYN ACK
3 源再向目标设备反馈一个TCP的ACKS————DACK
传输数据
四次握手来终止连接:
1 源向目标方发送一个FINS————DFIN
2/3.分别目标设备向源设备发送一个ACK/FIND——SACK/FIN
4 源设备向目标设备发送一个ACKS——DACK
连接终止。
如果收到一个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-IDRID
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、起源于IGPBGP更新是通过BGP进程下network命令所发布出来的。i
2、起源于EGPBGP更新是通过__EGP重分发__进来的。e
3、起源不完整incompleteBGP更新是通过IGPOSPFEIGRP等重分发进来的。?
如果同一个BGP更新有3种起源方式。IGP优先于EGP优先Incomplete
**3、下一跳NEXT_HOP属性**到达目标AS的下一个AS**IP地址**)。
BGP连接关系
1内部BGPInternal BGPIBGP
建立BGP邻居关系的BGP路由器位于同一自治系统。对于IBGP邻居AS-PATH属性是不变下一跳属性也不变。
2外部BGPExternal BGPEBGP
建立BGP邻居关系的BGP路由器位于不同自治系统。对于EBGPAS_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属性为空NULLCISCO设备把空值当成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 EBGPAD=20优先于IBGPAD=200
-------------------------------------------------分水岭------------------------------------------------------------
8a.如果是EBGP更新那么最老最先被学习到的的EBGP更新更优先
8b.如果是IBGP更新离邻居IGP度量值最小的路径优先。
-------------------------------------------------分水岭------------------------------------------------------------
9比较BGP RID越低越优先。
10比较学习到BGP更新的接口地址接口IP地址越小越优先。

View 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&gt;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&gt;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, &gt; 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
*&gt;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, &gt; 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]
*&gt;[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, &gt; best, i - internal,
r RIB-failure, S Stale
Origin codes: i - IGP, e - EGP, ? - incomplete
Network Next Hop Metric LocPrf Weight Path
*&gt; [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, &gt; 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
*&gt; 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, &gt; best, i - internal,
r RIB-failure, S Stale
Origin codes: i - IGP, e - EGP, ? - incomplete
Network Next Hop Metric LocPrf Weight Path
*&gt; 1.1.1.0/24 1.1.1.1 0 0 1 i
*&gt; 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 listvs. 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 expressionregex
[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

View 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的变化以及邻居的识别等技术都将是我们共同探讨的课题。

View 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分钟
链路状态路由协议只在网络拓扑发生变化以后产生路由更新。当链路状态发生变化以后检测到变化的设备创建__LSAlink state advertisement__通过使用__组播地址__传送给所有的__邻居设备__然后每个设备拷贝一份LSA更新它自己的__链路状态数据库link state databaseLSDB__接着再转发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 areabackbone或area 0
  2.regular areasnonbackbone areas
  transit area负责的主要功能是IP包快速和有效的传输。transit area__互联__OSPF其他区域(类型)。一般的这个区域里不会出现端用户end user
  regular areas负责的主要功能就是**连接用户和资源**。这种区域一般是根据功能和地理位置来划分。一般的一个regular area__不允许__其他区域的流量通过它到达另外一个区域__必须穿越transit area__比如area 0. regular areas还可以有很多子类型比如stub arealocally area和not-so-stubby area
在链路状态路由协议中所有的路由器都保持有LSDBOSPF路由器越多LSDB就越大。这可能对了解完整的网络信息有帮助但是随着网络的增长__可扩展性__的问题就会越来越大。采用的__折中方案就是引入区域的概念__。在某一个区域里的路由器__只保持该区域中所有路由器__**或链路的详细信息**和其他区域的一般信息。当某个路由器或某条链路出故障以后,信息**只会**在那个区域以内在邻居之间传递。那个区域以外的路由器不会收到该信息。OSPF要求层次化的网络设计意味着所有的区域要和area 0__直接相连__。如下图  
 {{./1.jpg}}
注意area 1和area 2或3之间的连接是不允许的它们都必须通过backbone area 0进行连接。Cisco建议每个区域中路由器的数量为50到100个构建area 0的路由器称为__骨干路由器__backbone routerBR如上图A和B就是BR__区域边界路由器__area border routerABR连接area 0和nonbackbone areas.如图CD和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 routerDR__再选举一个做为__backup designated routerBDR__所有其他的和DR以及BDR相连的路由器形成完全邻接状态而且**只传输LSA给DR和BDR**.
DR从邻居处转发更新到另外一个邻居那里。DR的主要功能就是在一个LAN内的所有路由器拥有相同的数据库而且把__完整的数据库信息__发送给__新加入__的路由器。路由器之间还会和LAN内的其他路由器非DR/BDR即DR OTHERs维持一种**部分邻居关系**two-way adjacency。OSPF的邻接一旦形成以后会交换LSA来同步LSDBLSA将进行可靠的洪泛
链路状态陆游协议使用Dijkstra算法来查找到达目标网络中的最佳路径。所有的路由器拥有**相同**的LSDB后__把自己放进SPF tree中的root里__然后根据每条链路的耗费cost选出耗费最低的做为最佳路径最后把最佳路径放进**forwarding database路由表**里。下图就是一个SPF计算的例子  
 
{{./2.jpg}}
  1.LSA遵循split horizon原则H对E宣告它的存在E把H的宣告和它自己的宣告再传给C和GC和G再和之前类似继续传播开来……
  2.X有4个邻居ABC和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 descriptionDBD用来检验路由器之间数据库的同步
  3.link state requestLSR链路状态请求包
  4.link state updateLSU特定链路之间的请求记录
  5.link state acknowledgementLSAck确认包
OSPF Packet Header Format
  5种OSPF包都是__直接被封装在IP包里__的而不使用TCP或UDP.由于没有使用可靠的TCP协议但是OSPF包又要求可靠的传输所以就有了LSAck包。如下图所示就是OSPF包在IP包里的形式  
 {{./4.jpg}}
  协议号为89EIGRP协议号为8一些字段如下
  1.Version Number当前为OSPF版本2
  2.Type定义OSPF包的类型
  3.Packet Length包的长度单位字节
  4.**Router IDRID**产生OSPF包的__源路由器__
  5.**Area ID**定义OSPF包是从哪个area产生出来的
  6.Checksum校验和错误校验
  7.**Authentication Type**验证方法可以是明文cleartext密码或者是Message Digest 5MD5加密格式
  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 IDRID路由器的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 addressDR/BDR的IP地址信息
  7.authentication password如果启用了验证邻居路由器之间必须交换相同的密码信息。此项可选
  8.stub area flagstub 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包给邻居之前等待的时间

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

View 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 分别支持 1EB1,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 在文件系统层面实现了持久预分配并提供相应的 APIlibc 中的 posix_fallocate()),比应用软件自己实现更有效率。
13. 默认启用 barrier。 磁盘上配有内部缓存,以便重新调整批量数据的写操作顺序,优化写入性能,因此文件系统必须在日志数据写入磁盘之后才能写 commit 记录,若 commit 记录写入在先而日志有可能损坏那么就会影响数据完整性。Ext4 默认启用 barrier只有当 barrier 之前的数据全部写入磁盘,才能写 barrier 之后的数据。(可通过 "mount -o barrier=0" 命令禁用该特性。)

View 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。后来美国国家科学基金会又开发了NSFNET1995年4月停用。发展到现在__因特网成为了由商业提供者运营的的更分散的体系__。而下一代因特网NGI的计划已于1997年10月启动目前已推出的主要方案有Internet2,Abilene等。
出于__管理和扩展__的目的因特网可以被分割成许多不同的__自治系统__Autonomous System。换句话说因特网是由自治系统汇集而成的。
BGPv4(Border gateway protocol Version 4)——边缘网关协议定义于RFC1771是现行因特网的实施标准__就是用来连接自治系统实现自治系统间的路由选择功能的。__
===== 2. IGP与EGP =====
所有的路由选择协议可以被分成IGP和EGP两种。要了解IGP和EGP的概念应该首先了解自治系统AS的概念。传统的AS定义RFC1771AS是同一个技术管理下的一组路由器它们使用一种内部网关协议和一致的度量尺度来对数据包进行AS内部的路由而使用外部网关协议来对发向其它AS的数据包进行路由选择。发展到现在已经允许并且时常采用在一个自治系统AS中使用多个内部网关协议甚至多个路由选择的度量标准。所以现在的自治系统被扩展的定义为__共享同一路由选择策略的一组路由器。__
IGPInterior gateway protocols——内部网关协议定义为在一个自治系统内部使用的路由协议包括动态路由协议和静态路由。IGP的功能是完成数据包在AS内部的路由选择或者说是讲述数据包如何穿过本地AS的。RIPv1&v2IGRPEIGRPOSPF**ISIS**都是典型的IGP。
EGPExterior gateway protocols——外部网关协议定义为在多个自治系统之间使用的路由协议。它主要完成数据包在AS间的路由选择或者说讲述数据包为了到达目的IP需要通过哪些AS。BGP4就是一种EGP。
IGP只作用于本地AS内部而对其他AS一无所知。它负责将数据包发到主机所在的网段segment。EGP作用于各AS之间它只了解AS的__整体结构__而不了解各个AS内部的拓扑结构。它只负责将数据包发到相应的AS中余下的工作便交给IGP来做。
每个自治系统AS都有唯一的标识称为__AS号__AS number由IANAInternet Assigned Numbers Authority来授权分配。这是一个16位的二进制数范围为1~65535其中65412~65535为AS专用组RFC2270
{{./1.jpg}}
图一
===== 3BGP-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对等体的连接有两种模式IBGPInternal BGP和EBGPExternal 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消息有__四种类型__:OPENUPDATENOTIFICATION和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路由属性依次是
1ORIGIN产生该路由信息的AS
2AS_PATH包已通过的AS集或序列
3NEXT_HOP要到达该目的下一跳的IP地址IBGP连接不会改变从EBGP发来的NEXT_HOP
4MULTI_EXIT_DISC本地路由器使用区别到其他AS的多个出口
5LOCAL-PREF在本地AS内传播标明各路径的优先级
6ATOMIC_AGGREGATE
7AGGREGATOR。
8COMMUNITY。
其中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消息来通知错误。
===== 5BGP与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是单口的或者说是末端ASStub 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-1X/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连接时才会使用联盟技术。
===== 7BGP的使用 =====
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以及具体实施方案。

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

View 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将__链路状态广播数据包LSALink 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的以太网的链路开销是1016Mbps令牌环网的链路开销是6FDDI或快速以太网的开销是12M串行链路的开销是4856KB串行线路的开销为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因为这个地址是永久不关闭的而且最好有完善的规划。
10LSA与LSU
路由器对__某一条线路__的状态更新称为LSA对__一组链路的状态__更新称为LSU**LSU更新包里可包含多个LSA**。
11DR与BDR
当几台路由器工作在同一网段上时为了__减少网段中路由信息的交换数量__OSPF路由协议定义了DR和BDR。负责__收集__网络中的链路状态通告并将他们__集中__发给其他的路由器。BDR实际上是DR的备份在DR本身没有问题时BDR并不工作一旦DR出现了问题BDR就会接替DR的工作。在实际工程中DR与BDR的概念应用并不广泛。

View 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 -&gt; inactive,referenced
* inactive,referenced -&gt; active,unreferenced
* active,unreferenced -&gt; active,referenced
*/
void mark_page_accessed(struct page *page)
{
if (!PageActive(page) &amp;&amp; !PageUnevictable(page) &amp;&amp;
PageReferenced(page) &amp;&amp; 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

View 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。该函数从应用程序的地址空间中分配一段区域作为映射的内存地址并使用一个VMAvm_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虚拟内存管理)}}

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View 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.Qpage cache里同一个页面里的内容在磁盘上是否连续
A不一定连续因为文件在磁盘上未必连续。
4.Qpage cache和buffer cache是什么关系
Abuffer cache用page cache来实现主要用来作文件不连续时的缓存和缓存磁盘块(如裸盘读写)。
5.Q同一个页面是否可会缓存多个文件的内容
A不会。
6.Q当一个文件被mmap之后再打开它有几份副本存在于内存中
A当使用shared映射时只存在一份。
7.Qpage cache是否换出
A不会刷出(如果需要)后直接回收。
8.Qpage cache中的写操作以什么样的策略刷回磁盘
A有几个因素
1) /proc/sys/vm/dirty_background_ratio
2) /proc/sys/vm/dirty_ratio
3) /proc/sys/vm/dirty_writeback_centisecs
9.Q如何查看当前系统中的磁盘操作
Aecho “1″ > /proc/sys/vm/block_dump
10.Q主要有哪些文件系统/硬盘/iscsi/nas性能测试工具
A我知道的有bonnie/iometer/iozone

View 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 = &amp;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即可

View 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

View 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.

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View 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

View 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_blockinodedentryfilevfsmount等主要数据结构
Ÿ 参见http://blog.chinaunix.net/u2/87570/showart_2126000.html
Q: 哪些接口必须实现?
Ÿ VFS实现了很多通用接口如基本所有的读写操作都可直接使用generic_file_aio_readgeneric_file_aio_write接口我的内核版本为2.6.19ext2的读写就是使用该接口但各个文件系统必须实现自己的read_page方法用于从磁盘读取一页的数据还可实现read_pages一次读取多页以提高效率如果要实现direct_io访问必须实现direct_IO接口。
Ÿ read_page的实现需要基于文件系统实际的数据组织它将用户的文件请求位置逻辑页号转换为物理块号并向通用块层发送请求。
Q: ext2文件系统如何组织文件的数据
Ÿ ext2使用长度为15的数组ext2_inode的一个字段其中前12个数组元素记录直接块映射即其内容即为文件前12个块的地址第13个元素记录一级索引关系即该元素的内容为一个块的地址这个块的内容为一系列块的地址第1415个元素分别为二级索引三级索引。
Q: 内核如何根据用户态传递的路径名得到文件的inodedentry信息
Ÿ 通过路径名查找可以通过路径名得到inodedentry的信息
Ÿ Linux提供了path_lookup接口来实现路径名的解析其具体实现以下工作
1. 获取路径名查找的起点,当前目录或是根目录;
2. 以/为分隔符,解析每个目录项。
3. 针对每个目录项首先查找目录项高速缓存判断当前的目录项对象是否在缓存中如果在则直接从缓存中获取结果如果不在则需要在上一级目录中调用实际文件系统实现的lookup方法查找并读取目录项对应的inode信息填充dentry结构并将该结构加入到高速缓存。
Q: 内核如何根据路径名查找的结果得到file结构
Ÿ 通过dentry_open实现具体执行以下工作
1. 分配一个文件对象;
2. 根据传递进来的dentry信息vfsmnt信息初始化file对象的f_fentryf_vfsmnt
3. 以索引节点的i_fop填充f_op。
4. 将文件对象插入到文件系统超级块的s_files字段所指向的链表中。
Q: 索引节点的i_fopi_opi_mapping的a_ops字段何时被初始化
Ÿ 具体文件系统读取索引节点时初始化如ext2的ext2_read_inode方法
Ÿ 在ext2_read_inode中该方法根据get_ext2_inode从磁盘上读取ext2_inode并根据ext2_inode的信息初始化vfs的inode如modeuidgidtimestamp等并根据文件的类型不同将i_fopi_opi_mapping的a_ops初始化为相应的方法。
1. 对于普通的文件三者的值分别为ext2_file_operationsext2_file_inode_operationsext2_aops
2. 对于目录三者的值分别为ext2_dir_operationsext2_dir_inode_operationsext2_aops;
3. 对于链接文件i_op被赋值为ext2_symlink_inode_operations
4. 对于其他的类型的文件如块设备字符设备fifo管道socket则通过init_special_inode进行初始化。其相应的被初始化为def_blk_fopsdef_chr_fopsdef_fifo_fopsbad_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_syncbi_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该值可配置

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View 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.

View 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指向块设备的指针。

View 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每次运行时它将在系统的所有“脏”缓冲区中查找那些“冲刷”时间已经超过一定期限的这些过期缓冲区都要被写回磁盘。

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View 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层该层允许用户和应用程序访问记录。它提供了通用的记录recordI/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(sbino)__从磁盘读入相应索引节点并在内核中建立起相应的索引节点(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)**启动页IO操作具体将要完成这几方面工作
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 装配超级块。
11 初始化超级块对象某些域。
12 从设备中读取__磁盘第0块__到内存bread(dev,0,ROMBSIZE)其中dev是文件系统安装时指定的设备0指设备的首块也就是磁盘超级块ROMBSIZE是读取的大小。
13 检验磁盘超级块中的校验和
14 继续初始化超级块对象某些域
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 根据文件的访问权限(类别)设置索引节点的相应操作表
31 如果是__目录文件__则将索引节点表设为i->i_op = &romfs_dir_inode_operations;文件操作表设置为->i_fop = &romfs_dir_operations; 如果索引节点对应目录的话那么需要的操作仅仅会是__lookup操作__因为romfs是个功能很有限的文件系统对于文件操作表中的两个方法一个为read另一个为readdir。前者利用通用函数generic_read_dir返回用户错误消息。后者是针对readdir/getdents等系统调用实现的返回目录中文件的函数。
32 如果是__常规文件__则将文件操作表设置为i->i_fop = &generic_ro_fops;
将页高诉缓存表设置为**i->i_data.a_ops = &romfs_aops; **
由于romfs是只读文件系统它在对正规文件操作时不需要索引节点操作如mknodlink等因此不用管索引节点操作表。
对常规文件的操作也只需要使用__内核提供的通用函数表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]。
33 如果是链接文件则将索引节点操作表设置为i->i_op=&page_symlink_inode_operations;
将页高速缓存操作表设置为:
i->i_data.a_ops = &romfs_aops;
符号连接文件需要使用通用符号连接操作page_symlink_inode_operations实现同时也需要使用页高速缓存方法。
34 如果是套接字或管道则进行特殊文件初始化操作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操作最小传输的数据大小到逻辑块文件实际操作基本单元的映射关系。

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Some files were not shown because too many files have changed in this diff Show More