Tuesday, February 22, 2011

Library可分成三種,static、shared與dynamically loaded。

Library可分成三種,static、shared與dynamically loaded。

轉:http://tw.myblog.yahoo.com/jw!hNFmfgGGChKlJMAjZ8LkvnQn1bo-/article?mid=720
 
2009/02/26 19:30
1. Static libraries


Static 程式庫用於靜態連結,簡單講是把一堆object檔用ar(archiver)

包裝集合起來,檔名以`.a’ 結尾。優點是執行效能通常會比後兩者快,

而且因為是靜態連結,所以不易發生執行時找不到library或版本錯置而

無法執行的問題。缺點則是檔案較大,維護度較低;例如library如果發

現bug需要更新,那麼就必須重新連結執行檔。


1.1 編譯


編譯方式很簡單,先例用`-c’ 編出object 檔,再用ar 包起來即可。


____ hello.c ____

#include

void hello(){ printf(”Hello “); }


____ world.c ____

#include

void world(){ printf(”world.”); }


____ mylib.h ____

void hello();

void world();


$ gcc -c hello.c world.c /* 編出hello.o 與world.o */

$ ar rcs libmylib.a hello.o world.o /* 包成limylib.a */


這樣就可以建出一個檔名為libmylib.a 的檔。輸出的檔名其實沒有硬性規定,

但如果想要配合gcc 的’-l’ 參數來連結,一定要以`lib’ 開頭,中間是你要

的library名稱,然後緊接著`.a’ 結尾。


1.2 使用


____ main.c ____

#include “mylib.h”

int main() {

hello();

world();

}


使用上就像與一般的object 檔連結沒有差別。


$ gcc main.c libmylib.a


也可以配合gcc 的`-l’ 參數使用


$ gcc main.c -L. -lmylib


-Ldir' 參數用來指定要搜尋程式庫的目錄,`.' 表示搜尋現在所在的目
錄。

通常預設會搜/usr/lib 或/lib 等目錄。
-llibrary' 參數用來指定要連結的程式庫,'mylib' 表示要與mylib進
行連結

,他會搜尋library名稱前面加'lib'後面接'.a'的檔案來連結。


$ ./a.out

Hello world.

2. Shared libraries


Shared library 會在程式執行起始時才被自動載入。因為程式庫與執行檔

是分離的,所以維護彈性較好。有兩點要注意,shared library是在程式起始

時就要被載入,而不是執行中用到才載入,而且在連結階段需要有該程式庫

才能進行連結。


首先有一些名詞要弄懂,soname、real name與linker name。


so name 用來表示是一個特定library 的名稱,像是libmylib.so.1 。

前面以`lib' 開頭,接著是該library 的名稱,然後是`.so' ,接著

是版號,用來表名他的介面;如果介面改變時,就會增加版號來維護相容度。


real name 是實際放有library程式的檔案名稱,後面會再加上minor 版號與

release 版號,像是libmylib.so.1.0.0 。


一般來說,版號的改變規則是(印象中在APress-Difinitive Guide to GCC中有

提到,但目前手邊沒這本書),最尾碼的release版號用於程式內容的修正,

介面完全沒有改變。中間的minor用於有新增加介面,但相舊介面沒改變,所以

與舊版本相容。最前面的version版號用於原介面有移除或改變,與舊版不相容

時。


linker name是用於連結時的名稱,是不含版號的soname ,如: libmylib.so。

通常linker name與real name是用ln 指到對應的real name ,用來提供

彈性與維護性。


2.1 編譯

shared library的製作過程較複雜。


$ gcc -c -fPIC hello.c world.c


編譯時要加上-fPIC 用來產生position-independent code。也可以用-fpic

參數。(不太清楚差異,只知道-fPIC 較通用於不同平台,但產生的code較大

,而且編譯速度較慢)。


$ gcc -shared -Wl,-soname,libmylib.so.1 -o libmylib.so.1.0.0 \

hello.o world.o


-shared 表示要編譯成shared library

-Wl 用於參遞參數給linker,因此-soname與libmylib.so.1會被傳給linker處理。

-soname用來指名soname 為limylib.so.1

library會被輸出成libmylib.so.1.0.0 (也就是real name)


若不指定soname 的話,在編譯結連後的執行檔會以連時的library檔名為

soname,並載入他。否則是載入soname指定的library檔案。


可以利用objdump 來看library 的soname。


$ objdump -p libmylib.so | grep SONAME

SONAME libmylib.so.1


若不指名-soname參數的話,則library不會有這個欄位資料。


在編譯後再用ln 來建立soname 與linker name 兩個檔案。

$ ln -s libmylib.so.1.0.0 libmylib.so

$ ln -s libmylib.so.1.0.0 libmylib.so.1

2.2 使用


與使用static library 同。


$ gcc main.c libmylib.so


以上直接指定與libmylib.so 連結。


或用


$ gcc main.c -L. -lmylib


linker會搜尋libmylib.so 來進行連結。


如果目錄下同時有static與shared library的話,會以shared為主。

使用-static 參數可以避免使用shared連結。

$ gcc main.c -static -L. -lmylib


此時可以用ldd 看編譯出的執行檔與shared程式庫的相依性

$ldd a.out

linux-gate.so.1 => (0xffffe000)
libmylib.so.1 => not found

libc.so.6 => /lib/libc.so.6 (0xb7dd6000)

/lib/ld-linux.so.2 (0xb7f07000)

輸出結果顯示出該執行檔需要libmylib.so.1 這個shared library。

會顯示not found 因為沒指定該library所在的目錄,所找不到該library。


因為編譯時有指定-soname參數為libmylib.so.1 的關係,所以該執行檔會

載入libmylib.so.1。否則以libmylib.so連結,執行檔則會變成要求載入

libmylib.so


$ ./a.out

