2010年5月30日星期日

TCP协议握手协商通信详解(转)

200710061019316711.gif

转:http://www.bitscn.com/network/tcpip/200711/117889.html
TCP协议握手协商通信详解


MSL是Maximum Segment Lifetime英文的缩写



  
  1、建立连接协议(三次握手) 中国网管联盟www、bitsCN、com
  (1)客户端发送一个带SYN标志的TCP报文到服务器。这是三次握手过程中的报文1.
  (2) 服务器端回应客户端的,这是三次握手中的第2个报文,这个报文同时带ACK标志和SYN标志。因此它表示对刚才客户端SYN报文的回应;同时又标志SYN给客户端,询问客户端是否准备好进行数据通讯。
  (3) 客户必须再次回应服务段一个ACK报文,这是报文段3.
  2、连接终止协议(四次握手)
  由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。 网管网bitsCN.com
  (1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送(报文段4)。
  (2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。 中国网管联盟www、bitsCN、com
  (3) 服务器关闭客户端的连接,发送一个FIN给客户端(报文段6)。
  (4) 客户段发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。
  CLOSED: 这个没什么好说的了,表示初始状态。
  LISTEN: 这个也是非常容易理解的一个状态,表示服务器端的某个SOCKET处于监听状态,可以接受连接了。 中国网管论坛bbs.bitsCN.com
  SYN_RCVD: 这个状态表示接受到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。
  SYN_SENT: 这个状态与SYN_RCVD遥想呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。
  ESTABLISHED:这个容易理解了,表示连接已经建立了。 中国网管联盟www_bitscn_com
  FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。 54ne.com
  FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。 网管联盟www.bitsCN.com
  TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。
  CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。
  CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。 54com.cn
  LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了。 中国网管联盟www、bitsCN、com
  最后有2个问题的回答,我自己分析后的结论(不一定保证100%正确)
  1、 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
  这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACK和SYN(ACK起应答作用,而SYN起同步作用)放在一个报文里来发送。但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送FIN报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。
  2、 为什么TIME_WAIT状态还需要等2MSL后才能返回到CLOSED状态?
  这是因为:虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文

2010年5月26日星期三

linux service

service命令,顾名思义,就是用于管理Linux操作系统中服务的命令。

1. 声明:这个命令不是在所有的linux发行版本中都有。主要是在redhat、fedora、mandriva和centos中。

2. 此命令位于/sbin目录下,用file命令查看此命令会发现它是一个脚本命令。

3. 分析脚本可知此命令的作用是去/etc/init.d目录下寻找相应的服务,进行开启和关闭等操作。

4. 开启httpd服务器:service httpd start

start可以换成restart表示重新启动,stop表示关闭,reload表示重新载入配置。

5. 关闭mysql服务器:service mysqld stop

6. 强烈建议大家将service命令替换为/etc/init.d/mysqld stop

unix erlang man 安装

在Unix系统,man手册页并不是默认安装的,如果命令 erl -man ... 没有工 作,你需要自己安装man手册页。所有的man手册页都是单独压缩存档的, 可以到 http://www.erlang.org/download.html 下载。man手册页可以通过 root解压并安装到Erlang的安装目录,通常为 。
/usr/local/lib/e

2010年5月25日星期二

解析emacs find-grep find-dired

find-grep
命令:find . -type f -exec grep -nH -e {} /dev/null \;
解释:
1. -exec COMMAND {} \;
-exec 参数后面跟的是 command命令,注意点如下:

command命令的终止,使用 ';' (分号)来判定,在后面必须有一个 ';'

'{}',使用{}来表示文件名,也就是find前面处理过程中过滤出来的文件,用于command命令进行处理

特别强调,对于不同的系统,直接使用分号可能会有不同的意义, 使用转义符 '\'在分号前明确说明

2. grep -nH -e
-n 行号
-H 文件名
-e pattern
替代:find . -type f | xargs -I % grep -nH -e % /dev/null

--------------------------------------------------
find-dired
命令:find . \( -name "a*" \) -exec ls -ld \{\} \;
解释:
1. ls -ld
参数
-ct 按照参考时间排序
-r 反序

解决mac emacs 字体 方框问题

用了两次进行解决
第一次:
方案:
(set-default-font "-apple-STKaiti-medium-normal-normal-*-*-*-*-*-p-0-iso10646-1")
(set-face-attribute 'default (selected-frame) :height 155)
原理:
用现有的字体, 替换目前的字体
效果:
勉强可以接受, 其实是用楷体, 但是楷体的默认英文字体 奇丑无比
补充方案:
(defun cha-b()
(interactive)
(set-default-font "-apple-Heiti_SC-bold-normal-normal-*-*-*-*-*-p-0-iso10646-1")
(set-face-attribute 'default (selected-frame) :height 130))

(defun cha-a()
(interactive)
(set-default-font "-apple-Monaco-medium-normal-normal-*-*-*-*-*-m-0-iso10646-1"))
补充方案效果:
通过命令进行切换, 暂时解决问题. 但跟吃了苍蝇一样.
第二次:
方案:
(create-fontset-from-fontset-spec
"-apple-bitstream vera sans mono-medium-r-normal--12-*-*-*-*-*-fontset-mymonaco,
ascii:-apple-Monaco-medium-normal-normal-*-12-*-*-*-m-0-iso10646-1,
chinese-gb2312:-apple-STHeiti-medium-normal-normal-12-*-*-*-*-p-0-iso10646-1,
latin-iso8859-1:-apple-Monaco-medium-normal-normal-*-12-*-*-*-m-0-iso10646-1,
mule-unicode-0100-24ff:-apple-Monaco-medium-normal-normal-*-12-*-*-*-m-0-iso10646-1")

(setq default-frame-alist (append '((font . "fontset-mymonaco")) default-frame-alist))
(set-default-font "fontset-mymonaco")
原理:
自己配置个完整的字体方案, 中文和英文分开设置. 愿意用啥字体就啥字体
效果:
终于爽了.

参考
http://www.emacswiki.org/emacs/SetFonts#toc6
http://strickland.spaces.live.com/Blog/cns!3B996A2BC0C32C32!512.entry

2010年5月24日星期一

ar,.tar.gz,.bz2,.tar.bz2,.bz,.gz是什么文件,如何解开他们?(转)

压缩文件
如何解开.tar,.tar.gz,.bz2,.tar.bz2,.gz文件
1.tar:把文件打包,不压缩:tar cvf *.tar dirName
把文件释放:tar xvf *.tar
2.tar.gz:把文件打包并压缩:tar czvf *.tar.gz dirName
把文件释放并解压:tar xzvf *.tar.gz
查询tar包中的内容:tar tf *.tar
查询压缩tar包中的内容:tar ztf *.tar


3.bz2:解开:bzip2 -d *.bz2
4.bz: 解开:bzip -d *.bz
5.gz: 解开:gzip -d *.gz
6.可以用unzip *.zip 解开zip文件,unrar *.r
7.tar命令祥解:
tar function option files
function是下列命令之一:
  c:建立新的备份文件
x:将备份文件解开
 t:列出备份文件的
option是下列命令的组合:
v:在处理文件时显示更多的信息
 k:在解开文件时保留已经存在的文件,也就是在备份文件中的文件不能覆盖以经存在的文件
 f:filename,指定filename为读出或写入的备份文件之文件名
z:可以用来自动产生或解开压缩后的备份文件ar,.tar.gz,.bz2,.tar.bz2,.bz,.gz是什么文件,如何解开他们?

webtool 远程访问启动方式

webtool:start("/usr/local/lib/erlang/lib/webtool-0.8.4/priv", [{port, 8889}, {bind_address, {0,0,0,0}}, {server_name, "testtest.com"}]).

2010年5月22日星期六

mac safari 跨墙

无法查看这则摘要。请 点击此处查看博文。

2010年5月21日星期五

修改 computer name

系统偏好设置->共享,修改“电脑名称”,就可以了。

另外,楼主说的在终端命令行提示符里的东东不是HostName,是ComputerName。用楼上同学说的scutil也可以修改,具体命令是:
sudo scutil --set ComputerName


修改 computer name 后要修改 hosts, 127.0.0.1

2010年5月20日星期四

man 手册页存储库部分及其内容 (转)

转:http://approach.javaeye.com/blog/468869
man 手册页存储库部分及其内容

man1 一般命令。这个部分中的命令通常不需要超级用户(即管理员)特权。ls、cat 和 passwd 放在这里,还有 shell。例如,请试试 man bash。
man2 用来访问 UNIX 内核提供的服务的系统调用或函数。例如 fork 系统,它从一个现有的进程生成一个新进程。输入 man fork 显示它的手册页。使用系统软件的程序员常常参考这个部分。
man3 C 库函数。许多软件包提供功能丰富的代码库,让开发人员可以创建新软件来补充现有的特性或开发全新的特性。每个库通常有一个手册页;一些库(比如系统的 libc)太大了,所以各个函数或一组相关函数有单独的文档。
man4 特殊文件,比如设备和驱动程序。
man5 文件格式。UNIX 几乎完全使用文本配置文件定制系统的操作。有大量配置文件,包括网络服务的列表 (/etc/services) 和可用的 shell 列表 (/etc/shells) 等等。
man6 游戏和屏幕保护程序。
man7 杂类文件。这是一个包罗万象的类别。在传统的系统上,可以了解 glob 操作符、正则表达式等方面的信息。
man8 系统管理命令,超级用户很可能要使用它们。

CMD manpath查看man的搜索路径,可以进行定制。

参考:http://www.ibm.com/developerworks/cn/aix/library/au-spunix_manpages/

path finder 替代 finder

菜单Path Finder > Preference > Reveal
勾中
Set Path Finder as the default file viewer

Applications:
勾中 Mount DMGs using Path Finder

General:
勾中 Launch Path Finder automatically after log in
Quit the Finder's desktop

不过,Path Finder要比Finder慢一些,建议你还是保留Finder

mochiweb 不一定每个请求是一个新的process(转)

转:http://approach.javaeye.com/blog/547638

今天试了一下mochiweb,这个东东的文档实在是少的可怜,推荐一个不错的tutorial:

http://erlang-china.org/start/mochiweb_intro.html

做了一个简单的comet测试,在firefox下浏览时发现每个http请求处理的process id固定在2个,而且进程字典的数据也可以跨请求共享,找了下mochiweb的源码,发现其在http请求结束时,支持tcp连接复用(http1.1),代码如下:

mochiweb_http.erl

Erlang代码
  1. after_response(Body, Req) ->
  2. Socket = Req:get(socket),
  3. case Req:should_close() of
  4. true ->
  5. gen_tcp:close(Socket),
  6. exit(normal);
  7. false ->
  8. Req:cleanup(),
  9. ?MODULE:loop(Socket, Body)
  10. end.

mochiweb_request.erl

Erlang代码
  1. %% @spec should_close() -> bool()
  2. %% @doc Return true if the connection must be closed. If false, using
  3. %% Keep-Alive should be safe.
  4. should_close() ->
  5. ForceClose = erlang:get(mochiweb_request_force_close) =/= undefined,
  6. DidNotRecv = erlang:get(mochiweb_request_recv) =:= undefined,
  7. ForceClose orelse Version < {1, 0}
  8. %% Connection: close
  9. orelse get_header_value("connection") =:= "close"
  10. %% HTTP 1.0 requires Connection: Keep-Alive
  11. orelse (Version =:= {1, 0}
  12. andalso get_header_value("connection") =/= "Keep-Alive")
  13. %% unread data left on the socket, can't safely continue
  14. orelse (DidNotRecv
  15. andalso get_header_value("content-length") =/= undefined
  16. andalso list_to_integer(get_header_value("content-length")) > 0)
  17. orelse (DidNotRecv
  18. andalso get_header_value("transfer-encoding") =:= "chunked").

So, it's clear now.

2010年5月18日星期二

六步之内设置 Synergy(转)

转:http://scxcz.com/article.asp?id=291
Synergy设置.超好用的软件.控制多台电脑 作者:kite 日期:2009-07-18
字体大小: 小 中 大



六步之内设置 Synergy
What is Synergy? It's a program that leats you share a virtual desktop between two different computers. With one mouse and keyboard, you can control two computers, and even move your mouse from one desktop to the other. It's almost like the computers are welded together. I'll demonstrate. This is what my desk at home looks like when my desk is clean:

Matt's desk at home in July 2007

On the left-hand monitor I have a Windows XP system running. On the right-hand monitor I run Ubuntu 7.04 (Feisty Fawn). If I move my mouse off my Windows (left) monitor to the right, it shows up on the Ubuntu (right) monitor and vice-versa. Plus cut-and-paste works between the machines as well.

Here's how to configure Synergy with two computers. Let's assume that you have two home machines called "windowspc" and "ubuntu". We'll put the Windows machine on the left and the Ubuntu machine on the right. One of the two machines will run as the Synergy server and the other will run as a Synergy client. I picked the Ubuntu machine to be the server.

1. Install Synergy on both Ubuntu and Windows
Ubuntu: Run the command "sudo apt-get install synergy"
Windows: Download the .exe program from SourceForge.
2. As root on the Ubuntu machine, create an /etc/synergy.conf file:

section: screens
ubuntu:
windowspc:
end

section: aliases
windowspc:
192.168.1.104
end

section: links
ubuntu:
left = windowspc
windowspc:
right = ubuntu
end

section: options
screenSaverSync = false
# My KVM uses Scroll Lock to switch screens, so set the
# hotkey to lock the cursor to the screen to something else
keystroke(f12) = lockCursorToScreen(toggle)
end

There are several things to note in this configuration file:
- If one of your machines doesn't have a DNS name, you can use the IP address of that machine. The "aliases" section lets you do that in a clean way. To find your IP address on Windows XP, do Start->Run, enter cmd, and type "ipconfig /all". On Linux/Ubuntu, use "ifconfig" to find your machines's IP address.

- In the "links" section, you have to define both behaviors: going offscreen-left on the Ubuntu (right) machine, and going offscreen-right on the Windows (left) machine. In theory you can create really weird mappings, but keeping it simple is usually best.

- The "screenSaverSync = false" command says not to link the screensavers of the two machines.

- Synergy normally uses the "Scroll Lock" key as a toggle that prevents your mouse from leaving the screen. I have a KVM switch that uses the Scroll Lock key, so I redefined the "lock Cursor to Screen" key to a harmless button (f12).
3. Make sure that the Ubuntu configuration file is world-readable. Run the command "sudo chmod a+r /etc/synergy.conf" to do that.
4. Next, test the server and client and make sure that everything works. On the Ubuntu server, run "synergys -f -config /etc/synergy.conf" (that's one dash in front of the 'f' and two dashes in front of the 'config'). The "-f" option means "run Synergy in the foreground" and it lets you see debugging and other Synergy messages. Note that the command is "synergys" because the 's' stands for server. There's also a synergyc to run the client.

On the Windows client, run Synergy. You'll need to enter the name or IP address of the Ubuntu Synergy server and then click Test. The Windows program will look like this:

Windows Synergy client

If you're using a KVM switch, don't forget to shift your mouse/keyboard back to the Synergy server running on Ubuntu. The mouse/keyboard will be routed through Synergy to your Windows PC, so your KVM switch has to be set to the Synergy server.
5. If Synergy works fine in test mode, it's time to run it for real. On the Ubuntu server, run the command "synergys -config /etc/synergy.conf" with two dashes in front of 'config'. That's the same command that you ran before, except remove the "-f" option to run in the foreground. On the Windows computer, just click "Start" on the Synergy window.
6. Finally, make Synergy run on both machines on start-up. On Windows, there's an "Autostart" button. Click the button and choose to start Synergy either when you log in or when the computer boots up. If you have sufficient Administrator access rights, I'd set Synergy to run when the computer starts:

Windows menu to autostart Synergy

On the Ubuntu server, click System->Preferences->Sessions and then click New and make a startup program (I called it "Synergy Server") that runs the command "/usr/bin/synergys -config /etc/synergy.conf". Again, that's two dashes in front of the word "config."

I hope this guide helps. Once you get Synergy going, it's incredibly cool to copy some text on the Ubuntu machine, mouse over to the Windows machine, and then paste that text on Windows.

If you want to dig into Synergy more, here are some helpful links:
Setting up and running Synergy
Setting Synergy to run automatically
Details on the configuration file format for Synergy
Frequently Asked Questions (FAQ) about Synergy
Troubleshooting Synergy

One final tip: If you're using a KVM switch, remember which computer your keyboard/mouse is driving. That bit me a couple times.

使用inotify监视文件系统(转)

转:http://hi.baidu.com/hq1305018/blog/item/55c7bbde8c78d9abcc11661b.html
使用inotify监视文件系统
2009-10-19 12:07
使用inotify监视文件系统

inotify是Linux的功能,监视文件系统的读取,写入,文件建立等动作。inotify是事件驱动型的,而且使用方法又非常简单,比起用cron来频繁地查找文件系统的变化相比,非常有效。

关于inotify

inotify是Linux系统的核心功能,监视文件系统,当有事件(删除,读取,写入或者unmount等等)发生时,向指定的程序发出通知。另外,文件的移动等的操作发生时,移动文件和移动位置也可以通过通知来获取。

inotify的使用文件非常简单。首先生成一个文件标识,一个文件标识中记载了一个以上的监视对象(文件系统的路径和所要监视的事件类别)。然后,使用read()函数和读取该文件标识的事件通知。inotify并不会不断的去检索文件系统,如果一个事件没有发生,那么read()会一直等待。

另外因为inotify使用的是以前的文件标识方式,所以可以和select()等系统函数结合起来,对文件监视对象和多种入力源同时进行事件驱动型监视。事件驱动型的实行方式,避免了频繁地检索文件系统,大量地节省了系统资源。

接下来我会通过一些例子程序来详细说明inotify的使用方法。inotify需要Linux2.6.13以上,可以使用下面的命令来检查系统是否可以使用inotify。另外一种确认方法是,查找/usr/include/sys/inotify.h是否存在。

%uname -a

Linux ubuntu-desktop 2.6.24-19-generic #1 SMP ... i686 GNU/Linux
注意:FreeBSD,或者Mac OS X系统并不支持inotify,但是提供了和inotify相似的kqueue。关于kqueue可以使用man 2 kqueue来获取相关信息。

inotify的C语言API

inotify提供了3个系统调用函数,使用它们可以进行任意的文件系统监视。

● inotify_init() 用来生成inotify的实例,成功时返回一个文件标识,失败时返回-1。和其它的系统调用一样,失败的时候可以使用errno来进行分析。

● inotify_add_watch() 看函数名就知道了,这个是用来追加监视对象的系统调用。追加监视对象的时候需要监视文件路径,监视的事件列表(监视事件的指定要使用象IN_MODIFY等的常量)。多个监视对象之间是逻辑OR的关系(C语言中是(|))。inotify_add_watch()调用成功时,会返回一个监视标识,失败的时候会返回-1。当需要对监视对象进行变更或者删除的时候要使用监视标识。

● inotify_rm_watch() 是用来删除监视对象的系统调用函数。

除了上述的三个系统调用 之外,还要使用系统调用 函数read()和close()。read()是用来读取通知,如果通知没有发生那么read()不会返回。对文件标识执行了close()之后,和那个文件标识相关的内存资源都被删除和释放。

例子程序:事件监视

#include
#include
#include
#include
#include

#define EVENT_SIZE ( sizeof (struct inotify_event) )
#define BUF_LEN ( 1024 * ( EVENT_SIZE + 16 ) )

int main( int argc, char **argv )
{
int length, i = 0;
int fd;
int wd;
char buffer[BUF_LEN];

fd = inotify_init();

if ( fd <>
perror( "inotify_init" );
}

wd = inotify_add_watch( fd, "/home/kankyou",
IN_MODIFY | IN_CREATE | IN_DELETE );
length = read( fd, buffer, BUF_LEN );

if ( length <>
perror( "read" );
}

while ( i <>
struct inotify_event *event = ( struct inotify_event * ) &buffer[ i ];
if ( event->len ) {
if ( event->mask & IN_CREATE ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was created.\n", event->name );
}
else {
printf( "The file %s was created.\n", event->name );
}
}
else if ( event->mask & IN_DELETE ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was deleted.\n", event->name );
}
else {
printf( "The file %s was deleted.\n", event->name );
}
}
else if ( event->mask & IN_MODIFY ) {
if ( event->mask & IN_ISDIR ) {
printf( "The directory %s was modified.\n", event->name );
}
else {
printf( "The file %s was modified.\n", event->name );
}
}
}
i += EVENT_SIZE + event->len;
}

( void ) inotify_rm_watch( fd, wd );
( void ) close( fd );

exit( 0 );
}
这个程序中,用 fd = inotify_init();来生成了一个文件标识。
用wd = inotify_add_watch(...)追加了一个监视标识。对 /home/kankyou的文件系统变化来进行监视。
read()函数在一个通知到来之前会一直等待。通知的详细信息(文件和事件)是通知一个字节流来送信的。利用应用程序中的循环来获取字节流中的信息。
事件通知的构造体被定义在/usr/include/sys/inotify.h文件中。(参照下记)
struct inotify_event
{
int wd; /* The watch descriptor */
uint32_t mask; /* Watch mask */
uint32_t cookie; /* A cookie to tie two events together */
uint32_t len; /* The length of the filename found in the name field */
char name __flexarr; /* The name of the file, padding to the end with NULs */
}


wd是监视标识,表明一个监视对象。mask是事件通知的类别(文件删除,文件更新等等)。

通过使用cookie来关联两个监视事件对象,比如当文件从一个目录被移动到另一个目录的时候,发生的两个事件的cookie是相同的。对文件移动的监视要使用IN_MOVED_FROM和IN_MOVED_TO,或者使用IN_MOVED来对双方进行监视。

最后,name和len是发生事件的文件的文件名(不包括路径)和文件名的长度。


Build例子程序


调用C编译器(大部分的Linux系统是gcc)。之后是实行编译后的程序。

%cc -o watcher watcher.c
%./watcher
watcher实行中的状态下,在/home/kankyou目录中执行touch,cat,rm等命令,来进行实验。

%cd $HOME
%touch a b c
The file a was created.
The file b was created.
The file c was created.

%./watcher &
%rm a b c
The file a was deleted.
The file b was deleted.
The file c was deleted.

%./watcher &
%touch a b c
The file a was created.
The file b was created.
The file c was created.

%./watcher &
%cat /etc/passwd >> a
The file a was modified.

%./watcher &
%mkdir d
The directory d was created.


inotify的使用技巧

因为read()在通知到来之前一直是处于等待状态,所以对于象画面应该程序是不能使用的。这种场合可以结合select(),pselect(),poll(),epoll()来进行监视。



int return_value;
fd_set descriptors;
struct timeval time_to_wait;

FD_ZERO ( &descriptors );
FD_SET( ..., &descriptors );
FD_SET ( fd, &descriptors );

...

time_to_wait.tv_sec = 3;
time.to_waittv_usec = 0;

return_value = select ( fd + 1, &descriptors, NULL, NULL, &time_to_wait);

if ( return_value <>
/* Error */
}

else if ( ! return_value ) {
/* Timeout */
}

else if ( FD_ISSET ( fd, &descriptors ) ) {
/* Process the inotify events */
...
}

else if ...


select()函数被调用之后,程序会等待time_to_wait秒,但是如果在这期间,fd中的任何文件发生变化,那么监视会立刻被再次实行。time_to_wait秒之后,应用程序可以进行其它的处理(象GUI事件处理等等)。


最后介绍几个inotify使用时的技巧。

● 监视对象的文件或者目录被删除之后,该当监视对象自动被删除。

● 如果正在监视的文件或者目录,发生了unmount,那么该当监视对象接收到unmount事件通知,之后受unmount影响的所有的监视对象都被删除。

● 监视事件类别中如果指定了IN_ONESHOT,那么事件通知只被送信一次。

● 事件通知的队列到达最大值时,会发生IN_OVERFLOW。

● close()函数执行之后,inotify的实例和相关的所有监视对象会被删除,队列中等待处理的事件会被删除。

launched launchctll 学习心得

因工作需要 macbook 需要配置nginx开机自动启动
步骤如下:
1. 编写对应的 属性文件






  Label
  com.example.processor
  OnDemand
  
  Program
  /Users/strike/bin/processor
  ProgramArguments
  
    processor
  

  WatchPaths
  
    /Users/strike/data/incoming
  





2. launchctl load < < 文件名 > >
3. 修改 nginx 配置文件 保证单独可启
4. launchctl start
< < 文件名 > >
5. top / htop 检查是否起来
6. web 测试

转:https://www6.software.ibm.com/developerworks/cn/education/aix/au-usingcron/index.html(需要注册账户)

ps: 经常忽略 ibm, 但ibm 资料库里的 好东西真是多多, 而且质量很高

2010年5月17日星期一

cat ,more, Less区别(转)

转:http://blog.chinaunix.net/u/4637/showart_328633.html


cat ,more, Less区别
使用cat more less都可以查看文本内容,但是它们三者有什么区别呢?
more和less的功能完全重复吗?
以下是我个人的总结,欢迎大家一起来分享

cat 连续显示、查看文件内容
more 分页查看文件内容
less 分页可控制查看文件内容

通俗点说:
cat一次性把文件内容全部显示出来,管你看不看得清,显示完了cat命令就返回了,不能进行交互式操作,适合察看内容短小、不超过一屏的文件;
more比cat强大一点,支持分页显示,你可以ctrl+B ctrl+F .....上下滚屏,但是不支持像shift+G(跳到文件尾)这种操作;
less比more更强大一点,支持各种命令,随便翻页、跳转、查找.....想怎么看,就怎么看,爱怎么看,就怎么看。

附less的帮助内容
[code]

SUMMARY OF LESS COMMANDS

Commands marked with * may be preceded by a number, N.
Notes in parentheses indicate the behavior if N is given.

h H Display this help.
q :q Q :Q ZZ Exit.
---------------------------------------------------------------------------

MOVING

e ^E j ^N CR * Forward one line (or N lines).
y ^Y k ^K ^P * Backward one line (or N lines).
f ^F ^V SPACE * Forward one window (or N lines).
b ^B ESC-v * Backward one window (or N lines).
z * Forward one window (and set window to N).
w * Backward one window (and set window to N).
ESC-SPACE * Forward one window, but don't stop at end-of-file.
d ^D * Forward one half-window (and set half-window to N).
u ^U * Backward one half-window (and set half-window to N).
ESC-) RightArrow * Left one half screen width (or N positions).
ESC-( LeftArrow * Right one half screen width (or N positions).
F Forward forever; like "tail -f".
r ^R ^L Repaint screen.
R Repaint screen, discarding buffered input.
---------------------------------------------------
Default "window" is the screen height.
Default "half-window" is half of the screen height.
---------------------------------------------------------------------------

SEARCHING

/pattern * Search forward for (N-th) matching line.
?pattern * Search backward for (N-th) matching line.
n * Repeat previous search (for N-th occurrence).
N * Repeat previous search in reverse direction.
ESC-n * Repeat previous search, spanning files.
ESC-N * Repeat previous search, reverse dir. & spanning files.
ESC-u Undo (toggle) search highlighting.
---------------------------------------------------
Search patterns may be modified by one or more of:
^N or ! Search for NON-matching lines.
^E or * Search multiple files (pass thru END OF FILE).
^F or @ Start search at FIRST file (for /) or last file (for ?).
^K Highlight matches, but don't move (KEEP position).
^R Don't use REGULAR EXPRESSIONS.
---------------------------------------------------------------------------

JUMPING

g < style="font: normal normal normal 12px/normal song, Verdana; "> G > ESC-> * Go to last line in file (or line N).
p % * Go to beginning of file (or N percent into file).
t * Go to the (N-th) next tag.
T * Go to the (N-th) previous tag.
{ ( [ * Find close bracket } ) ].
} ) ] * Find open bracket { ( [.
ESC-^F * Find close bracket .
ESC-^B * Find open bracket
---------------------------------------------------
Each "find close bracket" command goes forward to the close bracket
matching the (N-th) open bracket in the top line.
Each "find open bracket" command goes backward to the open bracket
matching the (N-th) close bracket in the bottom line.

m Mark the current position with .
' Go to a previously marked position.
'' Go to the previous position.
^X^X Same as '.
---------------------------------------------------
A mark is any upper-case or lower-case letter.
%2



2010年5月13日星期四

Solaris8下安装SSH总汇(转)

1、需要下载的软件
openssh
openssl (SSL)
prngd (Psuedo Random Generator Daemon)
zlib (Z library)
以上软件可以到http://www.sunfreeware.com下载或者到ftp://ftp.sjtu.edu.cn/sites/ftp.sunfreeware.com/中去下载

2、安装
#gunzip openssl*
#gunzip prngd*
#gunzip zlib*
#gunzip openssh*
#pkgadd -d openssl-0.9.6c-sol8-sparc-local
#pkgadd -d prngd-0.9.23-sol8-sparc-local
#pkgadd -d zlib-1.1.4-sol8-sparc-local
#pkgadd -d openssh-3.1p1-sol8-sparc-local

3、创建自启动控制文件
1)sshd启动脚本:
vi /etc/init.d/sshd


#! /bin/sh
#
# start/stop the secure shell daemon

case "$1" in

'start')
# Start the sshd daemon
if [ -f /usr/local/sbin/sshd ]; then
echo "starting SSHD daemon"
/usr/local/sbin/sshd &
fi
;;

'stop')
# Stop the ssh deamon
PID=`/usr/bin/ps -e -u 0 | /usr/bin/fgrep sshd | /usr/bin/awk '{print $1}'`
if [ ! -z "$PID" ] ; then
/usr/bin/kill ${PID} >;/dev/null 2>;&1
fi
;;

*)
echo "usage: /etc/init.d/sshd {start|stop}"
;;

esac

2)设置sshd启动脚本

#chmod +x /etc/init.d/sshd
#ln -s /etc/init.d/sshd /etc/rc2.d/S99sshd

3)prngd启动脚本
#vi /etc/init.d/prngd


#! /bin/sh
#
# start/stop the pseudo random generator daemon

case "$1" in

'start')
# Start the ssh daemon
if [ -f /usr/local/sbin/prngd ]; then
echo "starting PRNG daemon"
/usr/local/sbin/prngd /var/spool/prngd/pool&
fi
;;

