2014-09-15 18:16:56 cqu20093154 阅读数 953
  • Linux操作系统及常用基础命令深入讲解

    本课程为全新马哥linux全套系列课程之一--Linux基础入门和架构了解,从Linux起源,Linux架构和Linux形成历史开始逐步讲解,让你彻彻底底了解Linux的诞生,之后介绍了Linux相关文化和核心组成结构,以及Linux常用命令和基本用法,课程由浅入深,讲授方法受到98%学员一致好评!

    29624 人正在学习 去看看 马永亮

        最近我又开始看这个《深入理解linux网络技术内幕》了。以前一直觉得这本书是一个巨无霸,昨天和前天各花了一点时间。大概是一直在搞内核的缘故吧!现在看起来倒不是很吃力了。大概看了1/3吧,虽然的确有点跑马观花了意味,但是自我感觉还是明白了主干的东西。《linux内核情景分析》里面对软中断讲得比较简略,而《深入》那书后边还图书馆了,所以导致我对软中断的理解很不够透彻。现在大致终结一下吧!

        首先,软中断的执行是由ksoftirqd完成的,这是多个内核进程,每个CPU都有一个这样的进程,申明如下:
		//linux-3.0.8/include/linux/interrupt.h
		DECLARE_PER_CPU(struct list_head [NR_SOFTIRQS], softirq_work_list);
		DECLARE_PER_CPU(struct task_struct *, ksoftirqd);
		
		static struct softirq_action softirq_vec[NR_SOFTIRQS] __cacheline_aligned_in_smp;
		
		struct softirq_action
		{
			void	(*action)(struct softirq_action *);
		};
		
       softirq_action就是一个函数指针,NR_SOFTIRQS就是软中断的个数,不信你可以看看下边。
	enum
		{
			HI_SOFTIRQ=0,
			TIMER_SOFTIRQ,
			NET_TX_SOFTIRQ,
			NET_RX_SOFTIRQ,
			BLOCK_SOFTIRQ,
			BLOCK_IOPOLL_SOFTIRQ,
			TASKLET_SOFTIRQ,
			SCHED_SOFTIRQ,
			HRTIMER_SOFTIRQ,
			RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */
		
			NR_SOFTIRQS
		};

        在每个ksoftirqd线程中,它所干的是就去执行softirq_vec函数指针数组中的函数,相当于按优先级执行。每个软中断的函数都是在内核里定义好的。比如tasklet_hi_action(), tasklet_action(),net_rx_action()。当然,这还不算完。对于不同的软中断函数,执行的方法也不一样。就tasklet_hi_action()和tasklet_action()而言,它们各自有一个链表分别叫tasklet_vec,tasklet_hi_vec。这些链表单元结构都是tasklet_struct类型的。定义如下:
		//linux-3.0.8/include/linux/interrupt.h
		struct tasklet_struct
		{
			struct tasklet_struct *next;
			unsigned long state;
			atomic_t count;
			void (*func)(unsigned long);
			unsigned long data;
		};
	//linux-3.0.8/kernel/softirq.c
		static DEFINE_PER_CPU(struct tasklet_head, tasklet_vec);
		static DEFINE_PER_CPU(struct tasklet_head, tasklet_hi_vec);
看到那个func函数指针没有,这个就是链表最重要的元素。软中断函数tasklet_hi_action()和tasklet_action()的作用就是调用这个链表中的func函数。可以根据需要练添加tasklet_struct元素进入链表。对于net_rx_action(),这函数就是真正衔接中断的函数,算是所谓的“bottom half”了。它的工作机理其实和前面两个差不多。依然是一个链表,链表的表头在softnet_data。这是一个全局的数据结构。每一个CPU都有一个对应的soft_data,它包含一个headlist polllist的指针。polllist实际连接了napi_struct类型。而这个napi_struct类型是net_device息息相关。napi_struct中的poll指针就指向了具体的驱动模块中的poll()函数。到这里,我们终于找到了和上半部衔接的地方了。下面这是e100驱动的poll函数。
		static int e100_poll(struct napi_struct *napi, int budget)
		{
			struct nic *nic = container_of(napi, struct nic, napi);
			unsigned int work_done = 0;
		
			e100_rx_clean(nic, &work_done, budget);
			e100_tx_clean(nic);
		
			/* If budget not fully consumed, exit the polling mode */
			if (work_done < budget) {
				napi_complete(napi);
				e100_enable_irq(nic);
			}
		
			return work_done;
		}
        看似清爽的函数其实并没有这么简单。主要的工作都是e100_rx_clean()在干。然后就是接收了,值得一提的是e100使用了DMA,然后调用的netif_receive_skb(skb)。这的确是历史性的一刻了。这标志着我们的数据经过千辛万苦终于是走出了驱动层,向L3迈进。



