2009年12月1日星期二

Linux下配置ip地址

即时生效:
ifconfig eth0 192.168.1.102 netmask 255.255.255.0
启动生效:
vim /etc/sysconfig/network-scripts/ifcfg-eth0
加入
IPADDR=192.168.1.102
NETMASK=255.255.255.0

修改default gateway
即时生效:
route add default gw 192.168.1.1
启动生效:
vim /etc/sysconfig/network-scripts/ifcfg-eth0
加入
GATEWAY=192.168.1.1
最后结果如下:
DEVICE=eth0
BOOTPROTO=static
IPADDR=192.168.1.102
NETMASK=255.255.255.0
GETEWAY=192.168.1.1
ONBOOT=yes
TYPE=Ethernet
注:BOOTPROTO只有在static(静态)模式下才可以使用设置的IP信息
修改dns
vim /etc/resolv.conf
修改后可即时生效,启动同样有效

形式
加入nameserver= ***.***.***.***
最多可有三个,作为上一个失败后的候选

修改host name
即时生效:
hostname fc2
启动生效:
vim /etc/sysconfig/network
形式如下:
NETWORKING=yes
HOSTNAME=rh-linux.fc.org
##########################################################
首先,先了解传统的网络配置命令:
1. 使用ifconfig命令配置并查看网络接口情况
示例1: 配置eth0的IP,同时激活设备:
ifconfig eth0 192.168.4.1 netmask 255.255.255.0 up
示例2: 配置eth0别名设备 eth0:1 的IP,并添加路由
ifconfig eth0:1 192.168.4.2
route add –host 192.168.4.2 dev eth0:1
示例3:激活(禁用)设备
ifconfig eth0:1 up(down)
示例4:查看所有(指定)网络接口配置
ifconfig (eth0)
—————————
2. 使用route 命令配置路由表
示例1:添加到主机路由
route add –host 192.168.4.2 dev eth0:1
route add –host 192.168.4.1 gw 192.168.4.250
示例2:添加到网络的路由
route add –net IP netmask MASK eth0
route add –net IP netmask MASK gw IP
route add –net IP/24 eth1
示例3:添加默认网关
route add default gw IP
示例4:删除路由
route del –host 192.168.4.1 dev eth0:1
示例5:查看路由信息
route 或  route -n  (-n 表示不解析名字,列出速度会比route 快)
—————————
3.ARP 管理命令
示例1:查看ARP缓存
arp
示例2: 添加
arp –s IP MAC
示例3: 删除
arp –d IP
—————————
4. ip是iproute2软件包里面的一个强大的网络配置工具,它能够替代一些传统的网络管理工具。例如:ifconfig、route等,
上面的示例完全可以用下面的ip命令实现,而且ip命令可以实现更多的功能.下面介绍一些示例:

4.0 ip命令的语法
ip [OPTIONS] OBJECT [COMMAND [ARGUMENTS]]

4.1 ip link set–改变设备的属性. 缩写:set、s

示例1:up/down 起动/关闭设备。
ip link set dev eth0 up
这个等于传统的
ifconfig eth0 up
ifconfig eth0 down
示例2:改变设备传输队列的长度。
参数:txqueuelen NUMBER或者txqlen NUMBER
ip link set dev eth0 txqueuelen 100
示例3:改变网络设备MTU(最大传输单元)的值。
ip link set dev eth0 mtu 1500
示例4: 修改网络设备的MAC地址。
参数: address LLADDRESS
ip link set dev eth0 address 00:01:4f:00:15:f1

4.2 ip link show–显示设备属性.  缩写:show、list、lst、sh、ls、l
-s选项出现两次或者更多次,ip会输出更为详细的错误信息统计。
示例:
ip -s -s link ls eth0
这个命令等于传统的
ifconfig eth0
—————————
5.1 ip address add–添加一个新的协议地址. 缩写:add、a
示例1:为每个地址设置一个字符串作为标签。为了和Linux-2.0的网络别名兼容,这个字符串必须以设备名开头,接着一个冒号,
ip addr add local 192.168.4.1/28 brd + label eth0:1 dev eth0
等价于
ifconfig eth0:5 192.168.4.1/28
ip addr add local 192.168.4.1/28 dev eth0
示例2:
在以太网接口eth0上增加一个地址192.168.20.0,掩码长度为24位(155.155.155.0),标准广播地址,标签为eth0:Alias:
ip addr add 192.168.4.2/24 brd + dev eth1 label eth1:1
这个命令等于传统的: 
ifconfig eth1:1 192.168.4.2/24