'stop')
# Stop the ssh deamon
PID=`/usr/bin/ps -e -u 0 | /usr/bin/fgrep prngd | /usr/bin/awk '{print $1}'`
if [ ! -z "$PID" ] ; then
/usr/bin/kill ${PID} >;/dev/null 2>;&1
fi
;;

*)
echo "usage: /etc/init.d/prngd {start|stop}"
;;

esac

4)设置prngd启动脚本

#chmod +x /etc/init.d/prngd
#ln -s /etc/init.d/prngd /etc/rc2.d/S99prngd

4、启动prngd
# /etc/init.d/prngd start
starting PRNG daemon
Info: Random pool not (yet) seeded
Could not bind socket to /var/spool/prngd/pool: No such file or directory
# mkdir -p /var/spool/prngd
#/etc/init.d/prngd start
starting PRNG daemon
# Info: Random pool not (yet) seeded
#

5、启动sshd
# /etc/init.d/sshd start
starting SSHD daemon
Could not load host key: /usr/local/etc/ssh_host_key
Could not load host key: /usr/local/etc/ssh_host_rsa_key
Could not load host key: /usr/local/etc/ssh_host_dsa_key
Disabling protocol version 1. Could not load host key
Disabling protocol version 2. Could not load host key
sshd: no hostkeys available -- exiting.
#
The errors above are due to the fact that we didn't create any key pairs for our ssh server.