/a.out: error while loading shared libraries: [1;33mlibmylib.so.1[m:

cannot open shared object file: No such file or directory


因為找不到libmylib.so.1 所以無法執行程式。

有幾個方式可以處理。


a. 把libmylib.so.1 安裝到系統的library目錄,如/usr/lib下

b. 設定/etc/ld.so.conf ,加入一個新的library搜尋目錄,並執行ldconfig

更新快取

c. 設定LD_LIBRARY_PATH 環境變數來搜尋library

這個例子是加入目前的目錄來搜尋要載作的library

$ LD_LIBRARY_PATH=. ./a.out

Hello world.

3. Dynamically loaded libraries


Dynamicaaly loaded libraries 才是像windows 所用的DLL ,在使用到

時才載入,編譯連結時不需要相關的library。動態載入庫常被用於像plug-ins

的應用。


3.1 使用方式

動態載入是透過一套dl function來處理。

#include

void *dlopen(const char *filename, int flag);

開啟載入filename 指定的library。

void *dlsym(void *handle, const char *symbol);

取得symbol 指定的symbol name在library被載入的記憶體位址。

int dlclose(void *handle);

關閉dlopen開啟的handle。

char *dlerror(void);

傳回最近所發生的錯誤訊息。


____ dltest.c ____

#include

#include

#include

int main() {

void *handle;

void (*f)();

char *error;


/* 開啟之前所撰寫的libmylib.so 程式庫*/

handle = dlopen("./libmylib.so", RTLD_LAZY);

if( !handle ) {

fputs( dlerror(), stderr);

exit(1);

}


/* 取得hello function 的address */

f = dlsym(handle, "hello");

if(( error=dlerror())!=NULL) {

fputs(error, stderr);

exit(1);

}

/* 呼叫該function */

f();

dlclose(handle);

}


編譯時要加上-ldl 參數來與dl library 連結

$ gcc dltest.c -ldl

結果會印出Hello 字串

$ ./a.out

Hello


關於dl的詳細內容請參閱man dlopen


--

參考資料:


Creating a shared and static library with the gnu compiler [gcc]

http://www.adp-gmbh.ch/cpp/gcc/create_lib.html


Program Library HOWTO

http://tldp.org/HOWTO/Program-Library-HOWTO/index.html

Wednesday, February 9, 2011

文件系统

文件系统

轉:http://www.cnitblog.com/tarius.wu/articles/364.html
 
首先,文件系统应该被看作一种存储和定位数据的机制。为了提供对磁盘高效且便捷的访问,操作系统通过文件系统来实现存储、定位、和提取数据。文件系统有两个不同的设计问题。
第一个问题是如何定义文件系统对用户的接口。具体方面涉及到定义文件及其属性,文件所允许的操作,组织文件的目录结构。这一层就是普通用户对文件系统的第一映像,如文件的类型,读写权限,目录是树型结构还是其它结构。这一层通常被称为逻辑文件系统。
第二个问题是创建怎样的数据结构和算法来将逻辑文件系统映射到物理外存设备上。这一层可以称之为底层文件系统。

1 逻辑文件系统

逻辑文件系统用来当作与用户的接口,用户不需要知道文件具体如何访问,只需知道文件名、文件在哪一个目录中,用户所具有的访问权限等。逻辑文件系统通过文件控制块(file control blockFCB来维护文件结构,它包含文件的信息,如拥有者,权限和文件的位置。因此,FCB是逻辑文件系统和底层文件系统的联系纽带。在UNIX系统中,FCB被称做索引节点(i 节点);而在NTFS中将FCB的信息存在主控文件表中,主控文件表采用关系数据库结构,每个文件占一行。
除了FCB,在格式化后的磁盘中还应存在以下数据结构:
  •   引导控制块(boot control block:存储从该分区引导操作系统所需要的信息。如内核镜像的位置,引导参数等。它通常为分区的第一块。如果该分区没有操作系统,那么这块的内容为空。UNIX文件系统称之为引导块,NTFS称之为分区引导扇区。
  •     分区控制块(partition control block:包含分区的详细信息,如分区的块数,块的大小,空闲块的数量和指针。UNIX系统称之为超级块(super block)NTFS称之为主控文件表(master file table)。
  •   目录结构:用来组织文件。

分区

硬盘等非易失性存储设备在使用前必须先被格式化,格式化的含义就是规定这个硬盘中的某个分区的文件系统是什么格式,是ext2ext3,还是windowsfat32ntfs等,这种格式化信息写在硬盘分区的固定位置上,如磁盘标签或卷头上。这样,当操作系统或其他能读写硬盘的实体一旦访问到某一硬盘分区,就会先读该分区的文件系统格式,看看自己是不是支持这种文件系统格式,如果不支持,就会读不到数据,当然也无法往该分区写数据。典型的如DOS就不认识NTFS格式。分区是硬件级的划分,因此它们是独立于任何操作系统的。

文件和目录

UNIX中存在的任何东西都是文件,发生的任何事情都是进程。目录是文件类型的一种,目录中放置其所有组成文件的文件名和索引号(i节点),包括子目录在内。也就是说,目录是文件的分组集合。
目录实际上是一张表,每一项记录着所包含文件的文件名和一个指向该文件的指针。要访问一个文件,就必须先访问文件所在的目录,而这个目录可能是它上级目录的子目录。因此,访问文件的过程就是根据文件系统中最上层的目录来层层索引,先找到文件的起始地址,以找到文件的i节点,再从i节点上得到整个文件的物理存储信息和属性。
目录是一个表,查找表里的项目就带来了查找效率问题。一般目录列表有线性列表和哈希列表。其各自的效率问题这里不作讨论。

2 底层文件系统

在底层文件系统里我们要关心磁盘空间的划分方式以及文件的存储方式。这里列出了三种最基本的方式,现实中的实现都是以它们为基础的变形。磁盘的划分方式也即所谓的分区格式。

磁盘空间连续分配

连续分配要求每个文件在磁盘上占有一组连续的块。与内存空间的分配一样,连续分配的好处就是访问数据块的速度加快,比如在访问b块后访问b+1块时通常不需要移动磁头。只有从当前磁道的最后一个扇区到下个磁道的第一个扇区需要,需要移动一个磁道。连续分配的缺点和内存连续分配一样,就是带来了磁盘碎片。

链接分配

硬盘的访问最小单元是块,一般是512B。采用链接分配,每个文件是磁盘块的链表;磁盘块分布在磁盘的任何地方。
文件目录包括文件第一块的指针和最后一块的指针。
例如有一个5块的文件从第9块开始,然后是1611025;每一块都有一个指向下一块的指针,-1表示最后一块。
链接分配的缺点有两个:一是它只能用于文件的顺序访问;要访问一个文件的第i块,就要从该文件的起始块开始。二是指针需要空间。一个块有512B,指针要占用4B
一种优化的办法是将硬盘的最小访问单元定义成几个块的组合,叫做簇。比如一个簇4块。按簇而不是按块来分配。这种方法提高了访问速度和指针的占用比,但增加了磁盘碎片。
MS-DOSOS/2使用的FAT(文件分配表)格式就是链接分配的一种变种。

索引分配

索引分配可以解决链接分配不能有效地解决的随机访问问题,它把文件的所有块指针放在一起:索引块。
rr.JPG
目录中文件条目包含索引块地址,索引块中的第i个条目指向文件的第i块。要读入第i块,只要读入索引块的第i项。

内存管理单元 MMU

内存管理单元 MMU

轉:http://www.cnitblog.com/tarius.wu/articles/322.html
 
现代操作系统及CPU硬件中,都会提供内存管理单元(memory management unitMMU)来进行内存的有效管理。内存管理算法有许多,从简单的裸机方法到分页和分段策略。各种算法都有其优缺点,为特定系统选择内存管理算法依赖于很多因素,特别是系统的硬件设计。

1 内存管理的目的

内存管理的目的是为了更好的使用内存(似乎是废话-,-)。 内存是现代操作系统运行的中心。操作系统中任何一个进程的运行都需要内存,但是,操作系统中的内存是有限的;另一方面,从安全的角度出发,进程都需要有自 己的内存空间,其他的进程都不能访问这个私有的空间;同时,内存的分配会导致内存碎片问题,严重影响计算机的性能。以上这三个问题就是一般内存管理算法所 需要处理的目标。
  •   交换
  • 内存保护
  •   碎片问题

2 交换

进程需要在内存中执行,但进程可以暂时从内存中交换swap)出去到备份存储上,当需要时再调回到内存中去。
m1.JPG
在基于优先级的交换调度算法中,如果一个更高优先级的进程来了且需要服务,内存管理可以交换出低优先级的进程,以便装入和执行更高优先级的进程。当高优先级的进程执行完毕之后,低优先级的进程可以交换回内存继续执行。这种交换有时被称之为滚出(roll out)、滚进(roll in)
       通常一个交换出的进程需要交换回它原来的内存空间。这一限制是由地址捆绑方式决定的。如果捆绑是在汇编时或加载时决定的,那么就不可以移动到不同的位置。如果捆绑是在运行时决定,由于物理地址是在运行时才确定,那么进程可以移到不同的地址空间。
交换的代价:交换时间
交换时间主要是指转移时间,总的转移时间和所交换的内存空间成正比。交换不需要或只需要很少的磁头移动,简单的说,交换空间通常作为磁盘的一整块,且独立于文件系统(如Linuxswap空间),因此使用就有可能很快。
交换需要花很多时间,而且进程执行时间却很少,故交换通常不执行,但只有在许多进程运行且内存吃紧时,交换才开始启动。

3 分页

3.1内存保护和内存碎片的背景

内存保护是指操作系统进程不受用户进程的影响,保护用户进程不受其他用户进程的影响。内存保护最基本的思路是进程使用逻辑地址空间,而内存管理单元所看的是物理地址。操作系统将逻辑地址分配给进程,MMU负责逻辑地址到物理地址的映射(转换,捆绑)。
       值得注意的是对于指令(程序)和数据映射到内存地址可以在以下步骤地任意一步执行:
  • 编译时:如果编译时就知道进程将在内存中的驻留地址,那么就可以生成绝对代码。如MS-DOSCOM格式程序。
  • 加载时:如果编译时不知道程序将驻留在何处,那么编译器就必须要生成可重定位代码(relocatable code )。这种情况下,最后地址映射会延迟到加载时才确定,如果开始地址发生变换,必须重新加载程序,以引入新值。
  • 执行时:如果进程在执行时可以从一个内存段移到另一个内存段,那么映射必须延迟到执行时才进程。采用这种方法需要硬件的支持,目前绝大多数操作系统采用这种方法(基于分页、分段等)。
内存碎片是操作系统内存分配的产物。最初操作系统的内存是连续分配的,即进程A需要某一大小的内存空间,如200KB,操作系统就需要找出一块至少200KB的连续内存空间给进程A。随着系统的运行,进程终止时它将释放内存,该内存可以被操作系统分配给输入队列里的其他等待内存资源的进程。
可以想象,随着进程内存的分配和释放,最初的一大块连续内存空间被分成许多小片段,即使总的可用空间足够,但不再连续,因此产生的内存碎片。
一 个办法是不再对内存空间进行连续分配。这样只要有物理内存就可以为进程进行分配。而实际上,不进行连续分配只是相对的,因为完全这样做的代价太大。现实 中,往往定出一个最小的内存单元,内存分配是这最小单元的组合,单元内的地址是连续的,但各个单元不一定连续。这样的内存小单元有页和段。
当然,分段和分页也会产生碎片,但理论上每个碎片的大小不超过内存单元的大小。

3.2 分页

分页时一种内存管理方案,同时也提供了内存保护机制。它允许分配的物理内存地址可以是非连续的。
逻辑内存分为大小相等块的组合,这个块称之为页;物理内存则分为固定大小的帧(frame)。页大小应和帧大小相同,这是由硬件决定的。通常是2的幂,这样可以方便地将逻辑地址映射到物理地址。
基于分页的逻辑地址到物理地址的映射
m2.JPG
考虑32位地址。如果页大小是4KB,则需要12位来表示每一页中的某个具体地址,因此32位的逻辑地址中需要12位来对某一页中的具体地址寻址。这12位叫做页偏移。剩下的20位可以作为页码,可以有1M的页。逻辑地址可以表示为:
                m4.JPG
寻址时,根据页码P查页表,找到该页对应的帧,将帧号与页偏移(也是帧偏移)组合即得到物理地址。这样也说明了为什么页大小要等于帧大小,因为页数要等于帧数。
例如,页大小为4K,页码11对应的帧码是10,即表示是第10块物理帧,也偏移为5,则逻辑地址0X0000b 005对应的物理地址是0X0000a 005

3.3 分页的内存保护

基于分页的操作系统在分配内存时分给进程所需要的页数,其对应物理内存的帧号同时装入该MMU的页表。同时页表上有一个标记为,指明该页是属于哪个进程的。甚至可以定义该页对于某个进程的读写权限。非法的读写操作会产生硬件陷阱(或内存保护冲突)。

3.4 分页的代价

由上一节可知分页是基于查找表的,而在内存中存储这个1M个项目的页表本身就带来了内存消耗和查找速度问题。于是,页表通常需要硬件的支持,即将页表写在硬件MMU的寄存器中。
如果页表比较小,那么页表写在寄存器中可以加快查找速度。但绝大多数当代的计算机页表都非常大。对于这些页表,采用快速寄存器来实现页表就不太合理了。
一种办法是使用TLBtranslation look-aside buffer)。TLB是关联的寄存器,TLB条目由两部分组成:页号和帧号。
m5.JPG
TLB只包含页表中的一小部分条目,整个页表还是保存在内存中。当CPU产生逻辑地址后,其页号提交给TLB。如果找到了页号,那同时也就找到了帧号,就可以访问物理内存;如果页号不在TLB中(称为TLB失效),那就需要访问页表。在页表中找到帧号后,把页号和帧号增加到TLB中,这样下次再用时可以很快找到。如果TLB中条目已满,则操作系统会根据一个替换策略来替换条目。替换策略有很多,从最近最小使用替换到随机替换等。另外,有的TLB允许某些条目不被替换,如内核代码的条目。
有的TLB还在条目中保存地址空间保护标志符,用来唯一标志进程,以提供进程内存保护。

3.5 页表结构

对于32位以及64位逻辑地址的计算机来说,其页表实在太过庞大。为了压缩页表,一个简单的方法是使用层次化分页算法,就是将页表再分页。(这实际上是一种索引的方法)
m6.JPG
m7.JPG
即将2^20个页表项分为2^10个组,每个组里面有2^10项。这样只需要2K*4=8K的页表空间,且查找速度也有很大提升。例如原先最坏情况下要查2^20次,现在最坏只要2*2^10

关于进程、线程和轻量级进程的一些笔记

关于进程、线程和轻量级进程的一些笔记

轉:http://hi.baidu.com/plulu/blog/item/cc5f3a16fdeec710962b4369.html

在现代操作系统中,进程支持多线程。进程是资源管理的最小单元;而线程是程序执行的最小单元。一个进程的组成实体可以分为两大部分:线程集合资源集。进程中的线程是动态的对象;代表了进程指令的执行。资源,包括地址空间、打开的文件、用户信息等等,由进程内的线程共享。
线程有自己的私有数据:程序计数器,栈空间以及寄存器。

Why Thread?(传统单线程进程的缺点)

1. 现实中有很多需要并发处理的任务,如数据库的服务器端、网络服务器、大容量计算等。
2. 传统的UNIX进程是单线程的,单线程意味着程序必须是顺序执行,不能并发;既在一个时刻只能运行在一个处理器上,因此不能充分利用多处理器框架的计算机。
3. 如果采用多进程的方法,则有如下问题:
a. fork
一个子进程的消耗是很大的,fork是一个昂贵的系统调用,即使使用现代的写时复制(copy-on-write)技术。
b.
各个进程拥有自己独立的地址空间,进程间的协作需要复杂的IPC技术,如消息传递和共享内存等。

多线程的优缺点

多线程的优点和缺点实际上是对立统一的。
支持多线程的程序(进程)可以取得真正的并行(parallelism),且由于共享进程的代码和全局数据,故线程间的通信是方便的。它的缺点也是由于线程共享进程的地址空间,因此可能会导致竞争,因此对某一块有多个线程要访问的数据需要一些同步技术。

三种线程——内核线程、轻量级进程、用户线程

内核线程
内核线程就是内核的分身,一个分身可以处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,唯一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫做多线程内核(Multi-Threads kernel )
轻量级进程[*]
轻量级线程(LWP)是一种由内核支持的用户线程。它是基于内核线程的高级抽象,因此只有先支持内核线程,才能有LWP。每一个进程有一个或多个LWPs,每个LWP由一个内核线程支持。这种模型实际上就是恐龙书上所提到的一对一线程模型。在这种实现的操作系统中,LWP就是用户线程。
由于每个LWP都与一个特定的内核线程关联,因此每个LWP都是一个独立的线程调度单元。即使有一个LWP在系统调用中阻塞,也不会影响整个进程的执行。
轻量级进程具有局限性。首先,大多数LWP的操作,如建立、析构以及同步,都需要进行系统调用。系统调用的代价相对较高:需要在user modekernel mode中切换。其次,每个LWP都需要有一个内核线程支持,因此LWP要消耗内核资源(内核线程的栈空间)。因此一个系统不能支持大量的LWP
LWP.JPG
注:
1.   LWP的术语是借自于SVR4/MPSolaris 2.x
2.   有些系统将LWP称为虚拟处理器。
3.   将之称为轻量级进程的原因可能是:在内核线程的支持下,LWP是独立的调度单元,就像普通的进程一样。所以LWP的最大特点还是每个LWP都有一个内核线程支持。
用户线程
LWP虽然本质上属于用户线程,但LWP线程库是建立在内核之上的,LWP的许多操作都要进行系统调用,因此效率不高。而这里的用户线程指的是完全建立在用户空间的线程库,用户线程的建立,同步,销毁,调度完全在用户空间完成,不需要内核的帮助。因此这种线程的操作是极其快速的且低消耗的。
Uthread1.JPG
上图是最初的一个用户线程模型,从中可以看出,进程中包含线程,用户线程在用户空间中实现,内核并没有直接对用户线程进程调度,内核的调度对象和传统进程一样,还是进程本身,内核并不知道用户线程的存在。用户线程之间的调度由在用户空间实现的线程库实现。
这种模型对应着恐龙书中提到的多对一线程模型,其缺点是一个用户线程如果阻塞在系统调用中,则整个进程都将会阻塞。
加强版的用户线程——用户线程+LWP
这种模型对应着恐龙书中多对多模型。用户线程库还是完全建立在用户空间中,因此用户线程的操作还是很廉价,因此可以建立任意多需要的用户线程。操作系统提供了LWP作为用户线程和内核线程之间的桥梁。LWP还是和前面提到的一样,具有内核线程支持,是内核的调度单元,并且用户线程的系统调用要通过LWP,因此进程中某个用户线程的阻塞不会影响整个进程的执行。用户线程库将建立的用户线程关联到LWP上,LWP与用户线程的数量不一定一致。当内核调度到某个LWP上时,此时与该LWP关联的用户线程就被执行。
Uthread2.JPG

小结:

很多文献中都认为轻量级进程就是线程,实际上这种说法并不完全正确,从前面的分析中可以看到,只有在用户线程完全由轻量级进程构成时,才可以说轻量级进程就是线程。

How to create X.509 key by using openssl

Setting up OpenSSL to Create Certificates

Web servers, imap servers, smtp servers can be configured to use ssl connections and there are many other uses for ssl certificates, such as encrypting email or digitally signing documents. You don't have to pay a certificate authority, such as Verisign, because you can use the OpenSSL package to create your own certificates. I do not cover the installation of OpenSSL here and I assume you know at least how to change directories, move files, use an editor, and other basics from the command-line.
Note: The author of this page, and owner of this web site, is not to be held liable for any damage or trouble arising from following these directions. You are responsible for your own security, use, and creation of certificates.

Quick steps:

  1. Create a directory.
  2. Create two subdirectories.
  3. Create a database to keep track of each certificate signed.
  4. Make a custom config file for openssl to use.
  5. Create a root certificate.
  6. Installing the root certificate for use.
  7. Tips.
  8. Creating certificates pages.

Note: While this document covers OpenSSL under Linux, Windows-only folks can use the Win32 OpenSSL project. I found GOSSL and CertWiz, GUIs for Windows, after a quick search. I am running Red Hat Linux 8.0 and openssl 0.9.x. If you have an interal box running Apache web server with PHP and the OpenSSL libraries installed, you could also use PHPki. I would not use PHPki for creating certificates on a publicly accessable server, because your root private certificate must be installed on that server (security risk). [Note: I found it not too hard to migrate manually to PHPki after already having created some certificates from the command line. Also you must have short tags enabled in your php.ini.] Only command line steps will be covered here.
1) Create a directory
Let's call it sslcert:
mkdir sslcert
Now protect that directory so only the user you are running as (and root) can access it:
chmod 0700 sslcert

2) Create two subdirectories
Cd into the first directory and make two subdirectories. Let's call them certs and private.
cd sslcert
mkdir certs private

3) Create a database to keep track of each certificate signed
Type:
echo '100001' >serial
touch certindex.txt