5.2 ip address delete–删除一个协议地址. 缩写:delete、del、d
ip addr del 192.168.4.1/24 brd + dev eth0 label eth0:Alias1

5.3 ip address show–显示协议地址. 缩写:show、list、lst、sh、ls、l
ip addr ls eth0

5.4.ip address flush–清除协议地址. 缩写:flush、f
示例1 : 删除属于私网10.0.0.0/8的所有地址:
ip -s -s a f to 10/8
示例2 : 取消所有以太网卡的IP地址
ip -4 addr flush label "eth0″
—————————
6. ip neighbour–neighbour/arp表管理命令
缩写 neighbour、neighbor、neigh、n
命令 add、change、replace、delete、fulsh、show(或者list)

6.1 ip neighbour add — 添加一个新的邻接条目
ip neighbour change–修改一个现有的条目
ip neighbour replace–替换一个已有的条目
缩写:add、a;change、chg;replace、repl
示例1: 在设备eth0上,为地址10.0.0.3添加一个permanent ARP条目:
ip neigh add 10.0.0.3 lladdr 0:0:0:0:0:1 dev eth0 nud perm
示例2:把状态改为reachable
ip neigh chg 10.0.0.3 dev eth0 nud reachable

6.2.ip neighbour delete–删除一个邻接条目
示例1:删除设备eth0上的一个ARP条目10.0.0.3
ip neigh del 10.0.0.3 dev eth0
6.3.ip neighbour show–显示网络邻居的信息. 缩写:show、list、sh、ls
示例1: ip -s n ls 193.233.7.254
193.233.7.254. dev eth0 lladdr 00:00:0c:76:3f:85 ref 5 used 12/13/20 nud
reachable
6.4.ip neighbour flush–清除邻接条目. 缩写:flush、f
示例1: (-s 可以显示详细信息)
ip -s -s n f 193.233.7.254
—————————
7. 路由表管理
7.1.缩写 route、ro、r
7.5.路由表
从Linux-2.2开始,内核把路由归纳到许多路由表中,这些表都进行了编号,编号数字的范围是1到255。另外,
为了方便,还可以在/etc/iproute2/rt_tables中为路由表命名。
默认情况下,所有的路由都会被插入到表main(编号254)中。在进行路由查询时,内核只使用路由表main。

7.6.ip route add — 添加新路由
ip route change — 修改路由
ip route replace — 替换已有的路由
缩写:add、a;change、chg;replace、repl
示例1: 设置到网络10.0.0/24的路由经过网关193.233.7.65
ip route add 10.0.0/24 via 193.233.7.65

示例2: 修改到网络10.0.0/24的直接路由,使其经过设备dummy
ip route chg 10.0.0/24 dev dummy

示例3:
实现链路负载平衡.加入缺省多路径路由,让ppp0和ppp1分担负载(注意:scope值并非必需,它只不过是告诉内核,这个路由要经过网关而不是直连的。实际上,如果你知道远程端点的地址,使用via参数来设置就更好了)。
ip route add default scope global nexthop dev ppp0 nexthop dev ppp1
ip route replace default scope global nexthop dev ppp0 nexthop dev ppp1
示例4:
设置NAT路由。在转发来自192.203.80.144的数据包之前,先进行网络地址转换,把这个地址转换为193.233.7.83
ip route add nat 192.203.80.142 via 193.233.7.83

示例5: 实现数据包级负载平衡,允许把数据包随机从多个路由发出。weight
可以设置权重.
ip route replace default equalize nexthop via 211.139.218.145 dev eth0
weight 1 nexthop via 211.139.218.145 dev eth1 weight 1