2019-04-01 17:42:16 USTC_YZ 阅读数 129
  • Linux操作系统及常用基础命令深入讲解

    本课程为全新马哥linux全套系列课程之一--Linux基础入门和架构了解,从Linux起源,Linux架构和Linux形成历史开始逐步讲解,让你彻彻底底了解Linux的诞生,之后介绍了Linux相关文化和核心组成结构,以及Linux常用命令和基本用法,课程由浅入深,讲授方法受到98%学员一致好评!

    29624 人正在学习 去看看 马永亮

深入LINUX内核架构(德)莫尔勒:
链接:https://pan.baidu.com/s/1g9klBGkKqtRfnT2uAmGlpw
提取码:n83g

深入理解LINUX网络技术内幕_高清版带书签:
链接:https://pan.baidu.com/s/1gVlqKFHTeNVj4U8PT3B5Aw
提取码:l1o1

2015-02-03 14:25:42 Windeal 阅读数 3002
  • Linux操作系统及常用基础命令深入讲解

    本课程为全新马哥linux全套系列课程之一--Linux基础入门和架构了解,从Linux起源,Linux架构和Linux形成历史开始逐步讲解,让你彻彻底底了解Linux的诞生,之后介绍了Linux相关文化和核心组成结构,以及Linux常用命令和基本用法,课程由浅入深,讲授方法受到98%学员一致好评!

    29624 人正在学习 去看看 马永亮
 

概述

   内核的初始化过程过程中,与网络相关的工作如下所示:

    内核引导时执行start_kernel,start_kernel结束之前会调用rest_init,rest_init初始化内核线程init(在Linux3-12中为kernel_init)。
asmlinkage void __init start_kernel(void)
{
    ...
    parse_early_param();//间接调用parse_args
    parse_args(...); //处理内核引导程序(boot loader)在引导期间传给内核的参数,
    ...
    init_IRQ();   //初始化硬件中断
    tick_init();
    init_timers(); //定时器用于支持后续初始化工作的运行。
    hrtimers_init();
    softirq_init(); //初始化软件中断
    ...
    rest_init();   //这里会通过kernel_thread函数调用内核线程init(kernel_init).
}

static init __ref kernel_init(void *unused)
{
    kernel_init_freeable();   //Linux-3.12 在这里调用do_basic_setup
    free_initmem();  //用于释放已经不再需要的内存。
    .....
    run_init_process(execute_command)
    ...
}

static void __init do_basic_setup(void)
{
    cpuset_init_smp();
    usermodehelper_init();
    shmem_init();
    driver_init();
    init_irq_proc();
    do_ctors();
    usermodehelper_enable();
    do_initcalls();  //初始化内核子系统和内建的设备驱动 
    random_int_secret_init();
}



设备的注册和初始化

    一个设备要能够正常工作,他就必须被内核所识别,并且与正确的驱动关联起来。设备驱动程序以私有结构体的形式保存了驱动本设备所需要的所有信息,并且与其他和本设备有交互的组件相互影响。
    设备的注册和初始化一部分由内核完成,一部分由设备驱动程序完成。

      设备的初始化包括了硬件初始化、软件初始化、功能初始化三部分:硬件初始化:由设备驱动程序和通用总线层完成,有时也需要用户提供一些参数。主要任务是将硬件功能配置成IRQ和I/O地址,以便能够跟内核相互作用。软件初始化:设备使用之前必须关注当前配置或启用的网络协议, 一般需要用户提供诸如ip地址之类的参数。功能初始化: Linux内核提供了一系列的网络选项,有些网络选项对每一个设备都需要进行单独配置(如实现Qos的子系统),这些配置决定了数据包进入队列和离开设备的出口队列的方式 
    NIC初始化目标
    网络设备在Linux中都是以net_device实例进行初始化的,本节先不讨论这个,本节主要介绍设备驱动程序如何分配/建立设备与内核通信所需要的资源。IRQ线NIC必须分派一个IRQ,用于在必要时唤起内核的注意(虚拟设备不需要分配IRQ,因为它的工作都是在内部实现。)/proc/interrupts文件可用于观察当前中断线分派状态。I/O端口和内存注册: 驱动程序会将设备的一块内存映射到系统内存,使得驱动程序的读写操作能够通过系统内存直接进行。注册和释放操作分别由request_region和 release_region进行。

设备与内核之间的交互

几乎所有设备与内核的交互都是通过以下两种方式:
内核的轮询:
    内核定时检查设备的状态,判断设备是否有什么请求。
设备驱动的中断请求:
    设备驱动发送硬件信号引起内核注意

内核轮询在其他文章会介绍,本文主要介绍硬件中断中与网络有关的概念。
硬件中断
    每一个中断都会运行一个中断处理程序,这些中断响应程序都是设备驱动为设备量身定做的。一般而言,当设备注册一个NIC时,它首先会请求并分配一个IRQ,然后要为IRQ注册(如果设备被卸载了,则需要注销)一个IRQ响应程序。相应的内核代码在kernel/irq/manage.c和arch/XXX/kernel/irq.c。(其中XXX为处理器架构)