4) Make a custom config file for openssl to use
Create a file using your ASCII text editor. We will call it openssl.cnf. Here are the basics needed for this exercise (edit as needed):
#
# OpenSSL configuration file.
#
 
# Establish working directory.
 
dir     = .
 
[ ca ]
default_ca    = CA_default
 
[ CA_default ]
serial     = $dir/serial
database    = $dir/certindex.txt
new_certs_dir    = $dir/certs
certificate    = $dir/cacert.pem
private_key    = $dir/private/cakey.pem
default_days    = 365
default_md    = md5
preserve    = no
email_in_dn    = no
nameopt     = default_ca
certopt     = default_ca
policy     = policy_match
 
[ policy_match ]
countryName    = match
stateOrProvinceName   = match
organizationName   = match
organizationalUnitName   = optional
commonName    = supplied
emailAddress    = optional
 
[ req ]
default_bits    = 1024   # Size of keys
default_keyfile    = key.pem  # name of generated keys
default_md    = md5    # message digest algorithm
string_mask    = nombstr  # permitted characters
distinguished_name   = req_distinguished_name
req_extensions    = v3_req
 
[ req_distinguished_name ]
# Variable name    Prompt string
#-------------------------   ----------------------------------
0.organizationName   = Organization Name (company)
organizationalUnitName   = Organizational Unit Name (department, division)
emailAddress    = Email Address
emailAddress_max   = 40
localityName    = Locality Name (city, district)
stateOrProvinceName   = State or Province Name (full name)
countryName    = Country Name (2 letter code)
countryName_min    = 2
countryName_max    = 2
commonName    = Common Name (hostname, IP, or your name)
commonName_max    = 64
 