Create a public key pair to support the new, DSA-based version 2 protocol

# /usr/local/bin/ssh-keygen -d -f /usr/local/etc/ssh_host_dsa_key -N ""

Generating public/private dsa key pair.
Your identification has been saved in /usr/local/etc/ssh_host_dsa_key.
Your public key has been saved in /usr/local/etc/ssh_host_dsa_key.pub.
The key fingerprint is:
00:91:f5:8a:55:7c:ac:ff:b7:08:1f:ce:23:aa:f2:79 root@solaris8


Create a public key pair to support the old, RSA-based version 1 protocol

# /usr/local/bin/ssh-keygen -b 1024 -f /usr/local/etc/ssh_host_rsa_key -t rsa -N ""
Generating public/private rsa1 key pair.
Your identification has been saved in /usr/local/etc/ssh_host_rsa_key.
Your public key has been saved in /usr/local/etc/ssh_host_rsa_key.pub.
The key fingerprint is:
8e:b0:1d:8a:22:f2:d2:37:1f:92:96:02:e8:74:ca:ea root@solaris8

编辑配置文件/usr/local/etc/sshd_config,启用 protocol 2 and 1
#vi /usr/local/etc/sshd_config
找到#Port 22 替换为Port 22
找到#Protocol 2,1替换为Protocol 2,1