int request_threaded_irq(unsigned int irq, irq_handler_t handler,                                            
             irq_handler_t thread_fn, unsigned long irqflags,
             const char *devname, void *dev_id)
void free_irq(unsigned int irq, void *dev_id)

    注意:irq的注册和释放函数都带有参数dev_id。因为IRQ是可以共享的,因此需要IRQ number和dev_id共同来唯一表示中断。
    另,在注册IRQ时,必须保证IRQ还未有设备请求,除非所有设备都支持IRQ共享。
    内核接收到一个中断信号时,会通过IRQ number调用关联的中断响应程序。IRQ number与中断响应程序以表的形式保存。由于多个设备可能共享IRQ的关系,IRQ number与中断响应程序的关系可能是一对多的。
    中断类型:接收到数据帧、帧传输失败、DMA传输已成功完成、设备已经有足够内存来创建新的传输会话(可用NIC可用内存达到一定数值<一般为设备MTU>时产生一个中断)
    为了防止内核在设备内存不足时多次提交传输请求,设备驱动可以关闭内核出口队列,待到资源足够是才重启。下面是一个范例:
static netdev_tx_t
el3_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    ……
    netif_stop_queue (dev);
    ……
    dev->trans_start = jiffies;
    if (inw(ioaddr + TX_FREE) > 1536)
        netif_start_queue(dev);
    else
        /* Interrupt us when the FIFO has room for max-sized packet. */
        outw(SetTxThreshold + 1536, ioaddr + EL3_CMD);
    ……
}



    IRQ共享:IRQ线是很有限的资源,为了让一个系统能支持更多的设备,只能让多个设备共享IRQ线。IRQ共享的机制是这样的,内核收到中断请求,然后调用所有与该中断相关联的响应例程,然后有各个响应例程自行判断过滤是否对这个中断进行处理。(注意,IRQ与响应程序是一对多的,发生一个IRQ,哪些响应程序要处理,哪些不需要不是有内核去判断,而是各个中断响应程序自己判断,内核则是调用所有的响应程序。)
    IRQ与IRQ响应程序的组织:用全局的vector:irq_desc来组织,irq_desc包含所有IRQ,每个IRQ对应自己的链表,链表中是该IRQ关联的所有响应程序。只有IRQ共享时,IRQ链表的节点才会超过一个。整个组织如下图:



初始化选项

    所有的系统内建组件以及作为模块加载的设备都能通过用户的输入参数调整所实现的功能、重写其默认值,或者在引导前后有不同的值。
模块选项:(module_param系列的宏)
    模块加载时可以定义。如果是内建的组件,由于在引导期间无法配置,可以通过sys/进行运行时配置。
引导期间内核选项:(__setup系列宏)
    引导期间提供。用于可以内建到内核的模块
    

设备处理层的初始化

    网络代码初始化有以下重要的部分:流量控制,每个CPU输入队列初始化。这些初始化工作在引导期间由net_dev_init完成:
static int __init net_dev_init(void)
subsys_initcall(net_dev_init);   


用户空间辅助程序

  • /sbin/modprobe 在内核需要加载某个模块时调用,判断内核传递的模块是不是/etc/modprobe.conf文件中定义的别名
  • /sbin/hotplug 在内核检测到一个新设备插入或拔出系统时调用,它的任务是根据设备标识加载正确的驱动

  1. 以模块方式加载
    kmod模块加载器允许内核组件通过调用request_module请求加载某个模块
    举个例子;如果系统管理员使用ifconfig配置某个网卡,但这个网卡驱动还没有加载,如eth0,内核就会给/sbin/modprobe发送一个请求,让它加载名称为
    eth0的模块。如果/etc/modprobe.conf中包含“alias eth0 xxx”的字符,/sbin/modprobe就会尝试加载xxx.ko模块。
    module_param 宏定义在引入sysfs后可以通过文件来访问得到模块参数
    模块选项有三项 , 第一项参数名称,第二项参数类型,第三项表示参数作为文件在sys文件系统中所有的权限。
    每个模块都会在sys/modules下生成对应的目录,通过目录下的文件可以获取模块参数。
  2. pnp热插拔
    hotplug允许内核检测热插拔设备的插入和拔出并通知用户进程(/sbin/hotplug),用户进程根据这些通知来加载相应的驱动
    在编译内核时,会在kernel目录下生成modules.pcimap和modules.usbmap两个文件,这两个文件分别包含了内核所支持设备的pci id和usb id,文件中还包
    含于每个设备的id相对应的内核模块名称,当用户进程收到内核关于pnp的通知后,会使用这个文件来查找正确的设备驱动