# Default values for the above, for consistency and less typing.
# Variable name    Value
#------------------------   ------------------------------
0.organizationName_default  = My Company
localityName_default   = My Town
stateOrProvinceName_default  = State or Providence
countryName_default   = US
 
[ v3_ca ]
basicConstraints   = CA:TRUE
subjectKeyIdentifier   = hash
authorityKeyIdentifier   = keyid:always,issuer:always
 
[ v3_req ]
basicConstraints   = CA:FALSE
subjectKeyIdentifier   = hash

5) Create a root certificate
All other certificates you create will be based off of this. Because you are not a commercial certificate authority, software may complain when they use your certificates. However you can give people one of the files, the "public" one, that will be created and they can manually import it. From then on your certificates will load just like the commercial ones.
To create, while in the 'sslcert' directory, type:
openssl req -new -x509 -extensions v3_ca -keyout \
private/cakey.pem -out cacert.pem -days 365 -config ./openssl.cnf

Note the backslash (\) at the end of the first line. If your OS supports it, this is a way to type long command lines. Simply press <Enter> after it and you will be prompted to continue typing. Otherwise, leave it out and continue typing.
Note the -days 365 option. For a root certificate you may want it to last longer than one year so that you do not have to reissue it so often. I set mine to 10 years.
You will be prompted for information and a password. Do not loose this password, make sure it is a secure one, and back up the two files that are created.
The two files that are created are cacert.pem, which is the one you can give to others for import in their browsers, and cakey.pem, which will be in the private directory.
Name Field Explanation Example
Country Name The two-letter ISO abbreviation for your country US = United States
State or Province Name The state or province where your organization is located. Can not be abbreviated. Georgia
City or Locality The city where your organization is located. Atlanta
Organization Name The exact legal name of your organization. Do not abbreviate SSL Secure Inc.
Organizational Unit Optional for additional organization information. Marketing
Common Name Since this is your root certificate, call it something like: Company_Name Certificate Authority
Email Address The email address for the CA (who to contact) someone@your.domain