7.7.ip route delete– 删除路由
缩写:delete、del、d
示例1:删除上一节命令加入的多路径路由
ip route del default scope global nexthop dev ppp0 nexthop dev ppp1

7.8.ip route show — 列出路由
缩写:show、list、sh、ls、l

示例1: 计算使用gated/bgp协议的路由个数
ip route ls proto gated/bgp |wc
1413  9891  79010

示例2:
计算路由缓存里面的条数,由于被缓存路由的属性可能大于一行,以此需要使用-o选项
ip -o route ls cloned |wc
159  2543  18707
示例3: 列出路由表TABLEID里面的路由。缺省设置是table
main。TABLEID或者是一个真正的路由表ID或者是/etc/iproute2/rt_tables文件定义的字符串,
或者是以下的特殊值:
all — 列出所有表的路由;
cache — 列出路由缓存的内容。
ip ro ls 193.233.7.82 tab cache
示例4: 列出某个路由表的内容
ip route ls table fddi153

示例5: 列出默认路由表的内容
ip route ls
这个命令等于传统的: route

7.9.ip route flush — 擦除路由表
示例1: 删除路由表main中的所有网关路由(示例:在路由监控程序挂掉之后):
ip -4 ro flush scope global type unicast
示例2:清除所有被克隆出来的IPv6路由:
ip -6 -s -s ro flush cache
示例3: 在gated程序挂掉之后,清除所有的BGP路由:
ip -s ro f proto gated/bgp
示例4: 清除所有ipv4路由cache
ip route flush cache
*** IPv4 routing cache is flushed.

7.10 ip route get — 获得单个路由 .缩写:get、g
使用这个命令可以获得到达目的地址的一个路由以及它的确切内容。
ip route get命令和ip route show命令执行的操作是不同的。ip route
show命令只是显示现有的路由,而ip route get命令在必要时会派生出新的路由。
示例1: 搜索到193.233.7.82的路由
ip route get 193.233.7.82
193.233.7.82 dev eth0 src 193.233.7.65 realms inr.ac cache mtu 1500 rtt
300
示例2:
搜索目的地址是193.233.7.82,来自193.233.7.82,从eth0设备到达的路由(这条命令会产生一条非常有意思的路由,这是一条到193.233.7.82的回环路由)
ip r g 193.233.7.82 from 193.233.7.82 iif eth0
193.233.7.82 from 193.233.7.82 dev eth0 src 193.233.7.65 realms
inr.ac/inr.ac
cache

示例1: 通过路由表inr.ruhep路由来自源地址为192.203.80/24的数据包
ip ru add from 192.203.80/24 table inr.ruhep prio 220

示例2:把源地址为193.233.7.83的数据报的源地址转换为192.203.80.144,并通过表1进行路由
ip ru add from 193.233.7.83 nat 192.203.80.144 table 1 prio 320

示例3:删除无用的缺省规则
ip ru del prio 32767

8.7. ip rule show — 列出路由规则
缩写:show、list、sh、ls、l
示例1: 
ip ru ls
0:   from all lookup local
32762: from 192.168.4.89 lookup fddi153
32764: from 192.168.4.88 lookup fddi153
32766: from all lookup main
32767: from all lookup 253
—————————
9. ip maddress — 多播地址管理
缩写:show、list、sh、ls、l
9.3.ip maddress show — 列出多播地址
示例1:
ip maddr ls dummy

9.4. ip maddress add — 加入多播地址
ip maddress delete — 删除多播地址
缩写:add、a;delete、del、d
使用这两个命令,我们可以添加/删除在网络接口上监听的链路层多播地址。这个命令只能管理链路层地址。

示例1: 增加    ip maddr add 33:33:00:00:00:01 dev dummy
示例2: 查看   
ip -O maddr ls dummy
2: dummy
link 33:33:00:00:00:01 users 2 static
link 01:00:5e:00:00:01
示例3: 删除  
ip maddr del 33:33:00:00:00:01 dev dummy
—————————
10.ip mroute — 多播路由缓存管理