# /etc/init.d//sshd start
starting SSHD daemon
#

至此基本完成工作。

在启动过程可能遇到的问题及解决办法
A.PRNG is not seeded的问题的解决办法
下载http://www.cosy.sbg.ac.at/~andi/SUNrand/pkg/ANDIrand-0.7-5.8-sparc-1.pkg该软件
安装
pkgadd -d ANDIrand*
安装了这个软件之后,会在/dev/目录下生成2个随机数设备
random urandom
安装之后不用重起
这时你在起 openssh 就会正常了

B.启动sshd时遇到下面的问题Could not load host key: /usr/local/etc/ssh_host_key
Disabling protocol version 1. Could not load host key
Missing privilege separation directory: /var/empty
和Privilege separation user sshd does not exist

解决办法是
mkdir /var/empty
chown root:sys /var/empty
chmod 755 /var/empty
groupadd sshd
useradd -g sshd -c 'sshd privsep' -d /var/empty -s /bin/false sshd
chown root /etc/init.d/sshd
chgrp sys /etc/init.d/sshd
chmod 555 /etc/init.d/sshd

C.Could not load host key: /usr/local/etc/ssh_host_key
Disabling protocol version 1. Could not load host key错误提示
临时解决办法是
修改/usr/local/etc/sshd_config
将Protocol 2,1改为Protocol 2

2010年5月12日星期三

intel e1000 网卡 napi分析(转)

转:http://sh-neo.spaces.live.com/blog/cns!1E3CA285E5F9E122!526.entry


内核如何从网卡接收数据,传统的过程:
1.数据到达网卡;
2.网卡产生一个中断给内核;
3.内核使用I/O指令,从网卡I/O区域中去读取数据;

我们在许多网卡驱动中(很老那些),都可以在网卡的中断函数中见到这一过程。

但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,就有一个问题,“可不可以不使用中断”,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,“你有没有数据啊?”……

从这个描述本身可以看到,如果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧

OK,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPU从I/O区域读,然后把它放到内存(这个内存指的是系统本身的物理内存,跟外设的内存不相干,也叫主内存)中”。于是自然地,就想到了DMA技术——让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:
1.首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。
2.内核将这个缓冲区通过DMA映射,把这个队列交给网卡;
3.网卡收到数据,就直接放进这个环形缓冲区了——也就是直接放进主内存了;然后,向系统产生一个中断;
4.内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;

——呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?