虚拟设备

    虚拟设备一般也使用net_device结构体进行实例化(也有一些例外,如别名接口设备)。
    虚拟设备一般会有用户空间配置工具来对其进行配置。尤其是无法使用ifconfig来进行配置的高级字段。
    虚拟设备一般会有一个/proc接口目录,其内容详细程度取决于虚拟设备的设计。
    虚拟设备与这是设备的对应关系不是一一对应的。这就导致了虚拟设备可能需要进行流量控制的配置。
    虚拟设备的流量是简介从真实物理设备获得的,因而不需要分配IRQ、IO端口和IO内存。
    虚拟设备与其他真实物理设备一样,能对特殊的事件通知做出相应的反应。


/proc调整

    












2015-02-08 11:00:38 Windeal 阅读数 4061
  • Linux操作系统及常用基础命令深入讲解

    本课程为全新马哥linux全套系列课程之一--Linux基础入门和架构了解,从Linux起源,Linux架构和Linux形成历史开始逐步讲解,让你彻彻底底了解Linux的诞生,之后介绍了Linux相关文化和核心组成结构,以及Linux常用命令和基本用法,课程由浅入深,讲授方法受到98%学员一致好评!

    29624 人正在学习 去看看 马永亮

设备注册于设备除名

    设备注册与设备除名一般有 register_netdev和unregister_netdev完成。这两个是包裹函数,负责上锁,真正起作用的是其调用的register_netdevice和unregister_netdevice。参见:net/core/dev.c。
    下图描述了设备注册过程中的一些状态变化



    状态的改变会用到UNINITIALIZED和REGISTERED之间的状态REGISTERING。这些进程有netdev_run_todo进行。参照“切割操作:netdev_run_todo”
    为设备进行注册和除名时,设备驱动程序可以使用net_device的两个虚拟函数init和uninit进行私有数据的初始化和清理工作。
    设备除名时,只有对net_device的引用计数为0,netdev_wait_allrefs才会返回,除名工作才会顺利完成。
    设备的注册和除名操作都是由netdev_run_todo完成的。

切割操作:netdev_run_todo

    register_netdevice会负责一部分的注册工作,然后再让netdev_run_todo予以完成。
    net_device的改变会通过rtnl_lock和rtnl_unlock收到Routing Netlink信号量的保护。这也是为什么register_netdev执行时需要请求锁,并且在返回时要释放锁的原因。一旦register_netdevice完成了它自己的工作,它会通过net_set_todo将net_device结构体添加到net_todo_list中。net_todo_list包含一系列有注册(或除名)操作需要被完成的设备列表。这份列表有register_netdev在释放锁时间接予以处理。
    因此rtnl_unlock不仅会释放锁,还会调用 netdev_run_todo。netdev_run_todo会浏览net_todo_list列表,然后完成全部的实例注册。

void rtnl_unlock(void)
{
    /* This fellow will unlock it for us. */
    netdev_run_todo();                                                          
}
EXPORT_SYMBOL(rtnl_unlock);
    netdev_run_todo所进行的工作不需要持有锁。


设备注册状态通知

    内核组件和应用程序都可能想知道设备的注册、除名、开启、关闭。设备装态信息通过两种方式传送通知。
netdev_chain
Netlink的 RTMGRP_LINK多播组

netdev_chain通知链:
    设备的注册和除名阶段各个状态的变化都是通过netdev_chain来进行通告的。对其感兴趣的内核组件可以分别通过register_netdevice_notifier和unregister_netdevice_notifier来针对该链进行注册和除名。
    netdev_chain报告的事件定义在:
//格式:
#define NETDEV_UP   0x0001  /* For now you can't veto a device up/down */
#define NETDEV_DOWN 0x0002
......
 
//含义:
NETDEV_UP        //该报告表明设备被启用,由dev_open产生报告
NETDEV_DOWN      //设备已关闭,由dev_close生成报告
NETDEV_REBOOT    //因硬件设备,设备已重启
NETDEV_CHANGE    //设备的状态或者配置发生改变
NETDEV_REGISTER        //设备已经注册
NETDEV_UNREGISTER      //设备已经除名
NETDEV_CHANGEMTU       //
NETDE   //
NETDEV_PRE_UP               //
NETDEV_PRE_TYPE_CHANGE      //
NETDEV_POST_TYPE_CHANGE     //
NETDEV_POST_INIT            //
NETDEV_UNREGISTER_FINAL     //
NETDEV_RELEASE              //
NETDEV_NOTIFY_PEERS         //
NETDEV_JOIN                 //
NETDEV_CHANGEUPPER  
V_CHANGEADDR      //设备硬件地址(或关联的广播地址)发生改变
NETDEV_GOING_DOWN      //
NETDEV_CHANGENAME      //设备名称发生改变
NETDEV_FEAT_CHANGE     //
NETDEV_BONDING_FAILOVER  
        //