10.4. ip mroute show — 列出多播路由缓存条目
缩写:show、list、sh、ls、l

示例1:查看  ip mroute ls
(193.232.127.6, 224.0.1.39)   Iif: unresolved
(193.232.244.34, 224.0.1.40)   Iif: unresolved
(193.233.7.65, 224.66.66.66)   Iif: eth0    Oifs: pimreg
示例2:查看  ip -s mr ls 224.66/16
(193.233.7.65, 224.66.66.66)   Iif: eth0    Oifs: pimreg
9383 packets, 300256 bytes
—————————
11. ip tunnel — 通道配置
缩写 tunnel、tunl

11.4.ip tunnel add — 添加新的通道
ip tunnel change — 修改现有的通道
ip tunnel delete — 删除一个通道
缩写:add、a;change、chg;delete、del、d
示例1:建立一个点对点通道,最大TTL是32
ip tunnel add Cisco mode sit remote 192.31.7.104 local 192.203.80.1 ttl 32

11.4.ip tunnel show — 列出现有的通道
缩写:show、list、sh、ls、l
示例1:  ip -s tunl ls Cisco
—————————
12. ip monitor和rtmon — 状态监视
ip命令可以用于连续地监视设备、地址和路由的状态。这个命令选项的格式有点不同,命令选项的名字叫做monitor,接着是操作对象:
ip monitor [ file FILE ] [ all | OBJECT-LIST ]
示例1: 
rtmon file /var/log/rtmon.log
示例2: 
ip monitor file /var/log/rtmon.log r

Linux库文件的搜索路径

  linux有很多值得学习的地方,这里我们主要介绍linux库文件,经过搜集和整理linux库文件材料,在这里拿出来和大家分享一下,希望大家看后会有不少收获。
  linux库文件在连接(静态库和共享库)和运行(仅限于使用共享库的程序)时被使用,其搜索路径是在系统中进行设置的。
  一般 Linux 系统把 /lib 和 /usr/lib
两个目录作为默认的库搜索路径,所以使用这两个目录中的库时不需要进行设置搜索路径即可直接使用。对于处于默认库搜索路径之外的库,需要将库的位置添加到库的搜索路径之中。
  设置linux库文件的搜索路径有下列两种方式,可任选其一使用:
  在环境变量 LD_LIBRARY_PATH 中指明库的搜索路径。
  在 /etc/ld.so.conf 文件中添加库的搜索路径。
  将自己可能存放库文件的路径都加入到/etc/ld.so.conf中是明智的选择
  添加方法也极其简单,将linux库文件的绝对路径直接写进去就OK了,一行一个。
  例如:
  /usr/X11R6/lib
  /usr/local/lib
  /opt/lib
  需要注意的是:第二种搜索路径的设置方式对于程序连接时的库(包括共享库和静态库)的定位已经足够了,但是对于使用了共享库的程序的执行还是不
够的。这是因为为了加快程序执行时对共享库的定位速度,避免使用搜索路径查找共享库的低效率,所以是直接读取库列表文件
/etc/ld.so.cache 从中进行搜索的。/
  etc/ld.so.cache 是一个非文本的数据文件,不能直接编辑,它是根据
/etc/ld.so.conf 中设置的搜索路径由 /sbin/ldconfig
命令将这些搜索路径下的共享库文件集中在一起而生成的(ldconfig 命令要以 root
权限执行)。因此,为了保证程序执行时对库的定位,在 /etc/ld.so.conf
中进行了库搜索路径的设置之后,还必须要运行 /sbin/ldconfig 命令更新
/etc/ld.so.cache 文件之后才可以。
  ldconfig
,简单的说,它的作用就是将/etc/ld.so.conf列出的路径下的linux库文件缓存到/etc/ld.so.cache
以供使用。因此当安装完一些库文件,(例如刚安装好glib),或者修改ld.so.conf增加新的库路径后,需要运行一下
/sbin/ldconfig使所有的库文件都被缓存到ld.so.cache中,如果没做,即使库文件明明就在/usr/lib下的,也是不会被使用
的,结果编译过程中抱错,缺少xxx库,去查看发现明明就在那放着,搞的想大骂computer蠢猪一个。
  在程序连接时,对于linux库文件(静态库和共享库)的搜索路径,除了上面的设置方式之外,还可以通过