6) Installing the root certificate for use
See your browser's help for how to do this. For IE, go to Tools, Options, Content tab, Certificates, Import and follow the steps. Note that we called our root certificate cacert.pem. Rename this file to cacert.crt as it is an X.509 certificate. To make it easy for people to install your root certificate, cacert.crt, place it on your web site with a URL to it. When they click on it in most modern browsers, they can choose to "Open" or "Install" and it will walk them through the install steps.
Note for Mozilla and Firefox/Thuderbird users: Mozilla apps do not use MS Windows' certificate storage. If you use Mozilla, you only need to install the certificate in the browser and it will be available to the email client. If you use Firefox and Thunderbird, or just one of the two, you will need to manually import the certificate with Thunderbird. To do this, right-click the link to the certificate and choose "Save As...". Then in "Manage Certificates", access is via "Advanced" in newer versions, click the Import button.

7) Tips
Read up on revocation lists and how to use them. One day need to revoke the root certificate, or any other certificates, and issue a new one.
The root certificate created per the example only good for 365 days. When it expires people receive a warning message. Don't forget to remake the certificate each year, or create it for more than 1 year.
Don't forget your password for the root certificate, but do not let it fall into the wrong hands.

8) Creating certificates pages
Now you are ready to create certificates. Here are my pages covering various applications of certificates and how to make those certificates:

UNIX系统的引导过程

UNIX系统的引导过程

轉:http://www.cnitblog.com/tarius.wu/articles/227.html

1. 介绍

引导过程就是简单意义上的UNIX;通过它能看到各种不同的组件和设计模式(它们构成了UNIX的全部)。在这里我们打算看一下每次按下电源按钮之后OS是如何启动的。

2. 五部引导过程概述

可将引导(启动)过程划分为5个主要步骤:
1)        固件:硬件自识别(PROM/BIOS
2)        Bootloader(自举操作系统载入器)
3)        内核:初始化和控制转移
4)        Init和初始化脚本
5)        交给管理员:其他杂项

3. 固件硬件自识别

固件是一系列特殊的指令,介于硬件和软件层之间,负责这两者之间的相互传译。尽管听起来是一件微不足道的工作,但它实际的工作却举足轻重:固件传译的不仅是语言,它还要传译核心元素。硬件只理解电子脉冲;软件(在其最基本的级别)只理解10的数据流;固件是两者的一种合成,在硬件中永久地(或半永久地)嵌入了软件形式的例程。它代表了用户同计算机进行交互的最基本的级别(原始的硬件插拔动作除外)。