NETDEV_RESEND_IGMP          //


 很多设备都在netdev_chain中注册,如:路由,防火墙
RTnetlink链接通知
    当设备发生一些状态改变(或其它事件),会通过rtmsg_ifinfo把通知传给link多播组。

设备注册:

    设备注册不仅仅只是把net_device嵌入到全局表dev_base和哈希表dev_name_head、dev_index_head中,它还包括初始化net_device部分参数,发送广播通告(提醒其他模块本设备加入)、以及一些其他的工作。
int register_netdev(struct net_device *dev)
{
    int err; 
 
    rtnl_lock();
    err = register_netdevice(dev);                                                                                       
    rtnl_unlock();
    return err; 
}
    register_netdevice开始设备注册工作,并调用net_set_todo,而net_set_todo最终会调用netdev_run_todo完成注册。
    register_netdevice的工作主要包括以下部分:
  •     初始化net_device的部分字段
  •     如果内核支持Divert功能,则用alloc_divert_blk分配该功能所需的数据空间块,并连接至dev->divert
  •     如果设备驱动已经对dev->init进行初始化,则执行此函数。
  •     由dev_new_index分配给设备一个识别码。
  •     把net_device插入到全局表dev_base,以及两张哈希表dev_name_head,dev_index_head。
  •     检查功能标识是否有无效的组合。
  •     设置dev->state中的__LINK_STATE_PRESENT标识,使得设备能为内核所用。
  •     用dev_init_scheduler初始化设备队列规则,以便流量控制用于实现Qos。
  •     通过netdev_chain通知表链通知所有对本设备注册感兴趣的子系统。
    当netdev_run_todo被调用完成注册时,它只更新dev->reg_state,并将设备注册进入sysfs。

设备除名:

    设备除名需要复原设备注册期间所进行的所有工作和一些其他事项:
  •     用dev_close关闭设备
  •     释放所有分配的资源(IRQ、I/O端口...)
  •     从全局表dev_base和两张哈希表中删除设备
  •     当该设备的所有引用都释放后,释放net_device结构的空间、该驱动程序的私有数据结构、以及相链接的内存区域块。
  •     删除添加到/proc 和/sys的所有文件
    net_device中三个函数指针比较有用:      
dev->stop       //用以关闭设备,通常包括关闭netif_stop_queue出口,释放硬件资源等
dev->uninit     //主要负责引用计数,少用到
dev->destructor //少数虚拟设备使用,通常初始化为: free_netdev 或其包裹函数

设备除名有int unregister_netdevice(struct net_device *dev)进行,其主要工作如下:
  •     如果设备没关闭,使用dev_close关闭
  •     从全局表dev_base和两张哈希表中删除设备
  •     所有与该设备关联的队列规则实例,由dev_shutdown销毁
  •     发送NETDEV_UNREGISTER消息到netdev_chain通知链。
  •     除名消息也必须通知用户空间。
  •     任何链接至net_device的数据块被释放
  •     register_netdevice中dev_init的所有操作都要有dev_uninit复原。
    最后调用net_set_todo,使得net_run_todo能完成除名。

引用计数

    只有当设备引用计数都被释放时,net_device结构才能被释放。
     引用计数保存在dev->refcnt中。当引用新增或删除时,使用dev_hold和dev_put更新引用计数。
    当设备以register_netdevice注册时,dev->refcnt初始化为1, 这第一个引用有负责网络设备数据库的内核代码所持有。因此,只有当设备除名时,该值才有可能降为0.
    

网络设备的启用和关闭

    设备一旦注册即可使用,但是如果没有明确的开启其功能,设备仍然无法接收和传输数据流。
    设备的开启请求由dev_open完成:
//net/core/dev.c
int dev_open(struct net_device *dev)
static int __dev_open(struct net_device *dev)
    启用设备包括下列工作:
  •     如果 dev->open初始化了,就调用dev->open。
  •     设置dev_state中的__LINK_STATE_START标识,以表明设备开启或在运行中
  •     设置dev_flags中的IFF_UP,标识设备为开启。
  •     调用dev_activate初始化流量控制所需使用到的出口队列规则,然后启用看门狗定时器。
  •     向netdev_chain发送NETDEV_UP通知
    设备的启用必须显示执行,但设备的关闭则可以通过用户命令显式执行或者通过其他程序隐式执行。
    设备关闭有以下任务:
  •     向netdev_chain发送 NETDEV_GOING_DOWN通知
  •     调用dev_deactivat关闭队列规则
  •     清除dev->state的_ _LINK_STATE_START标志,以表明设备禁用了
  •     如果正在执行读取入口队列数据包的轮询操作,则等待该操作完成。因为dev->state关系,入口队列不会再有入队操作,所以读取当前已经在队列中的即可
  •     如果dev->stop有定义,执行它。
  •     清除dev->flags的IFF_UP操作。
  •     向netdev_chain发送NEtdEV_DOWN通知。
    