-L 参数显式指定。因为用 -L
设置的路径将被优先搜索,所以在连接的时候通常都会以这种方式直接指定要连接的库的路径。
  前面已经说明过了,库搜索路径的设置有两种方式:在环境变量
LD_LIBRARY_PATH 中设置以及在 /etc/ld.so.conf
文件中设置。其中,第二种设置方式需要 root 权限,以改变 /etc/ld.so.conf
文件并执行 /sbin/ldconfig 命令。而且,当系统重新启动后,所有的基于 GTK2
的程序在运行时都将使用新安装的 GTK+ 库。不幸的是,由于 GTK+
版本的改变,这有时会给应用程序带来兼容性的问题,造成某些程序运行不正常。为了避免出现上面的这些情况,在
GTK+
及其依赖库的安装过程中对于linux库的搜索路径的设置将采用第一种方式进行。
  这种设置方式不需要 root 权限,设置也简单:
  $ export LD_LIBRARY_PATH=/opt/gtk/lib:$LD_LIBRARY_PATH
  可以用下面的命令查看 LD_LIBRAY_PATH 的设置内容:
  $ echo $LD_LIBRARY_PATH
  至此,库的两种设置就完成了。

内存开始1M地址的介绍

存储器(仅指内存)分为rom和ram,在物理上是独立的器件,cpu操作他们时,都做为内存来处理,把他们总的看作一个若干存储单元构成的逻辑存储器,这个逻辑存储器就是我们所说的内存地址空间
8086的内存地址空间分布如下:
0x00000-0x9ffff 为主存(ram)
0xa0000-0xbffff为显存地址空间
0xc0000-0xfffff为各类rom地址空间
bios(各个部件的,包括主板,显卡,网卡等)使用rom存储,所以存储在第三段地址空间中,cpu上电后会把cs=0xffff,ip=0,此地址在rom中,ffff:0处有一跳转指令,cpu执行后,转去执行bios中的硬件系统检测和初始化程序,初始化程序会建立中断向量表,仅把rom中中断处理程序的入口地址登记到中断向量表中,硬件系统检测和初始化完成后,则会调用int
19h进行操作系统的启动
中断向量表不是内存的前一兆空间,而是1k。
rom中的数据不会被加载或拷贝到主存(ram)中的,会直接寻址,因为他处在内存地址空间中,rom不是内存的说法好像是不正确的,内存是指内部存储器,与硬盘和磁盘等外部存储器相对而言,内存应该和ram不等同
如果你的ram超过1m的大小,实模式下,ram应该不会被完全使用,因为没有多与的逻辑地址空间供他们使用

linux用户空间和内核空间之细说

从以上几讲我们知道,Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此,Linux的虚拟地址空间也为0~4G。Linux内核将这4G字节的空间分为两部分。将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为"内核空间"。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为"用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
Linux使用两级保护机制:0级供内核使用,3级供用户程序使用。从图中可以看出(这里无法表示图),每个进程有各自的私有用户空间(0~3G),这个空间对系统中的其他进程是不可见的。最高的1GB字节虚拟内核空间则为所有进程以及内核所共享。
1.虚拟内核空间到物理空间的映射
内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。不管是内核空间还是用户空间,它们都处于虚拟空间中。读者会问,系统启动时,内核的代码和数据不是被装入到物理内存吗?它们为什么也处于虚拟内存中呢?这和编译程序有关,后面我们通过具体讨论就会明白这一点。
虽然内核空间占据了每个虚拟空间中的最高1GB字节,但映射到物理内存却总是从最低地址(0x00000000)开始。对内核空间来说,其地址映射是很简单的线性映射,0xC0000000就是物理地址与线性地址之间的位移量,在Linux代码中就叫做PAGE_OFFSET。