对应以上4步,来看它的具体实现:
1)分配环形DMA缓冲区
Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后用e1000_rx_ring 环形缓冲区队列描述符连接起来
2)建立DMA映射
内核通过调用
dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)
建立映射关系。
struct device *dev 描述一个设备;
buffer:把哪个地址映射给设备;也就是某一个skb——要映射全部,当然是做一个双向链表的循环即可;
size:缓存大小;
direction:映射方向——谁传给谁:一般来说,是“双向”映射,数据在设备和内存之间双向流动;
对于PCI设备而言(网卡一般是PCI的),通过另一个包裹函数pci_map_single,这样,就把buffer交给设备了!设备可以直接从里边读/取数据。
3)这一步由硬件完成;
4)取消映射
dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里——因为我们已经接收到数据了,应该由CPU把数据交给上层网络栈;当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用dma_sync_single_for_cpu()让CPU在取消映射前,就可以访问DMA缓冲区中的内容。


原代码分析
基于linux v2.6.26

//e1000_probe 网卡初始化 (重点关注两部分 1注册poll函数 2设置接收缓冲的大小)
static int __devinit e1000_probe(struct pci_dev *pdev,const struct pci_device_id *ent){
struct net_device *netdev;
struct e1000_adapter *adapter;
....
err=pci_enable_device(pdev);
...
err=pci_set_dma_mask(pdev,DMA_64BIT_MASK); //设置pci设备的dma掩码
...
netdev = alloc_etherdev(sizeof(struct e1000_adapter)); //为e1000网卡对应的net_device结构分配内存
...
pci_set_drvdata(pdev,netdev);
adapter=netdev_priv(netdev);
adapter->netdev=netdev;
adapter->pdev=pdev;
...
mmio_start = pci_resource_start(pdev,0);
mmio_len = pci_resource_len(pdev,0);
....
/*将e1000网卡驱动的相应函数注册到net_device中*/
netdev->open = &e1000_open;
netdev->stop = &e1000_close;
...
netif_napi_add(netdev,&adapter->napi,e1000_clean,64); // 注册poll函数为e1000_clean, weight为64
...
netdev->mem_start = mmio_start;
netdev->mem_end = mmio_start+mmio_len;
....
if(e1000e_read_mac_addr(&adapter->hw)) ndev_err(...); //从网卡设备的EEPROM中读取mac地址
memcpy(netdev->dev_addr, adapter->hw.mac.addr, netdev->addr_len);
memcpy(netdev->perm_addr, adapter->hw.mac.addr, netdev->addr_len);
....
adapter->rx_ring->count = 256; //设置接收环型缓冲区队列的缺省大小
...
e1000_reset(adapter);
...
strcpy(netdev->name,"eth%d");
err= register_netdev(netdev); //将当前网络设备注册到系统的dev_base[]设备数组当中
....
return 0;
}
e1000_open 各种数据结构初始化 (环形缓冲区队列的初始化)
static int e1000_open(struct net_device *netdev){
struct e1000_adapter *adapter = netdev_priv(netdev);
....
err = e1000_setup_all_rx_resoures(adapter) //预先分配缓冲区资源
....
err = e1000_request_irq(adapter); //分配irq中断
....
}
int e1000_setup_all_rx_resources(struct e1000_adapter *adapter){
int i,err=0;
for(i=0 ; inum_rx_queues ; i++){
err = e1000_setup_rx_resources(adapter,&adapter->rx_ring[i]);
if(err){
...
}
}
return err;
}
e1000_rx_ring 环形缓冲区队列(接收缓冲队列由多个描述符组成,每个描述符中都包含一个缓冲区buffer,该buffer以dma方式存放数据包,整个缓冲队列以环形排列 每个描述符都有一个状态变量以表示该缓冲区buffer是否可以被新到的数据包覆盖)
struct e1000_rx_ring{
void *desc; //指向该环形缓冲区
dma_addr_t dma; //dma物理地址
unsigned int size;
unsigned int count; //环形队列由多少个描述符组成,这个在probe中定义了
unsigned int next_to_use; //下一个可使用的描述符号
unsigned int next_to_clean; //该描述符状态(是否正在使用,是否脏)
struct e1000_buffer *buffer_info; //缓冲区buffer
...
}
struct e1000_buffer{
struct sk_buff *skb;
....
}
static int e1000_setup_rx_resources(struct e1000_adapt *adapter, struct e1000_rx_ring *rxdr){
struct pci_dev *pdev = adapter->pdev;
int size,desc_len;
size = sizeof(struct e1000_buffer) * rxdr->count;
rxdr->buffer_info = vmalloc(size);
memset(rxdr->buffer_info,0,size); //分配buffer所使用的内存
....
if(adapter->hw.mac_type <= e1000_82547_rec_2)
desc_len = sizeof(struct e1000_rx_desc);
else ....

rxdr->size = rxdr->count * desc_len;
rxdr->size = ALIGN(rxdr->size,4096);
rxdr->desc = pci_alloc_consistent(pdev,rxdr->size,&rxdr->dma);
...
memset(rxdr->desc,0,rxdr->size); //分配缓冲队列所使用的内存
rxdr->next_to_clean =0;
rxdr->next_to_use =0;
return 0;
}
e1000_up 启动网卡函数 调用alloc_rx_buf来建立环形缓冲队列
int e1000_up(struct e1000_adapter *adapter){
e1000_configure(adatper);
....
}
static void e1000_configure(struct e1000_adapter *adapter){
struct net_device *netdev = adapter->netdev;
int i;
...
e1000_configure_rx(adapter);
...
for (i=0;inum_rx_queues;i++){
struct e1000_rx_ring *ring = &adapter ->rx_ring[i];
adapter->alloc_rx_buf(adapter,ring,E1000_DESC_UNUSED(ring)); //从这里就可以看出 环形缓冲区并不是一开始就完全建好的,建了部分
}
...
}
static void e1000_configure_rx(struct e1000_adapter *adapter){
....
adapter->clean_rx = e1000_clean_rx_irq; //后面会提到的poll()
adapter->alloc_rx_buf = e1000_alloc_rx_irq //建立环形缓冲队列函数 这里实际调用的是e1000_alloc_rx_buffers
}
e1000_alloc_rx_buffers ----因为其中有些参数要看完下面的才能理解,所以这个函数最后再写