3.1 固件的一些实例

在同固件打交道的过程中,可能会遇到以下术语:
  •  BIOS
  •  PROM/EPPROM/EEPPROM
  •  NVRAM
  •  CMOS
它们的共同特点是:
  • 所有例程都是非易失性的芯片
  •  所有都采用与OS无关的技术。它们的设计和行动独立于其中存放的内容
  •  都具有类似的功能:维持基本的、简单的软件和驱动程序,以及它们相关的一些参数,还提供对键盘、显示器和硬盘的基本访问
  •  都采取了专门的设计,在出现磁盘/外设故障时,保证本身仍然可用,不会被破坏;即使不访问硬盘或移动式磁盘,它们存储的程序也能运行。
  • 所有都容纳着ROM bootstrap routine(启动程序,用来读入存放于MBR处的bootloader)程序

3.2 固件的工作

固件负责硬件的自查(查找硬件的存在),并让系统知道自己有哪些组件可用。这意味着,固件必须有相应的例程来检查连接的设备以及内建的控制器。但要注意的是:在内核装载恰当的驱动程序和模块之前,有的硬件仍是看不见的。
标识出设备之后,会对它们的基本功能进行测试,通常用它们的内嵌例程来进行。这些步骤统称为POST—开机自检(Power-On Self-Test)。
一旦确认并验证了系统的硬件,固件便开始搜索可引导设备要么一个内部列表,要么用一个环境变量。
找到一个可引导设备后,会检查它的引导扇区(或引导块),以获取更多的信息。这些信息会传给OS载入程序。

4. bootloader—载入OS

想象系统现在已被唤醒,并回答了第一个问题:“我是什么”,下一步是找到“大脑”,并回答:“我是谁?”。对于任何UNIX系统,基本身份信息都是封装在本地OS中的。
Bootloader这个术语是“bootstrap Operating System loader”的简称,即“自举操作系统的装入器”。
何谓“自举”
根据Merriam-Webster在线字典的说法,首次使用bootstrap(自举)这个术语是在1926年。它的来源确实是一个人试图通过拉自己的鞋带(bootstrap),从而将自己举高。这也是“自力更生”(pull yourself up by your bootstrap)这一谚语的来历。
通常,“自举”意味着利用尽可能少的外部介入和资源,从而让某种东西工作起来。对计算机来说,它的自举意味着使用少量的、独立的、内部的功能,从而初始化,并将控制权交给操作系统。

4.1 bootloader的工作

BootloaderUNIX实际启动你的系统的地方。在这里,你将启动一系列软件过程,最终将系统控制权从硬件交给OS
为了使bootloader能够保存到固件中,关键的一点是它尽可能小,它必须能装到磁盘的引导扇区。同时,这也意味着它必须相当简单,只需采用足够的逻辑,能够载入和运行OS引导序列的下一个程序即可。为了找到bootloader,要么对设备进行顺序搜索,要么检查由管理员提供的一个列表。注意bootloader的相关设置保存在系统固件中。
UNIX的世界中,会经常激将复杂的工作分解成小的,分散的任务,分别由小的,专用的程序来执行。这是UNIX十分重要的一个特点。

5 内核——初始化和控制转移

UNIX世界中,有两条恒定不变的真理。首先,UNIX中存在的任何东西都是一个文件;其次,UNIX中发生的任何事情都是一个进程。内核是一个编译好的可执行文件,在外观上同其他的可执行文件没什么区别。但只要在引导期间把它装入内存,它就开始控制其他所有系统进程和进程调度器——换句话说,它控制着发生的一切事情。
然而,“控制一切事情”并不意味着“做一切事情”——这是非常重要的区别。内核实际上能做的事情是非常有限的。
现在我们称内核是一个文件,但文件的位置却是不定的。事实上文件完全可能存在一个远程服务器上,根本不在你的系统中。但就实际用途来说,内核在哪里并不重要,只要能找到并载入它即可。
为简化讨论,我们认为在引导阶段最常见的下一个步骤是:载入存放在本地硬盘上的一个本地内核。

5.1 内核的工作

内核为UNIX系统中发生的其他一切事情提供了环境。它是一个基本框架,来自文件系统的任何其他命令都要布置其中。它构建了一个虚拟机,硬件和软件在此相会交互。内核负责处理所有系统资源的分配,包括内存、虚拟内存、CPU时间共享、交换和分页;它还负责系统内部的通信,不管是在软件进程之间还是在软件到硬件指令之间。为了对后者进行管理,内核可提供永久存储空间的访问渠道——包括硬盘及其文件。
文件(包括内核及其附属内容)保存在一种随机存储的介质上,由文件系统对其进行结构化和管理。文件系统必须逻辑地装入,以便系统能够进行文件存取。当然,和系统中的其他任何东西一样,最终都是由内核来控制文件系统。
这听起来似乎是一个先有鸡还是先有蛋的问题:你要先有文件系统才能装入内核;但除非你有一个内核,否则无法装入文件系统这里的诀窍在于,bootloader会在适当的时候介入,由它装入恰当的文件系统,以便OS能够正确地载入和接管。

6 init进程和初始化脚本

现在,内核已经装入。系统已经准备好回答这样一个问题:“现在做什么?”随后,要由init进程来回答这一问题。创建init进程是在引导过程中内核直接进行的最后一项干预。
Init启动时,它会执行各个默认启动例程,从而继续引导过程(包括检查和装入文件系统、启动守护进程等)。在一个正常运行的UNIX系统中,init是第一个可以存在的进程。正因为如此,它的进程ID肯定是1

6.1 init的工作

Init进程负责所有的核心系统功能的有序启动(和关闭),这些功能位于内核之外。为此,它必须有能力创建(或孵化spawn)大量进程。

6.2 init的机制和特点:inittab

当然,init仅仅孵化进程是不够的——它们还必须为进程赋予权限。这正是管理员真正能控制的第一个项目:取决于系统用什么“运行级别”(runlevel)或者说“运行模式”来工作。它们会表现出不同的行为。
inittab中,所有条目采取如下格式:
id: run-level : action : process
第一个字段id是条目的标志符。
第二个字段指运行级别。指该进程能在哪一个运行级别上被启动。一般UNIX的运行级别有0~9个级别,在run-level字段上看可以用多个运行级别的组合,如023
第三个字段描述了你想让系统做什么事情。一些比较常用的值有:
  •  Boot——只在系统引导时运行进程
  • Once——只有进入指定的运行级别后才运行进程
  •  Respawn——一旦进程终止,便重新启动进程
  • Wait——类似于once,但在继续前,会等待进程结束。
第四个字段指定了要运行的可执行程序的完整路径,以及应传递给它的任意参数(只能用空格分隔)。