更新队列规则状态:


    以后再补充




从用户空间配置设备相关信息

    许多工具可以用来配置设备相关参数:
  •     ipconfig和mii-tool 来自net-tool套件
  •     ethtool    来自ethtool套件
  •     ip link:来自IP ROUTER2 套件
 










2007-07-31 12:15:00 f5key 阅读数 187
  • Linux操作系统及常用基础命令深入讲解

    本课程为全新马哥linux全套系列课程之一--Linux基础入门和架构了解,从Linux起源,Linux架构和Linux形成历史开始逐步讲解,让你彻彻底底了解Linux的诞生,之后介绍了Linux相关文化和核心组成结构,以及Linux常用命令和基本用法,课程由浅入深,讲授方法受到98%学员一致好评!

    29624 人正在学习 去看看 马永亮

深入理解Linux网络文件系统

关键词深入理解Linux网络文件系统

网络文件服务器(Network File System,简称NFS)是Linux上最容易配置的服务之一。尽管它的规则简单,却有着丰富的内涵。本刊今年第2期曾经介绍了如何构建Linux上的NFS服务器。但NFS还有许多Unix/Linux爱好者,甚至系统管理员应该注意的特别之处。本文旨在帮助读者更深入地了解NFS。

NFS Server的配置

首先介绍一下Server端和NFS相关文件的含义:

◆ /etc/exports 是NFS Server最基本的配置文件之一,文件中列出了共享的文件系统和允许访问这些文件系统的主机。这个文件可以用通配符或网段,以及一组主机名的形式来表示客户机列表。它有很大的灵活性,一般由系统管理员配置。

◆ /proc/fs/nfs/exports 导出文件列表的内核视图,包括导出的参数。

◆ /var/lib/nfs/etab 状态文件,其中列出了当前的高级列表。此文件包含了/etc/exports中相同格式的所有项目,同时包含了由exportfs -i手工导入的项目。

◆ /var/lib/nfs/rmtab 状态文件,列出了挂接导出文件的远程客户机清单。

◆ var/lib/nfs/xtab 状态文件,当前的低级导出清单。

例如,/etc/exports有下列一行/data *.flying.com.cn(rw),显然有多个主机可以匹配这个域。如果两个主机,如mail.flying.com.cn和ftp.flying.com.cn都请求访问该文件系统,则/var/lib/nfs/xtab文件包含了这个文件系统的两个基本相同的项目。

/etc/exports是纯文本文件,空行和以“#”开头的行会被忽略,长行可以用斜杠“/”分解为多行。文件中列出了允许NFS客户机访问服务器的文件系统,定义了导出文件系统和访问权限。

/etc/exports文件中任意行的格式如下:


vol_name host_name1(option1,opeion2....) host_name2(option1,option2)



其中vol_name(卷名)表示要导出的文件系统,除去卷名以外,其它参数都可以不写,不显示指出的参数都会采用缺省参数。host_name1 (主机名)如果不写的话,则任何主机都可以请求该文件系统。主机名的定义非常灵活,既可以指定某台主机,也可以指定一组主机。可以用下列四种方法来描述:

◆ 单个主机 可以用短名及完全限定名,或者用IP地址,例如student01、student01.flying.com.cn或者192.168.10.1都是合法的主机名。

◆ 网组 可以列出/etc/netgroup文件中或NFS网组映射中定义的整组主机。网组名以“@”开头,例如 @teacher 或 @students。

◆通配符主机 用通配符可以列出一个域中的所有主机器,通配符可以使用“*”和“?”、“*”匹配一个或多个字符及“?”匹配一个字符,但通配符不能匹配主机名中的点号。*.flying.com.cn可以匹配 ftp01.flying.com.cn,但不能匹配backup.ftp01.flying.com.cn,而*.*.flying.com.cn 就可以匹配 backup.ftp01.flying.com.cn。ftp??.flying.com.cn可以匹配ftp01.flying.com.cn和ftp02.flying.com.cn。

◆ 主机网络 可以用主机网络的方式导出特定的子网或几个子网,用192.168.10.0/255.255.255.0的方式可以匹配从192.168.10.1到192.168.10.255所有主机。导出选项用逗号分隔,但不能包含空格,长度可以任意设定。多数选项都是布尔选项,值为True或False,选项与性能或者安全性有关。当/etc/exports文件没有给出值时,系统会自动采用缺省值。选项的详细用法可以用man exportfs命令来获得。

更改/etc/exports文件后,可以用exportfs命令将文件的改变告诉服务器,也可以忽略/etc/exports文件,直接使用exportfs命令导出共享卷。exportfs可以指定导出或非导出卷时影响的选项,可以影响一个卷或几个卷的选项,见表1。