e1000_intr e1000的中断处理函数
static irqreturn_t e1000_intr(int irq,void *data){
struct net_device *netdev = data;
struct e1000_adapter *adapter = netdev_priv(netdev);
..
u32 icr = E1000_READ_REG(hw,ICR);
#ifdef CONFIG_E1000_NAPI
int i;
#endif
...
#ifdef CONFIG_E1000_NAPI //进入轮询模式
if(unlikely(hw->mac_type E1000_WRITE_REG(hw,IMC,~0); //关闭中断
E1000_WRITE_FLUSH(hw);
}
if (likely(netif_rx_schedule_prep(netdev,&adapter->napi))){ //确定该设备处于运行状态, 而且还未被添加到网络层的poll队列中
...
__netif_rx_schedule(netdev,&adapter->napi); //将当前设备netdevice加到与cpu相关的softnet_data的轮旬设备列表poll_list中并触发NET_RX_SOFTIRQ软中断
}
#else //进入中断模式
{ ...
for(i=0;i if (unlikely(!adapter->clean_rx(adapter, adapter->rx_ring) &.... //执行clean_rx()中关于中断模式的代码 不走napi路径
break;
...
}
}
....
return IRQ_HANDLED;
}
static inline int netif_rx_schedule_prep(struct net_device *dev,struct napi_struct *napi){
return napi_schedule_prep(napi);
}
static inline int napi_schedule_prep(struct napi_struct *n){
return !napi_disable_pending(n) &&
!test_and_set_bit(NAPI_STATE_SCHED, &n->state); //测试该设备是否已被被加到poll队列
}
static inline int napi_disable_pending (struct napi_struct *n){
return test_bit(NAPI_STATE_DISABLE,&n->state); //测试该设备是否停止运行
}
static inline void __netif_rx_schedule(struct net_device *dev,struct napi_struct *napi){
__napi_schedule(napi);
}
void __napi_schedule(struct napi_struct *n){
unsigned long flags;
local_irq_save(flags);
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ); 触发软中断
local_irq_restore(flags);
}
#define __raise_softirq_irqoff(nr) do {or_softirq_pending(iUL<<(nr)); } while(0)
用到的数据结构napi_struct
struct napi_struct{
struct list_head poll_list; //poll_list链表
unsigned long state //设备状态信息 往上看看
int weight; //设备预处理数据配额,作用是轮询时更公平的对待各个设备
int (*poll) (strcut napi_struct *,int);
.....
}
接下来就是软中断处理函数net_rx_action()
static void net_rx_action(struct softirq_action *h){
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
unsigned long start_time = jiffies;
int budget = netdev_budget; //处理限额,一次最多只能处理这么多数据包
....
local_irq_disable();
while (!list_empty(list)){
struct napi_struct *n;
int work,
weight;
if (unlikely(budget < 0 || jiffies != start_time)) //如果实际工作量work 超过限额,或处理时间超过1秒,则出于系统响应考虑立即从软中断处理函数中跳出来, work是poll的返回值 限额budget每次都会根据返回的work值重新计算 ,配额weight和work配合来实现轮询算法,具体算法要看完e1000_clean(),e1000_rx_irq()才能清楚
goto softnet_break;
local_irq_enalbe();
n = list_entry(list->next,struct napi_struct,poll_list);
weight = n->weight;
work 0;
if (test_bit(NAPI_STATE_SCHED,&n->state))
work = n->poll(n,weight); //调用设备的poll函数e1000_clean()
....
budget -= work; //更新限额
local_irq_disable();
if (unlikely(work == weight)){ //处理量大于配额
if(unlikely(napi_disable_pending(n)))
__napi_complete(n);
else
list_move_tail(&n->poll_list,list); //该设备还有要接收的数据没被处理,因为轮询算法 被移动到poll_llst尾部等待处理
}
...
}
out:
local_irq_enable();
...
return;
softnet_break:
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
e1000网卡poll函数 e1000_clean()
static int e1000_clean(struct napi_struct *napi,int budget){ //此处的budget实际上是传过来的weight,不要和上面的budget弄混了
struct e1000_adapter *adapter = container_of(napi,struct e1000_adapter,napi);
struct net_device *poll_dev = adapter->netdev;
int work_done = 0;
adapter = poll_dev->priv;
.....
adapter ->clean_rx(adapter,&adapter ->rx_ring[0],&work_done,budget); //实际调用的是clean_rx_irq()
...
if(work_done ...
netif_rx_complete(poll_dev,napi); //__napi_complete()的包装函数
e1000_irq_enable(adapter); //开中断
}
return work_done;
}
static inline void netif_rx_complete(struct net_device *dev,struct napi_struct *napi){
unsigned long flags;
local_irq_save(flags);
__netif_rx_complete(dev,napi);
local_irq_restore(flags);
}
static inline void __netif_rx_complete(struct net_device *dev,struct napi_struct *napi){
__napi_complete(napi);
}
static inline void __napi_complete(struct napi_struct *n){
....
list_del(&n->poll_list);
clear_bit(NAPI_STATE_SCHED,&n->state);
}
设备轮询接收机制中最重要的函数e1000_clean_rx_irq()
#ifdef CONFIG_E1000_NAPI
e1000_clean_rx_irq(struct e1000_adapter *adapter,struct e1000_rx_ring *rx_ring,int *work_done,int work_to_do) //work_to_do实际上是传过来的配额weight
.....
{
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc,*next_rxd;
struct e1000_buffer *buffer_info, *next_buffer;
...
unsigned int i;
int cleaned_count = 0;
....
i = rx_ring->next_to_clean; //next_to_clean是下一个可以被清除的描述符索引,上面讲过环形缓冲队列由多个描述符组成,每个描述符都有一个用于存放接收数据包的缓冲区buffer,这里所说的“可以被清除”并不是将其删除,而是标记这个缓冲区的数据已经处理(可能正在处理),但是否处理完了要看rx_desc->status&E1000_RXD_STAT_DD,当有新数据需要使用缓冲区时,只是将已处理的缓冲区覆盖而已, 这里的i可以理解为可以被新数据覆盖的缓冲区序号
rx_desc = E1000_RX_DESC(*rx_ring,i); //得到相应的描述符
buffer_info = &rx_ring->buffer_info[i];
while(rx_desc->status & E1000_RXD_STAT_DD){ //测试其状态是否为已删除
struct sk_buff *skb;
u8 status;
#ifdef CONFIG_E1000_NAPI
if (*wrok_done>=work_to_do) //如果所完成的工作>配额则直接退出
break;
(*work_done) ++
#endif
status = rx_desc->status;
skb = buffer_info->skb; //得到缓冲区中的数据
buffer_info->skb = NULL;
prefetch(skb->data-NET_IP_ALIGN);
if(++i == rx_ring->count) //处理环形缓冲区达到队列末尾的情况,因为是环形的,所以到达末尾的下一个就是队列头,这样整个队列就不断地循环处理。然后获取下一格描述符的状态,看看是不是处理删除状态。如果处于就会将新到达的数据覆盖旧的缓冲区,如果不处于则跳出循环,并将当前缓冲区索引号置为下一次查询的目标
i = 0;
next_rxd = E1000_RX_DESC(*rx_ring,i);
next_buffer = &rx_ring->buffer_info[i];
cleaned = true ;
cleaned_count ++;
pci_unmap_single(pdev,buffer_info->dma,buffer_info->length,PCI_DMA_FROMDEVICE); //* 取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也就意味着,CPU可以处理主内存中的数据了 */
....
//checksum
...
#ifdef CONFIG_E1000_NAPI
netif_receive_skb(skb); //交由上层协议处理 , 如果数据包比较大,处理时间会相对较长
#else
netif_rx(skb); //进入中断模式 将数据包插入接收队列中,等待软中断处理 中断模式不用环形接收缓冲队列
#endif
netdev->last_rx = jiffies;

next_desc:
rx_desc->status =0;
if(unlikely(cleaned_count >= E1000_RX_BUFFER_WRITE)){
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count); //在e1000_up中已经调用了这个函数为环形缓冲区队列中的每个缓冲区分配了sk_buff内存,但是如果接收到数据以后,调用netif_receive_skb(skb)向上提交数据以后,这段内存将始终被这个skb占用(直到上层处理完以后才会调用_kfree_skb释放),换句话说,就是当前缓冲区必须重新申请分配sk_buff内存,为下一个数据作准备
cleaned_count = 0;
}
rx_desc = next_rxd;
buffer_info = next_buffer;
}
rx_ring->next_to_clean = i;
cleaned_count = E1000_DESC_UNUSED(rx_ring);
if(cleaned_count)
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count);
...
return cleaned;
}
static void e1000_alloc_rx_buffers(struct e1000_adapter *adapter,struct e1000_rx_ring *rx_ring,int cleaned_count){
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc;
struct e1000_buffer *buffer_info;
struct sk_buff *skb;
unsigned int i;
unsigned int bufsz = adapter->rx_buffer_len+NET_IP_ALIGN;
i=rx_ring->next_to_use;
buffer_info = &rx_ring->buffer_info[i];
while (cleaned_count--){
skb = buffer_info ->skb;
if(skb){
....
}
skb = netdev_alloc_skb(netdev,bufsz); //skb缓存的分配
if(unlikely(!skb)){
adapter->alloc_rx_buff_failed++;
break;
}
skb_reserve(skb,NET_IP_ALIGN);
buffer_info->skb = skb;
buffer_info->length = adapter ->rx_buffer_len;
map_skb:
buffer_info->dma = pci_map_single(pdev,skb->data, adapter->rx_buffer_len,PCI_DMA_FROMDEVICE); //建立DMA映射,把每一个缓冲区skb->data都映射给了设备,缓存区描述符利用dma保存了每一次映射的地址
....
rx_desc = E1000_RX_DESC(*rx_ring, i);
rx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
if (unlikely(++i == rx_ring->count)) //达到环形缓冲区末尾
i =0 ;
buffer_info = &rx_ring->buffer_info[i];
}
if(likely(rx_ring->netx_to_use!=i)){
rx_ring->next_to_use = i;
if (unlikely(i-- == 0))
i = (rx_ring->count - 1);
...
}
}

简要流程

intel e1000 网卡 napi分析(转)

转:http://sh-neo.spaces.live.com/blog/cns!1E3CA285E5F9E122!526.entry


内核如何从网卡接收数据,传统的过程:
1.数据到达网卡;
2.网卡产生一个中断给内核;
3.内核使用I/O指令,从网卡I/O区域中去读取数据;

我们在许多网卡驱动中(很老那些),都可以在网卡的中断函数中见到这一过程。

但是,这一种方法,有一种重要的问题,就是大流量的数据来到,网卡会产生大量的中断,内核在中断上下文中,会浪费大量的资源来处理中断本身。所以,就有一个问题,“可不可以不使用中断”,这就是轮询技术,所谓NAPI技术,说来也不神秘,就是说,内核屏蔽中断,然后隔一会儿就去问网卡,“你有没有数据啊?”……

从这个描述本身可以看到,如果数据量少,轮询同样占用大量的不必要的CPU资源,大家各有所长吧

OK,另一个问题,就是从网卡的I/O区域,包括I/O寄存器或I/O内存中去读取数据,这都要CPU去读,也要占用CPU资源,“CPU从I/O区域读,然后把它放到内存(这个内存指的是系统本身的物理内存,跟外设的内存不相干,也叫主内存)中”。于是自然地,就想到了DMA技术——让网卡直接从主内存之间读写它们的I/O数据,CPU,这儿不干你事,自己找乐子去:
1.首先,内核在主内存中为收发数据建立一个环形的缓冲队列(通常叫DMA环形缓冲区)。
2.内核将这个缓冲区通过DMA映射,把这个队列交给网卡;
3.网卡收到数据,就直接放进这个环形缓冲区了——也就是直接放进主内存了;然后,向系统产生一个中断;
4.内核收到这个中断,就取消DMA映射,这样,内核就直接从主内存中读取数据;

——呵呵,这一个过程比传统的过程少了不少工作,因为设备直接把数据放进了主内存,不需要CPU的干预,效率是不是提高不少?

对应以上4步,来看它的具体实现:
1)分配环形DMA缓冲区
Linux内核中,用skb来描述一个缓存,所谓分配,就是建立一定数量的skb,然后用e1000_rx_ring 环形缓冲区队列描述符连接起来
2)建立DMA映射
内核通过调用
dma_map_single(struct device *dev,void *buffer,size_t size,enum dma_data_direction direction)
建立映射关系。
struct device *dev 描述一个设备;
buffer:把哪个地址映射给设备;也就是某一个skb——要映射全部,当然是做一个双向链表的循环即可;
size:缓存大小;
direction:映射方向——谁传给谁:一般来说,是“双向”映射,数据在设备和内存之间双向流动;
对于PCI设备而言(网卡一般是PCI的),通过另一个包裹函数pci_map_single,这样,就把buffer交给设备了!设备可以直接从里边读/取数据。
3)这一步由硬件完成;
4)取消映射
dma_unmap_single,对PCI而言,大多调用它的包裹函数pci_unmap_single,不取消的话,缓存控制权还在设备手里,要调用它,把主动权掌握在CPU手里——因为我们已经接收到数据了,应该由CPU把数据交给上层网络栈;当然,不取消之前,通常要读一些状态位信息,诸如此类,一般是调用dma_sync_single_for_cpu()让CPU在取消映射前,就可以访问DMA缓冲区中的内容。