UNIX环境编程 笔记--第三章 文件I/O

UNIX环境编程 笔记--第三章 文件I/O

第三章 文件I/O

轉: http://www.cnitblog.com/tarius.wu/articles/299.html

在一个操作系统上,最常见的操作是针对文件的,本章描述了在UNIX系统上对文件的操作方法及与文件相关的一些概念。
大多数UNIX文件I/O只需用到5个函数:openreadwritelseek 以及close(还有create函数功能以及在open上实现,并且可以更为方便地创建文件,故不推荐使用)。

3.1 文件描述符

对于内核而言,所有打开的文件都由文件描述符引用,即内核根据文件描述符来定位打开的文件。文件描述符是一个非负整数,当打开一个文件或新创建一个文件时,内核向进程返回一个文件描述符。按照惯例,UNIX将文件描述符0与标准输入相结合,文件描述符1与标准输出相结合,文件描述符2同标准出错相结合。在POSIX系统中,幻数012被换成符号常数STDIN_FILENO,STDOUT_FILENO,STDERRO_FILENO

3.2 操作函数

open
调用open函数可以打开或创建一个文件。由open返回的文件描述符数字一定是最小的文件描述符数字。可以由dup2函数指定在特定的文件描述符上打开文件。
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char*pathname, int oflag,.../*, mode_t mode * / ) ;
返回:若成功为文件描述符,若出错为- 1
create
也可以由create函数创建一个新文件。但一般不再使用。
Create的一个不足之处是create只以只读方式打开所创建的文件。
Close
close关闭一个打开的文件。
#include <unistd.h>
int close (int filedes)
关闭一个文件也同时释放该进程加在该文件上的锁有记录锁。当一个进程终止时,它所有打开的文件都由内核自动关闭。很多程序都用这一功能,而不使用close显示关闭打开的文件。

Lseek
每个打开的文件都有一个与之关联的“当前文件偏移量”。它是一个非负整数,用以度量文件从文件打开开始处的计算的偏移量。通常,文件读写都从当前文件偏移量处开始,并使位移量增加所读或写的字节数。按系统默认,当打开一个文件时,除非指定O_APPEND,否则该文件偏移量被指定为0
可以调用l s e e k显式地定位一个打开文件。
#include <sys/types.h>
#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence) ;
返回:若成功为新的文件位移,若出错为- 1
Read
read函数从一个打开的文件中读取数据。
#include <unistd.h>
ssize_t read(int filedes, void *buff, size_t nbytes) ;
返回:读到的字节数,若已到文件尾为0,若出错为- 1
Write
write函数向一个打开的文件中写数据。
#include <unistd.h>
ssize_t write(int filedes, const void * buff , size_t nbytes) ;
返回:若成功为已写的字节数,若出错为- 1

3.3 文件共享

Unix支持在不同进程间共享打开文件。每个进程都有一个记录项,该记录项记录着该进程的信息,当然要包括该进程所打开的文件,这由记录项中的文件描述符表维护。
file1.JPG
3.1  打开文件的内核数据结构
由上图可知,进程表项中有文件描述符表,每一个文件描述符指向所对应文件的文件表,文件表记录着文件状态标志、当前文件位移量,而文件的物理信息,如文件名、路径、所有者以及文件大小则由V节点指针指向一个v节点表确定;实际上,是i节点信息包含了文件的物理信息。
file2.JPG
3.2描述了两个进程各自打开同一个文件的情况。每个进程都有自己文件表项的理由是,这样可以维护各自的当前文件位移量。

3.4 原子操作

原子操作的意思是指这种操作是不可分的,否则将会带来不可预知的结果。在操作系统上,操作不可分的意思是指调度进程不应该在该操作未完成时进行进程切换。
如有两个进程AB,都对同一个文件进行添加操作。A先调用lseek函数将当前文件描述符定到1500字节处(当前文件尾),但未等A开始写,内核切换进程使进程B开始执行,Blseek1500字节处,并调用write函数写了100字节。这样文件的长度变为1600字节,内核更新v节点信息(注意v节点信息是AB共享的)。然后,内核又进行进程切换使进程A恢复执行,此时A调用write也写了100字节。这样的后果就是导致文件的实际内容都不是进程AB所期望的。
为了解决这一问题,办法就是将lseekwrite作为一个原子操作,不允许在这两个操作未完成时进行进程切换。Unix提供了这种原子操作,就是在打开文件时指定O_APPEND

UNIX SOCKET编程简介

UNIX SOCKET编程简介

轉:http://www.cnitblog.com/tarius.wu/articles/434.html

1 Layered Model of Networking

Socket 编程的层次模型如下图所示,
1.JPG
最上面是应用层,应用层下面的是 SOCKET API 层,再下面是传输层和网络层…… 实际上, Sockets API 层并不是一个真正定义的网络层次,但却很好的描述了 Socket 编程所处的位置和所扮演的角色。
TCP 状态转换图
2.JPG

2 .什么是 SOCKET

简单的, Socket 可以被定义描述为两个应用通信通道的端点。一个 Socket 端点可以用 Socket 地址来描述, Socket 地址结构由 IP 地址,端口和使用协议组成( TCP or UDP )。
3.JPG

3 Socket 地址结构

网络中以主机 IP 、端口以及使用的协议表明一个网络应用。 UNIX Socket 将它们组成一个结构,统称为 SOCKET 地址结构。

3.1  IPv4 对应的 SOCKET 地址结构

通常也称为“网际 SOCKET 地址结构”,以“ socketaddr_in ”命名,定义在头文件 <netinet/in.h> 中。
4.JPG
所涉及到的数据类型:
5.JPG

3.2  IPv6 对应的 SOCKET 地址结构

6.JPG

3.3   通用地址结构

 
当地址结构作为传递给任何一个 SOCKET 函数的参数时,任意一个 Socket 函数必须能处理每一种地址结构,为了解决这个问题,提出了使用通用套接口地址结构。当任何一个地址结构要作为指针参数传到套接口函数时,必须要显示转化为通用地址结构。
IPv4 应用中,使用的通用地址结构为 struct sockaddr, 定义在 <sys/socket.h> 中,
7.JPG
例如:
8.JPG
IPv6 中,提出了一种新的通用地址结构: struct sockaddr_storage 定义在<netinet/in.h>中。
9.JPG
struct sockaddr struct sockaddr_storage 的区别
<!--[if !supportLists]-->a.<!--[endif]-->struct sockaddr_storage能提供严格的结构对齐
<!--[if !supportLists]-->b.  <!--[endif]-->struct sockaddr_storage能容纳系统支持的更大的地址结构

3.4  Socket 地址结构的比较

a.JPG

4 Client/Server Model

Client/Server 的网络通信模型是使用最广最为基础的通信模型。在 Socket API 环境,其应用可以归纳为:
b.JPG
TCP 链接建立
根据 TCP/IP 协议,函数 connect() 激发 TCP 的三路握手过程。
客户端给服务器一个 SYN(J) 信号,服务器返回一个 ACK(J+1); 然后服务器给客户端一个 SYN(K), 客户端也同样给服务器一个 ACK(K+1) ,这样就算 connect 成功。
注意:服务器返回的 SYN(K) ACK(J+1) 是放在同一个分节中发给客户端的,因此链路在建立连接时只用了三个分节,故称为三路握手。
c.JPG
TCP 链接终止
TCP 用三个分节建立一个链接,而终止连接时需要四个分节。原因在于被动关闭链接一方需要关闭处理时间,因此 ACK FIN 不能同时发给主动关闭一方。
d.JPG
主动关闭一方在收到 ACK 后,在等待被动关闭方发 FIN 消息这一状态称为处于 TIME_WAIT 状态。

5 Socket API 描述

5.1 创建套接字—— socket 函数

应用程序在使用套接字前,必须拥有一个套接字,系统调用 socket ()向应用程序提供
创建套接字的手段,其调用格式如下:
e.JPG
参数 family 指定通信发生的区域, UNIX 系统支持的地址族有: AF_UNIX,AF_INET,AF_NS 等,而 DOS WINDOWS 中仅支持 AF_INET ,它是网际网区域。因此,地址族与协议族相同。
参数 type 描述要建立的套接字的类型。参数 protocol 说明该套接字使用的特定协议,如果调用者不希望特别指定使用的协议,则置为 0 ,使用默认的连接模式。根据这 3 个参数建立一个套接字,并将相应的资源分配给它,同时返回一个整型套接字号。因此, socket ()系统调用实际上指定了 " 协议 "
f.JPG

5.2 指定本地址—— bind 函数

一个套接字用 socket ()创建后,存在一个名字空间(地址族),但它没有被命名。 Bind ()将套接字地址(包括本地主机地址和本断口地址)与所创建的套接字号联系起来,即将,名字赋予套接字,以指定本地半相关。其调用格式如下:
g.JPG
参数 socketfd 是由 socket() 调用返回的并且未作连接的套接字描述符(套接字号)。参数 myaddr 是赋给套接字 socketfd 的本地地址(名字),其长度可变,结构随通信域的不同而不同。 addrlen 表明了 name 的长度。
进程可以把一个特定的 IP 地址捆绑到它的套接口上,但此 IP 地址必须是主机的一个接口。对于 TCP 客户端,这就为在此套接口上发送的 IP 数据包分配了源 IP 地址。对于服务器端,这样做就限制了套接口只接收来自那些目的地址为此 IP 地址的客户链接。
对于 TCP ,调用函数 bind 可以指定一个端口号,指定一个 IP 地址,可以两者都指定,也可以一个都不指定。对于服务器,一般指定众所周知的端口号。而不指定具体的 IP 地址。如果 TCP 服务器不把地址绑到套接口上,那么内核就把客户所发 SYN 所在分组的目的 IP 地址作为服务器的源 IP 地址。
若指定端口号为 0 ,则调用函数 bind 时,内核选择一个临时端口;但若指定一个通配 IP 地址,则直到套接口以连接 (TCP) 或数据报已在套接口上发出 (UDP), 内核才选择一个本地 IP
注意到在第四部分图中,客户端并没有调用 bind 函数,因为对于客户端来说,没有必要指定具体的 IP 和端口,如果需要,内核会选择一个源 IP 地址和一个临时的端口。
h.JPG

5.3 connect 函数

TCP 客户用 connect() 函数建立一个与 TCP 服务器的链接。 Connect ()的调用格式如下:
i.JPG
参数 socket 是欲建立连接的本地套接字描述符。参数 servaddr 指出说明对方套接字地址结构指针。对方套接字地址长度由 addrlen 说明。
根据 TCP 的状态转移图,函数 connect 导致从 CLOSED 状态 ( 调用函数 socket 创建套接口以后就一直处于此状态 ) 转到 SYN_SENT 状态,若成功转到 ESTABLISHED 状态。如果函数 connect 失败,则原先创建的套接口就不可再用,必须关闭。如要重新尝试,必须重新调用 socket ,再 connect

5.4 listen 函数

函数 listen 仅被 TCP 服务器调用,它做两件事情:
<!--[if !supportLists]-->1.          <!--[endif]-->当函数socket创建一个套接口时,它被假设为一个主动套接口,也就是说,它是一个将调用connect发起链接的客户套接口。函数listen将未链接的套接口转换成被动套接口,告诉内核应接受指向此套接口的链接请求。根据TCP状态转移图,函数listen导致套接口状态从CLOSED转到LISTEN
<!--[if !supportLists]-->2.          <!--[endif]-->函数的第二个参数规定了内核为此套接口排队的最大链接个数。
j.JPG
一般而言,此函数应该在 socket bind 之后,调用函数 accept 之前调用。
参数 backlog 的解析:
对于给定的监听套接口 (listen 之后 ) ,内核要维护两个队列:一个是处于三次握手过程中(即客户端调用 connect ,服务器端收到 SYN ,并返回 ACK SYN 后,在等待客户端的 ACK ),处于 SYN_RCVD 状态;另一个是从 connect 成功,处于 ESTABLISHED 状态。
两个队列之和不能超过 backlog
k.JPG

5.5 accept 函数

Accept 函数由 TCP 服务器调用,从已完成连接队列头返回一个已完成的链接。若已完成队列为空,则进程睡眠(假定套接口为缺省的阻塞方式)。用于使服务器等待来自某客户进程的实际连接。
l.JPG
参数 cliaddr addrlen 用来返回链接对方进程(客户端)的协议地址。
若函数成功返回,返回值是一个由内核自动生成的全新的描述字,代表与客户端的链接。当我们讨论函数 accept 时,常把它的第一个参数称为监听套接口描述字(由 socket 生成的描述字);把它的返回值称为已连接套接口描述字。

6 Reference

补上参考书目及一些好文章

1. Windows Sockets 网络程序设计大全
    适合于WinSock编程入门。
2. Unix Networking Programming 3rd ed 
    很经典的书呀,如果你学习Unix Socket编程,你必须买的,学习WinSock也有作用的,因为WinSock是参考的Unix Socket。
3. Windows网络编程技术     讲了Win2000下的WinSock编程,讲了TCP/IP, Netbios, IPX/SPX, ATM等在Windows下的编程,有很多代码,还有一个MS Platform SDK的东西,这可是很难得的。对了,还有QoS和Raw Socket编程的程序例子