NFS锁及远程配额监控程序

在Unix中可以锁定文件,保证不会有两个用户同时写入文件的同一部分,以确保数据的一致。保证一致性的关键是内核要仲裁锁定和写入请求。

而在NFS/Samba之类的分布式文件系统中,许多客户可能同时要求写入和锁定同一文件。此时,只有服务器本身能扮演仲裁的角色。Unix/Linux的方法是增加一个RPC协议处理锁操作,这就是NFS锁监控程序(NLM,NFS Lock Manager)。通常不需要关心它的具体运行方式,将此服务打开即可。

rpc.rquotad监控程序实现RQUOTA协议,在Linux中NFS服务器总是执行配额,不管是否运行了rpc.rquotad。

启动和关闭NFS Server的顺序

1.启动NFS Server

/etc/rc.d/init.d/portmap start
/etc/rc.d/init.d/nfslock start
/etc/rc.d/init.d/nfs start


其实,读者可以根据/etc/rc.d/rc3.d或rc5.d下的文件里,以“S”开头的带有portmap、nfslock nfs的文件中数字的大小来判断哪个服务应该优先启动。在笔者的机器上,它们分别是S13portmap、S14nfslock和S60nfs,这几个数字清楚地表明了以上启动的顺序。

2.关闭NFS Server的顺序

/etc/rc.d/init.d/nfslock stop
/etc/rc.d/init.d/nfs stop
/etc/rc.d/init.d/portmap stop


如果在客户端取消挂接前关闭NFS Server,显而易见会发生错误,因此应该尽量避免。

NFS Client的配置

配置NFS客户机,可以通过配置/etc/fstab文件或运行mount命令来进行。

对于那些需要在启动时就挂接的文件系统而言,往往将其写入/etc/fstab文件中,其中要包括NFS服务器的主机名或IP地址、文件系统、挂接点、选项等。对于那些临时需要的NFS共享卷来说,则多数会使用mount命令将其手动挂接。这两种方法的参数使用基本上是一样的。下面就以/etc/fstab 文件为例,说明挂接NFS卷时参数的使用。

该文件中有关NFS卷的写法通常如下:

servername:/shareplace mountpoint nfs option01,option02,...optionX




表2列出了用于所有文件系统的一般选项。


对于NFS文件系统,还有一些可以使用的独特选项,使用的时候可以和一般选项混合使用,顺序任意。表3列出了NFS客户机可用的部分挂接选项。



请注意,NFS客户机和服务器的选项并不一定完全相同,而且有的时候会有冲突。比如说服务器以只读的方式导出,客户端却以可写的方式mount,虽然可以成功mount上,但尝试写入的时候就会发生错误。一般服务器和客户端配置冲突的时候,会以服务器的配置为准。

NFS性能优化和测试

1.同步或异步写入

了解了NFS配置的各种选项以后,再来看看各选项对性能及安全性的影响。Server端同步写入(sync)能提供较好的稳定性,但会减慢磁盘的速度。在高负载的服务器上,应用程序会经常花费大量的时间等待写入完成。异步写入(async)是服务器不把数据立即写入磁盘,而先将其保存到内核内存(缓冲区)中,计划在后边的某个时刻写入,迅速向客户机返回写入成功的信息。这种方法大大地提高了性能,但却牺牲了可靠性,如果系统在将缓冲区中的内容写入磁盘之前崩溃,则数据会永远丢失。

NFS客户端也可以采用异步写入的方式,在客户端缓存用户进程写入的数据,Linux的缺省方式如此。这种方法使得客户机的速度更快,当客户机崩溃时只丢失该客户端的数据。显而易见,客户端异步写入的风险比服务端异步写入的风险要小。鉴于Linux是个相当稳定的系统,为了得到比较好的NFS性能,值得采用客户端异步写入的办法。

2.选择TCP还是UDP

早期,Sun公司曾将NFS v2设计成为只使用UDP协议,主要原因是当时机器的内存、网络速度和CPU的影响,不得不选择对机器负担较轻的方式。而到了NFS v3,Sun公司选择了TCP协议作为缺省的传输方式。在Linux上,UDP协议是缺省使用的协议。作为服务器而言,别无选择。但作为客户端,可以使用TCP协议和其它使用TCP的Unix NFS服务器互连。

3.NFS性能测试

通常使用测试硬盘读写速度的方法来测试NFS的读写速度,但要注意以下几点:

◆ 保证主机和网络工作正常,集线器、交换机、路由器等网络设备工作正常;

◆ 分别在网络安静、普通和繁忙的时候进行测试,真实地了解实际状况和理想状况,以及最坏的状况;

◆ 没有必要为提高少许的性能而花费更多的精力。

基本测试可以用ping工具来检查网络状况,分别从服务器ping客户机,以及从客户机ping服务器。如果没有非对称路由的问题,这两种方法的结果应该相近。如果丢包率小于5%,就会造成NFS的性能不良。