我们来看一下在include/asm/i386/page.h中对内核空间中地址映射的说明及定义:
/*
* This handles the memory map.. We could make this a config
* option, but too many people screw it up, and too few need
* it.
*
* A __PAGE_OFFSET of 0xC0000000 means that the kernel has
* a virtual address space of one gigabyte, which limits the
* amount of physical memory you can use to about 950MB.
*
* If you want more physical memory than this then see the CONFIG_HIGHMEM4G
* and CONFIG_HIGHMEM64G options in the kernel configuration.
*/

#define __PAGE_OFFSET (0xC0000000)
……
#define PAGE_OFFSET ((unsigned long)__PAGE_OFFSET)
#define __pa(x) ((unsigned long)(x)-PAGE_OFFSET)
#define __va(x) ((void *)((unsigned long)(x)+PAGE_OFFSET))
源代码的注释中说明,如果你的物理内存大于950MB,那么在编译内核时就需要加CONFIG_HIGHMEM4G和CONFIG_HIGHMEM64G选项,这种情况我们暂不考虑。如果物理内存小于950MB,则对于内核空间而言,给定一个虚地址x,其物理地址为"x-
PAGE_OFFSET",给定一个物理地址x,其虚地址为"x+ PAGE_OFFSET"。
这里再次说明,宏__pa()仅仅把一个内核空间的虚地址映射到物理地址,而决不适用于用户空间,用户空间的地址映射要复杂得多。
2.内核映像
在下面的描述中,我们把内核的代码和数据就叫内核映像(kernel
image)。当系统启动时,Linux内核映像被安装在物理地址0x00100000开始的地方,即1MB开始的区间(第1M留作它用)。然而,在正常运行时,
整个内核映像应该在虚拟内核空间中,因此,连接程序在连接内核映像时,在所有的符号地址上加一个偏移量PAGE_OFFSET,这样,内核映像在内核空间的起始地址就为0xC0100000。
例如,进程的页目录PGD(属于内核数据结构)就处于内核空间中。在进程切换时,要将寄存器CR3设置成指向新进程的页目录PGD,而该目录的起始地址在内核空间中是虚地址,但CR3所需要的是物理地址,这时候就要用__pa()进行地址转换。在mm_context.h中就有这么一行语句:
asm volatile("movl %0,%%cr3": :"r" (__pa(next->pgd));
这是一行嵌入式汇编代码,其含义是将下一个进程的页目录起始地址next_pgd,通过__pa()转换成物理地址,存放在某个寄存器中,然后用mov指令将其写入CR3寄存器中。经过这行语句的处理,CR3就指向新进程next的页目录表PGD了。

Linux使用笔记: 在Linux中使能IP转发

Linux系统缺省并没有打开IP转发功能,要确认IP转发功能的状态,可以查看/proc文件系统,使用下面命令:
cat /proc/sys/net/ipv4/ip_forward

如果上述文件中的值为0,说明禁止进行IP转发;如果是1,则说明IP转发功能已经打开。

要想打开IP转发功能,可以直接修改上述文件:
echo 1 > /proc/sys/net/ipv4/ip_forward

把文件的内容由0修改为1。禁用IP转发则把1改为0。

上面的命令并没有保存对IP转发配置的更改,下次系统启动时仍会使用原来的值,要想永久修改IP转发,需要修改/etc/sysctl.conf文件,修改下面一行的值:
net.ipv4.ip_forward = 1

修改后可以重启系统来使修改生效,也可以执行下面的命令来使修改生效:
sysctl -p /etc/sysctl.conf

进行了上面的配置后,IP转发功能就永久使能了

Big endian and Little endian

谈到字节排序的问题,必然牵涉到两大CPU派系。那就是Motorola的PowerPC系列CPU和Intel的x86系列
CPU。PowerPC系列采用big endian方式存储数据,而x86系列则采用little
endian方式存储数据。ARM同时支持 big和little,实际应用中通常使用little
endian。那么究竟什么是big endian,什么又是little endian呢?

其实big endian是指低地址存放最高有效字节(MSB),而little
endian则是低地址存放最低有效字节(LSB)。用文字说明可能比较抽象,下面用图像加以说明。比如数字0x12345678在两种不同字节序CPU中的存储顺序如下所示:

Big Endian
一个Word中的高位的Byte放在内存中这个Word区域的低地址处

低地址 高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 12 | 34 | 56 | 78 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

Little Endian
一个Word中的低位的Byte放在内存中这个Word区域的低地址处

低地址 高地址
----------------------------------------->
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 78 | 56 | 34 | 12 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

从上面两图可以看出,采用big
endian方式存储数据是符合我们人类的思维习惯的。必须注意的是:一个Word的长度是16位,一个Byte的长度是8位。如果一个数超过一个
Word的长度,必须先按Word分成若干部分,然后每一部分(即每个Word内部)按Big-Endian或者Little-Endian的不同操作来
处理字节。
一个例子:
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
big-endian little-endian
0x0000 0x12 0xcd
0x0001 0x34 0xab
0x0002 0xab 0x34
0x0003 0xcd 0x12

需要特别说明的是,以上假设机器是每个内存单元以8位即一个字节为单位的.简单的说,little
endian把低位存放到高位,而big endian把低位存放到低位.
现在主流的CPU,intel系列的是采用的little
endian的格式存放数据,而motorola系列的CPU采用的是big endian.
以下是判断字节存储顺序的可移植的C语言代码:
/*可移植的用于判断存储格式是
little endian还是big ednian的C代码*/
#include < stdio.h >
union
{
long Long;
char Char[ sizeof ( long )];
} u;

int main()
{
u.Long = 1 ;
if (u.Char[ 0 ] == 1 )
{
printf( " Little Endian!\n " );
}
else if (u.Char[ sizeof ( long ) - 1 ] == 1 )
{
printf( " Big Endian!\n " );
}
else
{
printf( " Unknown Addressing!\n " );
}

printf( " Now, Let's look at every byte in the memory!\n " );
for ( int i = 0 ; i < sizeof ( long ); ++ i)
{
printf( " [%x] = %x\n " , & u.Char[i], u.Char[i]);
}
return 0 ;
}
在 网络编程中,TCP/IP统一采用big
endian方式传送数据,也就是说,假设现在是在一个字节顺序是little
endian的机器上传送数据,要求传送的数据是0XCEFABOBO,那么你就要以0XBOBOFACE的顺序在unsigned
int中存放这个数据,只有这样才能保证存放的顺序满足TCP/IP的字节顺序要求.很多时候,需要自己编写应用层的协议,字节顺序的概念在这个时候就显
得及其的重要了.
下面给出的是在big endian和little
endian中相互转换的代码,C语言强大的位操作的能力在这里显示了出来:
/*在little endian和big ednian之间相互转化数据的演示代码*/
#include < stdio.h >
const unsigned char SIZE_OF_UNSIGNEDINT = sizeof (unsigned int );
const unsigned char SIZE_OF_UNSIGNEDCHAR = sizeof (unsigned char );

void put_32(unsigned char * cmd, unsigned int data)
{
int i;
for (i = SIZE_OF_UNSIGNEDINT - 1 ; i >= 0 ; -- i)
{
cmd[i] = data % 256 ;
// 或者可以:
// cmd[i] = data & 0xFF;
data = data >> 8 ;
}
}

unsigned int get_32(unsigned char * cmd)
{
unsigned int ret;
int i;
for (ret = 0 , i = SIZE_OF_UNSIGNEDINT - 1 ; i >= 0 ; -- i)
{
ret = ret << 8 ;
ret |= cmd[i];
}
return ret;
}

int main( void )
{
unsigned char cmd[SIZE_OF_UNSIGNEDINT];
unsigned int data, ret;
unsigned char * p;
int i;
data = 0x12345678 ;
printf( " data = %x\n " , data);
// 以字节为单位打印出数据
p = (unsigned char * )( & data);
for(i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
{
printf( " %x " , * p ++ );
}
printf( " \n " );
//以相反的顺序存放到cmd之中
put_32(cmd, data);
for (i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
{
printf( " cmd[%d] = %x\n " , i, cmd[i]);
}
// 再以相反的顺序保存数据到ret中
// 保存之后的ret数值应该与data相同
ret = get_32(cmd);
printf( " ret = %x\n " , ret);
p = (unsigned char * )( & ret);
for(i = 0 ; i < SIZE_OF_UNSIGNEDINT; ++ i)
{
printf( " %x " , * p ++ );
}
printf( " \n " );
return 0 ;
}

为什么要注意字节序的问题呢?你可能这么问。当然,如果你写的程序只在单机环境下面运行,并且不和别人的程序打交道,那么你完全可以忽略字节序的存在。但
是,如果你的程序要跟别人的程序产生交互呢?在这里我想说说两种语言。C/C++语言编写的程序里数据存储顺序是跟编译平台所在的CPU相关的,而
JAVA编写的程序则唯一采用big
endian方式来存储数据。试想,如果你用C/C++语言在x86平台下编写的程序跟别人的JAVA程序互通时会产生什么结果?就拿上面的
0x12345678来说,你的程序传递给别人的一个数据,将指向0x12345678的指针传给了JAVA程序,由于JAVA采取big
endian方式存储数据,很自然的它会将你的数据翻译为0x78563412。什么?竟然变成另外一个数字了?是的,就是这种后果。因此,在你的C程序
传给JAVA程序之前有必要进行字节序的转换工作。

所有网络协议也都是采用big endian的方式来传输数据的。所以有时我们也会把big
endian方式称之为网络字节序。当两台采用不同字节序的主机通信时,在发送数据之前都必须经过字节序的转换成为网络字节序后再进行传输。ANSI
C中提供了下面四个转换字节序的宏。

当前的存储器,多以byte为访问的最小单元,当一个逻辑上的整理必须分割为物理
上的若干单元时就存在了先放谁后放谁的问题,于是endian的问题应运而生了,对于不同的存储方法,就有Big-endian和Little-
endian两个描述.(这两个术语来自于 Jonathan Swift
的《《格利佛游记》其中交战的两个派别无法就应该从哪一端--小端还是大端--打开一个半熟的鸡蛋达成一致。在那个时代,Swift是在讽刺英国和法国之
间的持续冲突,Danny
Cohen,一位网络协议的早期开创者,第一次使用这两个术语来指代字节顺序,后来这个术语被广泛接纳了。)
存在"如果说"跟word或者说字长根本就没关系",假设有一数据文件里面有N多数顺序排布,如果想以Little-Endian
format
读入内存某区域,那么应该怎么读?怎么排?"这样的问题是由于对于endian的实质理解的偏差,endian指的是当物理上的最小单元比逻辑上的最小单
元小时,逻辑到物理的单元排布关系。这里的"有一数据文件里面有N多数顺序排布",这个"有一数据"显然不是逻辑上的最小单元,而其中的"N多数"的一个
才是逻辑最小单元,于是可应用楼主表格中的原则排列,而"N多数"之间的顺序则是由这"N多数"的宿主决定的,比如是你写的程序,这个顺序由你决定.

刚才谈到了,endian指的是当物理上的最小单元比逻辑上的最小单元小时,逻辑到物理的单元排布关系。咱们接触到的物理单元最小都是byte,在通信领域中,这里往往是bit,不过原理也是类似的。

实践可以给你更多的经验,比如在一个嵌入式系统的通信协议中,从底层射频驱动到上层的协议栈全部需要实现,那么很可能遇到多个endian的问题,底层的bit序、协议层的byte序、应用层的byte序,这些都是不同的概念.

gcc和g++的区别和联系

gcc和g++都是GNU(组织)的一个编译器。

误区一:gcc只能编译c代码,g++只能编译c++代码
两者都可以,但是请注意:
1,后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。
2,编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

误区二:gcc不会定义__cplusplus宏,而g++会
实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

误区三:编译只能用gcc,链接只能用g++

格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc
-lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。