原代码分析
基于linux v2.6.26

//e1000_probe 网卡初始化 (重点关注两部分 1注册poll函数 2设置接收缓冲的大小)
static int __devinit e1000_probe(struct pci_dev *pdev,const struct pci_device_id *ent){
struct net_device *netdev;
struct e1000_adapter *adapter;
....
err=pci_enable_device(pdev);
...
err=pci_set_dma_mask(pdev,DMA_64BIT_MASK); //设置pci设备的dma掩码
...
netdev = alloc_etherdev(sizeof(struct e1000_adapter)); //为e1000网卡对应的net_device结构分配内存
...
pci_set_drvdata(pdev,netdev);
adapter=netdev_priv(netdev);
adapter->netdev=netdev;
adapter->pdev=pdev;
...
mmio_start = pci_resource_start(pdev,0);
mmio_len = pci_resource_len(pdev,0);
....
/*将e1000网卡驱动的相应函数注册到net_device中*/
netdev->open = &e1000_open;
netdev->stop = &e1000_close;
...
netif_napi_add(netdev,&adapter->napi,e1000_clean,64); // 注册poll函数为e1000_clean, weight为64
...
netdev->mem_start = mmio_start;
netdev->mem_end = mmio_start+mmio_len;
....
if(e1000e_read_mac_addr(&adapter->hw)) ndev_err(...); //从网卡设备的EEPROM中读取mac地址
memcpy(netdev->dev_addr, adapter->hw.mac.addr, netdev->addr_len);
memcpy(netdev->perm_addr, adapter->hw.mac.addr, netdev->addr_len);
....
adapter->rx_ring->count = 256; //设置接收环型缓冲区队列的缺省大小
...
e1000_reset(adapter);
...
strcpy(netdev->name,"eth%d");
err= register_netdev(netdev); //将当前网络设备注册到系统的dev_base[]设备数组当中
....
return 0;
}
e1000_open 各种数据结构初始化 (环形缓冲区队列的初始化)
static int e1000_open(struct net_device *netdev){
struct e1000_adapter *adapter = netdev_priv(netdev);
....
err = e1000_setup_all_rx_resoures(adapter) //预先分配缓冲区资源
....
err = e1000_request_irq(adapter); //分配irq中断
....
}
int e1000_setup_all_rx_resources(struct e1000_adapter *adapter){
int i,err=0;
for(i=0 ; inum_rx_queues ; i++){
err = e1000_setup_rx_resources(adapter,&adapter->rx_ring[i]);
if(err){
...
}
}
return err;
}
e1000_rx_ring 环形缓冲区队列(接收缓冲队列由多个描述符组成,每个描述符中都包含一个缓冲区buffer,该buffer以dma方式存放数据包,整个缓冲队列以环形排列 每个描述符都有一个状态变量以表示该缓冲区buffer是否可以被新到的数据包覆盖)
struct e1000_rx_ring{
void *desc; //指向该环形缓冲区
dma_addr_t dma; //dma物理地址
unsigned int size;
unsigned int count; //环形队列由多少个描述符组成,这个在probe中定义了
unsigned int next_to_use; //下一个可使用的描述符号
unsigned int next_to_clean; //该描述符状态(是否正在使用,是否脏)
struct e1000_buffer *buffer_info; //缓冲区buffer
...
}
struct e1000_buffer{
struct sk_buff *skb;
....
}
static int e1000_setup_rx_resources(struct e1000_adapt *adapter, struct e1000_rx_ring *rxdr){
struct pci_dev *pdev = adapter->pdev;
int size,desc_len;
size = sizeof(struct e1000_buffer) * rxdr->count;
rxdr->buffer_info = vmalloc(size);
memset(rxdr->buffer_info,0,size); //分配buffer所使用的内存
....
if(adapter->hw.mac_type <= e1000_82547_rec_2)
desc_len = sizeof(struct e1000_rx_desc);
else ....

rxdr->size = rxdr->count * desc_len;
rxdr->size = ALIGN(rxdr->size,4096);
rxdr->desc = pci_alloc_consistent(pdev,rxdr->size,&rxdr->dma);
...
memset(rxdr->desc,0,rxdr->size); //分配缓冲队列所使用的内存
rxdr->next_to_clean =0;
rxdr->next_to_use =0;
return 0;
}
e1000_up 启动网卡函数 调用alloc_rx_buf来建立环形缓冲队列
int e1000_up(struct e1000_adapter *adapter){
e1000_configure(adatper);
....
}
static void e1000_configure(struct e1000_adapter *adapter){
struct net_device *netdev = adapter->netdev;
int i;
...
e1000_configure_rx(adapter);
...
for (i=0;inum_rx_queues;i++){
struct e1000_rx_ring *ring = &adapter ->rx_ring[i];
adapter->alloc_rx_buf(adapter,ring,E1000_DESC_UNUSED(ring)); //从这里就可以看出 环形缓冲区并不是一开始就完全建好的,建了部分
}
...
}
static void e1000_configure_rx(struct e1000_adapter *adapter){
....
adapter->clean_rx = e1000_clean_rx_irq; //后面会提到的poll()
adapter->alloc_rx_buf = e1000_alloc_rx_irq //建立环形缓冲队列函数 这里实际调用的是e1000_alloc_rx_buffers
}
e1000_alloc_rx_buffers ----因为其中有些参数要看完下面的才能理解,所以这个函数最后再写