测试程序用下面命令就可以完成:

time dd if=/dev/zero of=nfs.dat bs=4k count=4098



这个命令意思是在nfs.dat文件里写4098个4KB的块,也就是一个16MB的文件,也可以根据需要改变命令的参数, 但要注意每次实验时使用不同的文件名,否则又会和缓冲有关。

可能返回的结果如下:

4098+0 records in
4098+0 records out

real 0m23.984s 
user 0m0.260s
sys 0m4.080s



这里主要关心的是real的时间,上面的实验结果表明在NFS上写一个16MB的文件花了23.984秒,在改变导出选项和mount选项以后重复这个实验,就能初步确定各个选项对性能的影响。

NFS故障排除和诊断

1.故障诊断

NFS出现了故障,可以从以下几个方面着手检查:

◆ 检查NFS客户机和服务器的负荷是否太高,Server和Client之间的网络是否正常;

◆ 检查/etc/exports文件的正确性;

◆ 必要时重新启动NFS或portmap服务;

运行下列命令重新启动portmap和NFS:

service portmap restart 
service nfs start


◆ 检查Client上的mount命令或/etc/fstab的语法是否正确;

◆ 查看内核是否支持NFS和RPC服务。

普通的内核应有下列选项:

CONFIG_NFS_FS=m
CONFIG_NFS_V3=y
CONFIG_NFSD=m
CONFIG_NFSD_V3=y
CONFIG_SUNRPC=m


我们可以使用常见的网络连接和测试工具ping、tracerroute来测试网络连接及速度是否正常。网络连接正常是NFS能起作用的基础。

rpcinfo命令用于显示系统的RPC信息,一般使用-p参数列出某台主机的RPC服务。用rpcinfo-p命令检查服务器时,应该能看到portmapper、status、mountd nfs 和nlockmgr。用该命令检查客户端时,应该至少能看到portmapper服务。   2.常见问题处理

◆ 当mount NFS文件系统时,如果错误信息是“Permission denied”,则表示NFS服务器不让客户机挂接。一般可以用更改服务端上的/etc/exports文件来解决问题,使用exportfs-rv命令重新导出文件系统。

◆ 如果出现的错误是“Program not registed”,那么则是NFS服务可能没有启动或者运行不正常。最常用的解决方法是重新启动NFS服务。

◆ 如果出现“RPC:Unable to receive”,则可能是服务端没有启动portmap服务;如果已经启动了,则可能是被防火墙所屏蔽。

◆ 还有一种情况是mount文件系统的时候,客户端没有任何反应,这多半是因为客户端没有启动portmap服务。

建立安全的NFS服务器

任何网络服务器都会有安全问题,NFS也不能例外。由于设计方面的因素,NFS服务器不可能绝对安全。一般来说,不应该将NFS服务器运行在比较敏感的系统或者只有一般防火墙的机器上,应该尽量将其置于防火墙之后。配置安全的NFS服务器,可以从限制RCP服务的访问和控制文件系统的导出权限两方面着手。

限制RCP服务访问的办法一般是使用防火墙,比如基于tcp-wrapper、ipchians和iptalbes的防火墙。在全面使用Linux 2.4或更高版本内核的今天,了解iptables这种防火墙方法也就足够了。

缺省的状态下,portmap使用111端口,而NFS使用2049端口,可以通过iptables来限制对该端口的访问:

iptables -t filter -A INPUT -p udp -d 127.0.0.1 --dport 111 -j DROP
iptables -t filter -A INPUT -p udp -d 127.0.0.1 --dport 2049 -j DROP
iptables -t filter -A INPUT -p udp -s trusted_client -d this_server_ip --dport /
2049 -j ACCEPT
iptables -t filter -A INPUT -p udp -s not_trusted_client -d this_server_ip -dport /
2049 -j DROP


上面的脚本屏蔽了lo地址对portmap和NFS的访问。这样做的原因是,有的黑客程序会使用IP欺骗和RPC重定向的方法进行攻击。

除了防火墙的配置以外,还应该注意服务器导出选项。

此外,还要经常注意有关的安全通告,随时升级系统。如果是Red Hat的系统,可以采用RHN自动升级的办法(参见本刊2003年第1期《用RHN自动更新Red Hat Linux》)。

Linux上NFS还有许多复杂的原理和概念,如果读者有兴趣了解更多的知识,还应该了解以下一些内容:

◆ 外部数据表示(XDR,eXternal Data Representation);

◆ 远程过程调用(RPC,Remote Procedure Call);

◆ NFS 第四版本;

◆ 无状态服务器的概念;

◆ Amd 自动挂接器。

【作者: Liberal】【访问统计:】【2007年03月14日 星期三 10:51】【注册】【打印

没有更多推荐了,返回首页