e1000_intr e1000的中断处理函数
static irqreturn_t e1000_intr(int irq,void *data){
struct net_device *netdev = data;
struct e1000_adapter *adapter = netdev_priv(netdev);
..
u32 icr = E1000_READ_REG(hw,ICR);
#ifdef CONFIG_E1000_NAPI
int i;
#endif
...
#ifdef CONFIG_E1000_NAPI //进入轮询模式
if(unlikely(hw->mac_type E1000_WRITE_REG(hw,IMC,~0); //关闭中断
E1000_WRITE_FLUSH(hw);
}
if (likely(netif_rx_schedule_prep(netdev,&adapter->napi))){ //确定该设备处于运行状态, 而且还未被添加到网络层的poll队列中
...
__netif_rx_schedule(netdev,&adapter->napi); //将当前设备netdevice加到与cpu相关的softnet_data的轮旬设备列表poll_list中并触发NET_RX_SOFTIRQ软中断
}
#else //进入中断模式
{ ...
for(i=0;i if (unlikely(!adapter->clean_rx(adapter, adapter->rx_ring) &.... //执行clean_rx()中关于中断模式的代码 不走napi路径
break;
...
}
}
....
return IRQ_HANDLED;
}
static inline int netif_rx_schedule_prep(struct net_device *dev,struct napi_struct *napi){
return napi_schedule_prep(napi);
}
static inline int napi_schedule_prep(struct napi_struct *n){
return !napi_disable_pending(n) &&
!test_and_set_bit(NAPI_STATE_SCHED, &n->state); //测试该设备是否已被被加到poll队列
}
static inline int napi_disable_pending (struct napi_struct *n){
return test_bit(NAPI_STATE_DISABLE,&n->state); //测试该设备是否停止运行
}
static inline void __netif_rx_schedule(struct net_device *dev,struct napi_struct *napi){
__napi_schedule(napi);
}
void __napi_schedule(struct napi_struct *n){
unsigned long flags;
local_irq_save(flags);
list_add_tail(&n->poll_list, &__get_cpu_var(softnet_data).poll_list);
__raise_softirq_irqoff(NET_RX_SOFTIRQ); 触发软中断
local_irq_restore(flags);
}
#define __raise_softirq_irqoff(nr) do {or_softirq_pending(iUL<<(nr)); } while(0)
用到的数据结构napi_struct
struct napi_struct{
struct list_head poll_list; //poll_list链表
unsigned long state //设备状态信息 往上看看
int weight; //设备预处理数据配额,作用是轮询时更公平的对待各个设备
int (*poll) (strcut napi_struct *,int);
.....
}
接下来就是软中断处理函数net_rx_action()
static void net_rx_action(struct softirq_action *h){
struct list_head *list = &__get_cpu_var(softnet_data).poll_list;
unsigned long start_time = jiffies;
int budget = netdev_budget; //处理限额,一次最多只能处理这么多数据包
....
local_irq_disable();
while (!list_empty(list)){
struct napi_struct *n;
int work,
weight;
if (unlikely(budget < 0 || jiffies != start_time)) //如果实际工作量work 超过限额,或处理时间超过1秒,则出于系统响应考虑立即从软中断处理函数中跳出来, work是poll的返回值 限额budget每次都会根据返回的work值重新计算 ,配额weight和work配合来实现轮询算法,具体算法要看完e1000_clean(),e1000_rx_irq()才能清楚
goto softnet_break;
local_irq_enalbe();
n = list_entry(list->next,struct napi_struct,poll_list);
weight = n->weight;
work 0;
if (test_bit(NAPI_STATE_SCHED,&n->state))
work = n->poll(n,weight); //调用设备的poll函数e1000_clean()
....
budget -= work; //更新限额
local_irq_disable();
if (unlikely(work == weight)){ //处理量大于配额
if(unlikely(napi_disable_pending(n)))
__napi_complete(n);
else
list_move_tail(&n->poll_list,list); //该设备还有要接收的数据没被处理,因为轮询算法 被移动到poll_llst尾部等待处理
}
...
}
out:
local_irq_enable();
...
return;
softnet_break:
__raise_softirq_irqoff(NET_RX_SOFTIRQ);
goto out;
}
e1000网卡poll函数 e1000_clean()
static int e1000_clean(struct napi_struct *napi,int budget){ //此处的budget实际上是传过来的weight,不要和上面的budget弄混了
struct e1000_adapter *adapter = container_of(napi,struct e1000_adapter,napi);
struct net_device *poll_dev = adapter->netdev;
int work_done = 0;
adapter = poll_dev->priv;
.....
adapter ->clean_rx(adapter,&adapter ->rx_ring[0],&work_done,budget); //实际调用的是clean_rx_irq()
...
if(work_done ...
netif_rx_complete(poll_dev,napi); //__napi_complete()的包装函数
e1000_irq_enable(adapter); //开中断
}
return work_done;
}
static inline void netif_rx_complete(struct net_device *dev,struct napi_struct *napi){
unsigned long flags;
local_irq_save(flags);
__netif_rx_complete(dev,napi);
local_irq_restore(flags);
}
static inline void __netif_rx_complete(struct net_device *dev,struct napi_struct *napi){
__napi_complete(napi);
}
static inline void __napi_complete(struct napi_struct *n){
....
list_del(&n->poll_list);
clear_bit(NAPI_STATE_SCHED,&n->state);
}
设备轮询接收机制中最重要的函数e1000_clean_rx_irq()
#ifdef CONFIG_E1000_NAPI
e1000_clean_rx_irq(struct e1000_adapter *adapter,struct e1000_rx_ring *rx_ring,int *work_done,int work_to_do) //work_to_do实际上是传过来的配额weight
.....
{
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc,*next_rxd;
struct e1000_buffer *buffer_info, *next_buffer;
...
unsigned int i;
int cleaned_count = 0;
....
i = rx_ring->next_to_clean; //next_to_clean是下一个可以被清除的描述符索引,上面讲过环形缓冲队列由多个描述符组成,每个描述符都有一个用于存放接收数据包的缓冲区buffer,这里所说的“可以被清除”并不是将其删除,而是标记这个缓冲区的数据已经处理(可能正在处理),但是否处理完了要看rx_desc->status&E1000_RXD_STAT_DD,当有新数据需要使用缓冲区时,只是将已处理的缓冲区覆盖而已, 这里的i可以理解为可以被新数据覆盖的缓冲区序号
rx_desc = E1000_RX_DESC(*rx_ring,i); //得到相应的描述符
buffer_info = &rx_ring->buffer_info[i];
while(rx_desc->status & E1000_RXD_STAT_DD){ //测试其状态是否为已删除
struct sk_buff *skb;
u8 status;
#ifdef CONFIG_E1000_NAPI
if (*wrok_done>=work_to_do) //如果所完成的工作>配额则直接退出
break;
(*work_done) ++
#endif
status = rx_desc->status;
skb = buffer_info->skb; //得到缓冲区中的数据
buffer_info->skb = NULL;
prefetch(skb->data-NET_IP_ALIGN);
if(++i == rx_ring->count) //处理环形缓冲区达到队列末尾的情况,因为是环形的,所以到达末尾的下一个就是队列头,这样整个队列就不断地循环处理。然后获取下一格描述符的状态,看看是不是处理删除状态。如果处于就会将新到达的数据覆盖旧的缓冲区,如果不处于则跳出循环,并将当前缓冲区索引号置为下一次查询的目标
i = 0;
next_rxd = E1000_RX_DESC(*rx_ring,i);
next_buffer = &rx_ring->buffer_info[i];
cleaned = true ;
cleaned_count ++;
pci_unmap_single(pdev,buffer_info->dma,buffer_info->length,PCI_DMA_FROMDEVICE); //* 取消映射,因为通过DMA,网卡已经把数据放在了主内存中,这里一取消,也就意味着,CPU可以处理主内存中的数据了 */
....
//checksum
...
#ifdef CONFIG_E1000_NAPI
netif_receive_skb(skb); //交由上层协议处理 , 如果数据包比较大,处理时间会相对较长
#else
netif_rx(skb); //进入中断模式 将数据包插入接收队列中,等待软中断处理 中断模式不用环形接收缓冲队列
#endif
netdev->last_rx = jiffies;

next_desc:
rx_desc->status =0;
if(unlikely(cleaned_count >= E1000_RX_BUFFER_WRITE)){
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count); //在e1000_up中已经调用了这个函数为环形缓冲区队列中的每个缓冲区分配了sk_buff内存,但是如果接收到数据以后,调用netif_receive_skb(skb)向上提交数据以后,这段内存将始终被这个skb占用(直到上层处理完以后才会调用_kfree_skb释放),换句话说,就是当前缓冲区必须重新申请分配sk_buff内存,为下一个数据作准备
cleaned_count = 0;
}
rx_desc = next_rxd;
buffer_info = next_buffer;
}
rx_ring->next_to_clean = i;
cleaned_count = E1000_DESC_UNUSED(rx_ring);
if(cleaned_count)
adapter->alloc_rx_buf(adapter,rx_ring,cleaned_count);
...
return cleaned;
}
static void e1000_alloc_rx_buffers(struct e1000_adapter *adapter,struct e1000_rx_ring *rx_ring,int cleaned_count){
struct net_device *netdev = adapter->netdev;
struct pci_dev *pdev = adapter->pdev;
struct e1000_rx_desc *rx_desc;
struct e1000_buffer *buffer_info;
struct sk_buff *skb;
unsigned int i;
unsigned int bufsz = adapter->rx_buffer_len+NET_IP_ALIGN;
i=rx_ring->next_to_use;
buffer_info = &rx_ring->buffer_info[i];
while (cleaned_count--){
skb = buffer_info ->skb;
if(skb){
....
}
skb = netdev_alloc_skb(netdev,bufsz); //skb缓存的分配
if(unlikely(!skb)){
adapter->alloc_rx_buff_failed++;
break;
}
skb_reserve(skb,NET_IP_ALIGN);
buffer_info->skb = skb;
buffer_info->length = adapter ->rx_buffer_len;
map_skb:
buffer_info->dma = pci_map_single(pdev,skb->data, adapter->rx_buffer_len,PCI_DMA_FROMDEVICE); //建立DMA映射,把每一个缓冲区skb->data都映射给了设备,缓存区描述符利用dma保存了每一次映射的地址
....
rx_desc = E1000_RX_DESC(*rx_ring, i);
rx_desc->buffer_addr = cpu_to_le64(buffer_info->dma);
if (unlikely(++i == rx_ring->count)) //达到环形缓冲区末尾
i =0 ;
buffer_info = &rx_ring->buffer_info[i];
}
if(likely(rx_ring->netx_to_use!=i)){
rx_ring->next_to_use = i;
if (unlikely(i-- == 0))
i = (rx_ring->count - 1);
...
}
}

简要流程