zoukankan      html  css  js  c++  java
  • freeing unused kernel memory 2.6.19问题查找过程

    先google到以下内容

    ======================================================================================

    Sounds like you are able to build your own kernel ?

    In the Linux source code, linux/init/main.c there is a function called init.

    You could try adding some printk messages into init. Don't use printf as that is for user-space programs. printk should print to the console, note that it

    does have logging levels so watch out, KERN_ERR is the highest log level. However, all printks will appear in dmesg.

    Remember that a hang may prevent the last few printk messages from being seen. You could try adding delays by using ssleep() to allow time for the printks to

    be output.

    The "Freeing unused kernel memory" message is output by free_initmem() called from the init function. Perhaps the hang happens at the point of "unlocking the

    kernel" ? or maybe the init function can not spawn the init program off the root file system ?
      
    =============================我是分割线=======================================

    但是不知道改哪里,所以,又搜了main.c的注释来看。。。站在巨人的肩膀上才看得远,向前辈致敬!

    ==========================================================================

    核启动第二阶段从init/main.c的start_kernel()函数开始到函数结束。
    这一阶段对整个系统内存、cache、信号、设备等进行初始化,最后产生新的内核线程init后,
    调用cpu_idle()完成内核第二阶段。有很多书籍介绍这一部分的内容,我们这里仅仅讲述与xscale结构相关的部分。

    首先我们看一下start_kernel开始部分的源代码
    asmlinkage void __init start_kernel(void)
    {
       char * command_line;
       extern char saved_command_line[];
    /*
     * Interrupts are still disabled. Do necessary setups, then
     * enable them
     */
       lock_kernel();
       printk(linux_banner);
       setup_arch(&command_line);
       printk("Kernel command line: %s\n", saved_command_line);
       parse_options(command_line);
       trap_init();
       init_IRQ();
       sched_init();
       softirq_init();
       time_init();
       .......
       .....
       ...
      
    start_kernel使用了asmlinkage进行修饰,该修饰符定义在kernel/include/linux/linkage.h中,如下所示:
    #ifdef __cplusplus
    #define CPP_ASMLINKAGE extern "C"
    #else
    #define CPP_ASMLINKAGE
    #endif

    #if defined __i386__
    #define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))
    #elif defined __ia64__
    #define asmlinkage CPP_ASMLINKAGE __attribute__((syscall_linkage))
    #else
    #define asmlinkage CPP_ASMLINKAGE
    #endif

    应为我们使用的是arm平台,所以这些定义没有意义,不过还是简单介绍一下regparm的意思,察看gcc手册,原文
    介绍如下:
    On the Intel 386, the regparm attribute causes the compiler to pass arguments
    number one to number if they are of integral type in registers EAX, EDX,
    and ECX instead of on the stack. Functions that take a variable number of
    arguments will continue to be passed all of their arguments on the stack.
    Beware that on some ELF systems this attribute is unsuitable for global functions
    in shared libraries with lazy binding (which is the default). Lazy binding
    will send the first call via resolving code in the loader, which might assume
    EAX, EDX and ECX can be clobbered, as per the standard calling conventions.
    Solaris 8 is affected by this. GNU systems with GLIBC 2.1 or higher,
    and FreeBSD, are believed to be safe since the loaders there save all registers.
    (Lazy binding can be disabled with the linker or the loader if desired, to avoid
    the problem.)

    在网上还看到一个比较好的英文说明:
    The asmlinkage tag is one other thing that we should observe about this simple function.
    This is a #define for some gcc magic that tells the compiler that the function should not
    expect to find any of its arguments in registers (a common optimization),
    but only on the CPU's stack. Recall our earlier assertion that system_call consumes its
    first argument, the system call number, and allows up to four more arguments that are
    passed along to the real system call. system_call achieves this feat simply by leaving
    its other arguments (which were passed to it in registers) on the stack. All system calls
    are marked with the asmlinkage tag, so they all look to the stack for arguments. Of course,
    in sys_ni_syscall's case, this doesn't make any difference, because sys_ni_syscall doesn't
    take any arguments, but it's an issue for most other system calls. And, because you'll be
    seeing asmlinkage in front of many other functions, I thought you should know what it was about.

    简单描述一下他的功能:
    asmlinkage是个宏,使用它是为了保持参数在stack中。因为从汇编语言到C语言代码参数
    的传递是通过stack的,它也可能从stack中得到一些不需要的参数。Asmlinkage将要
    解析那些参数。regparm(0)表示不从寄存器传递参数。如果是__attribute__((regparm(3))),
    那么调用函数的时候参数不是通过栈传递,而是直接放到寄存器里,被调用函数直接从寄存器取参数。
    这一点可以从下面的定义可以看出:
    #define fastcall  __attribute__((regparm(3)))
    这些都必须是在i386平台下才有意义。

    说完asmlinkage,开始看源代码,第一个函数:lock_kernel(),
    这是为了在SMP系统下设计的,它定义在kernel/include/linux/smp_lock.h,如果是SMP系统,则会
    定义CONFIG_SMP,否则lock_kernel()将是空函数,如果定义CONFIG_SMP的话,则会包含kernel/include/
    asm/smplock.h头文件,lock_kernel()就定一在该文件中,首先我们来看一下smp_lock.h文件:
    #ifndef CONFIG_SMP

    #define lock_kernel()               do { } while(0)
    #define unlock_kernel()             do { } while(0)
    #define release_kernel_lock(task, cpu)      do { } while(0)
    #define reacquire_kernel_lock(task)     do { } while(0)  
    #define kernel_locked() 1

    #else

    #include <asm/smplock.h>

    #endif /* CONFIG_SMP */                                  

    我们的平台是单cpu的(没有定义CONFIG_SMP),所以lock_kernel是空函数,不过仍然对它进行一下说明,
    如果定义了CONFIG_SMP,则include kernel/include/asm-arm/smplock.h文件,看一下该文件:
    static inline void lock_kernel(void)
    {
         if (!++current->lock_depth)
            spin_lock(&kernel_flag);
    }

    static inline void unlock_kernel(void)
    {
         if (--current->lock_depth < 0)
             spin_unlock(&kernel_flag);
    }
    找到两个比较好的说明如下
    1
    kernel_flag是一个内核大自旋锁,所有进程都通过这个大锁来实现向内核态的迁移。只有
    获得这个大自旋锁的处理器可以进入内核,如中断处理程序等。在任何一对lock_kernel/
    unlock_kernel函数里至多可以有一个程序占用CPU。 进程的lock_depth成员初始化为-1,
    在kerenl/fork.c文件中设置。在它小于0时(恒为 -1),进程不拥有内核锁;当大于或等
    于0时,进程得到内核锁。
    2
    kernel_flag,定义为自旋锁,因为很多核心操作(例如驱动中)需要保证当前仅由一个进程执行,
    所以需要调用lock_kernel()/release_kernel()对核心锁进行操作,它在锁定/解锁kernel_flag的
    同时还在task_struct::lock_depth上设置了标志,lock_depth小于0表示未加锁。当发生进程切换的时候,
    不允许被切换走的进程握有kernel_flag锁,所以必须调用release_kernel_lock()强制释放,同时,
    新进程投入运行时如果lock_depth>0,即表明该进程被切换走之前握有核心锁,
    必须调用reacquire_kernel_lock()再次锁定;

    代码printk(linux_banner)将linux的一些标语打印在内核启动的开始部分,需要说明的是虽然这是
    在内核一开始运行时就打印了,但是它没有马上输出到控制台上,它只是将liunx_banner存储到printk
    的内部缓冲中,因为这时printk的输出设备,一般都是串口还没有初始化,只有到输出设备初始化完毕
    在缓冲中的数据才被输出,后面会看到在哪个位置linux_banner才真正输出到终端。linux_banner定义在
    kernel/init/version.c中:

    const char *linux_banner =
      "Linux version " UTS_RELEASE " (" LINUX_COMPILE_BY "@"
      LINUX_COMPILE_HOST ") (" LINUX_COMPILER ") " UTS_VERSION "\n";

    这里面的字符串定义在文件kernel/include/linux/compile.h和kernel/include/linux/version.h中,
    compile.h中的内容:
    #define UTS_VERSION "#1 Thu, 01 Feb 2007 13:32:14 +0800"
    #define LINUX_COMPILE_TIME "13:32:14"
    #define LINUX_COMPILE_BY "taoyue"
    #define LINUX_COMPILE_HOST "swlinux.cecwireless.com.cn"
    #define LINUX_COMPILE_DOMAIN "cecwireless.com.cn"
    #define LINUX_COMPILER "gcc version 3.2.1"

    version.h中的内容:
    #define UTS_RELEASE "2.4.19-rmk7-pxa2"
    #define LINUX_VERSION_CODE 132115
    #define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))
         
    这两个文件都是在编译时候生成的,看一下kernel/Makefile文件:
    include/linux/compile.h: $(CONFIGURATION) include/linux/version.h newversion
    @echo -n \#`cat .version` > .ver1
         @if [ -n "$(CONFIG_SMP)" ] ; then echo -n " SMP" >> .ver1; fi
         @if [ -f .name ]; then  echo -n \-`cat .name` >> .ver1; fi
         @LANG=C echo ' '`date -R` >> .ver1
         @echo \#define UTS_VERSION \"`cat .ver1 | $(uts_truncate)`\" > .ver
         @LANG=C echo \#define LINUX_COMPILE_TIME \"`date +%T`\" >> .ver
         @echo \#define LINUX_COMPILE_BY \"`whoami`\" >> .ver
         @echo \#define LINUX_COMPILE_HOST \"`hostname | $(uts_truncate)`\" >> .ver
         @([ -x /bin/dnsdomainname ] && /bin/dnsdomainname > .ver1) || \
          ([ -x /bin/domainname ] && /bin/domainname > .ver1) || \
          echo > .ver1
         @echo \#define LINUX_COMPILE_DOMAIN \"`cat .ver1 | $(uts_truncate)`\" >> .ver
         @echo \#define LINUX_COMPILER \"`$(CC) $(CFLAGS) -v 2>&1 | tail -1`\" >> .ver
         @mv -f .ver $@
         @rm -f .ver1

    include/linux/version.h: ./Makefile
         @expr length "$(KERNELRELEASE)" \<= $(uts_len) > /dev/null || \
          (echo KERNELRELEASE \"$(KERNELRELEASE)\" exceeds $(uts_len) characters >&2; false)
         @echo \#define UTS_RELEASE \"$(KERNELRELEASE)\" > .ver
         @echo \#define LINUX_VERSION_CODE `expr $(VERSION) \\* 65536 + $(PATCHLEVEL) \\* 256 + $(SUBLEVEL)` >> .ver
         @echo '#define KERNEL_VERSION(a,b,c) (((a) << 16) + ((b) << 8) + (c))' >>.ver
         @mv -f .ver $@
        
    可以修改的参数是:
    VERSION = 2
    PATCHLEVEL = 4
    SUBLEVEL = 19
    EXTRAVERSION = -rmk7-pxa2

    ======================我也是分割线==============================================

    然后仔细搜了下printk的使用

    ============================================================================

    printk()函数的总结

    我们在使用printk()函数中使用日志级别为的是使编程人员在编程过程中自定义地进行信息的输出,更加容易地掌握系统当前的状况。
    对程序的调试起到了很重要的作用。
    (下文中的日志级别和控制台日志控制级别是一个意思)

    printk(日志级别 "消息文本");这里的日志级别通俗的说指的是对文本信息的一种输出范围上的指定。
    日志级别一共有8个级别,printk的日志级别定义如下(在linux26/includelinux/kernel.h中):
    #defineKERN_EMERG"<0>"/*紧急事件消息,系统崩溃之前提示,表示系统不可用*/
    #defineKERN_ALERT"<1>"/*报告消息,表示必须立即采取措施*/
    #defineKERN_CRIT"<2>"/*临界条件,通常涉及严重的硬件或软件操作失败*/
    #defineKERN_ERR"<3>"/*错误条件,驱动程序常用KERN_ERR来报告硬件的错误*/
    #defineKERN_WARNING"<4>"/*警告条件,对可能出现问题的情况进行警告*/
    #defineKERN_NOTICE"<5>"/*正常但又重要的条件,用于提醒。常用于与安全相关的消息*/
    #defineKERN_INFO"<6>"/*提示信息,如驱动程序启动时,打印硬件信息*/
    #defineKERN_DEBUG"<7>"/*调试级别的消息*/

    没有指定日志级别的printk语句默认采用的级别是 DEFAULT_ MESSAGE_LOGLEVEL(这个默认级别一般为<4>,即与KERN_WARNING在一个级别上),其定义在linux26/kernel/printk.c中可以找到。
    下面是一个比较简单的使用
    printk(KERN_INFO "INFO\n");  //这里可以使用数字代替 KERN_INFO,即可以写成printk(<6> "INFO\n"); 
    在这个格式的定义中,日志级别和信息文本之间不能够使用逗号隔开,因为系统在进行编译的时候,将日志级别转换成字符串于后面的文本信息进行连接。

    在对系统输出进行控制时,主要是讨论控制台和伪终端的输出情况,以及系统日志等。

    下面是控制台日志级别的一些简要的介绍
    控制台相应的日志级别定义如下:
    #define MINIMUM_CONSOLE_LOGLEVEL  1   /*可以使用的最小日志级别*/
    #define DEFAULT_CONSOLE_LOGLEVEL  7 /*比KERN_DEBUG 更重要的消息都被打印*/

    int console_printk[4] = {
    DEFAULT_CONSOLE_LOGLEVEL,/*控制台日志级别,优先级高于该值的消息将在控制台显示*/
    /*默认消息日志级别,printk没定义优先级时,打印这个优先级以上的消息*/
    DEFAULT_MESSAGE_LOGLEVEL,
    /*最小控制台日志级别,控制台日志级别可被设置的最小值(最高优先级)*/
    MINIMUM_CONSOLE_LOGLEVEL,
    DEFAULT_CONSOLE_LOGLEVEL,/* 默认的控制台日志级别*/
    };
    在进行查看的时候,可以使用命令 cat /proc/sys/kernel/printk来查看这四个值
    可以通过修改文件/proc/sys/kernel/printk中的第一个值来更改当前的控制台日志级别。

    (声明:在下面的模块函数中控制台所使用的日志级别均为KERN_WARNING级别)当日志级别高于console_loglevel(控制台日志级别)时,消息才能在控制台显示出来。
    假如我们写了一个如下的模块函数:
    1 #include <linux/init.h>
    2 #include <linux/module.h>
    3 MODULE_LICENSE("Dual BSD/GPL");
    4 static int book_init(void)
    5 {
    6   printk(KERN_EMERG "EMERG\n");
    7   printk(KERN_ALERT "ALERT\n");
    8   printk(KERN_CRIT " CRIT\n");
    9   printk(KERN_ERR " ERR\n");
    10  printk(KERN_WARNING ""WARNING\n");
    11   printk(KERN_NOTICE "NOTICE\n");
    12  printk(KERN_INFO "INFO\n");
    13  printk(KERN_DEBUG "DEBUG\n");
    14  return 0;
        }
    15static void book_exit(void)
    16{
    17  printk(KERN_ALERT "Book module exit\n");
        }
    18  module_init(book_init);
    19  module_exit(book_exit);

    在控制台(这里指的是虚拟终端  Ctrl+Alt+(F1~F6))加载模块以后,控制台给出的信息为
    6~9行中要求输出的信息,我们在伪终端(如果对伪终端不是很清楚可以看相关的内容)上运行命令tail -n 10 /var/log/messages查看日志文件刚才得到的运行记录
    可以发现messages中的值为KERN_WARNING级别之后所要求输出到信息值。而如果我们在文件syslog和kern-log中查看系统日志文件,一般情况下可以得到所有的输出信息

    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637057] INFO
    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637063] CRIT
    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637066] WARNING
    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637068] ERR
    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637069] ALERT
    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637070] EMERG
    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637071]  NOTICE
    Jul 18 11:44:19 xiyoulinux-desktop kernel: [16100.637072] DEBUG
    (不过在有些机器上运行得到的结果并不是这样的)
    即一般情况下,syslog和kern.log两个文件中记录的内容从编程这个角度来看是基本一致的。
    在目录/var/log/下有一下四个文件可以查看日志
    syslog ,kern.log,messages ,DEBUG 。  
    syslog和kern.log一般情况下可以得到所有的系统输出值,而messages得到的是比控制台日志级别低的输出值,DEBUG得到的仅仅是DEBUG级别的
    输出值。
    一般情况下,优先级高于控制台日志级别的消息将被打印到控制台。优先级低于控制台日志级别的消息将被打印到messages日志文件中,而在伪终端下不打印任何的信息。
    我们在进行有关编程的时候,若使用到printk()这个函数,一般查看信息是在messages和虚拟终端下进行查看,而对于syslog和kern.log下是用来检验所有信息的输出情况。

    ==============================还是分割线=========================================

    如资料1描述,在init_mem()后面添加了printk(KERN_ERR “init_mem run well。。。\n”);ssleep(1);。。。逐行打印。。。最后追溯问题,最后发现在进入/sbin/init/之前还是正常的。。。过了这里是不是已经是udev的管辖内了?然后搜了init进程的执行过程。。。

    =============================================================================

    描述init进程,它是内核启动的第一个用户级进程。init有许多很重要的任务,比如象启动getty(用于用户登录)、实现运行级别、以及处理孤立进程。本章解释了怎样配置init以及如何运用不同的运行级别。

      对于Linux系统的运行来说,init程序是最基本的程序之一。但你仍可以大部分的忽律它。一个好的Linux发行版本通常随带有一个init的配置,这个配置适合于绝大多数系统的工作,在这样一些系统上不需要对init做任何事。通常,只有你在碰到诸如串行终端挂住了、拨入(不是拨出)调制解调器、或者你希望改变缺省的运行级别时你才需要关心init。

      当内核启动了自己之后(已被装入内存、已经开始运行、已经初始化了所有的设备驱动程序和数据结构等等),通过启动用户级程序init来完成引导进程的内核部分。因此,init总是第一个进程(它的进程号总是1)。

      内核在几个位置上来查寻init,这几个位置以前常用来放置init,但是init的最适当的位置(在Linux系统上)是/sbin/init。如果内核没有找到init,它就会试着运行/bin/sh,如果还是失败了,那么系统的启动就宣告失败了。

      当init开始运行,它通过执行一些管理任务来结束引导进程,例如检查文件系统、清理/tmp、启动各种服务以及为每个终端和虚拟控制台启动getty,在这些地方用户将登录系统。

      在系统完全起来之后,init为每个用户已退出的终端重启getty(这样下一个用户就可以登录)。init同样也收集孤立的进程:当一个进程启动了一个子进程并且在子进程之前终止了,这个子进程立刻成为init的子进程。对于各种技术方面的原因来说这是很重要的,知道这些也是有好处的,因为这便于理解进程列表和进程树图。init的变种很少。绝大多数Linux发行版本使用sysinit(由Miguel van Smoorenburg著),它是基于System V的init设计。UNIX的BSD版本有一个不同的init。最主要的不同在于运行级别:System V有而BSD没有(至少是传统上说)。这种区别并不是主要的。在此我们仅讨论sysvinit。 配置init以启动getty:/etc/inittab文件

      当init启动后,init读取/etc/inittab配置文件。当系统正在运行时,如果发出HUP信号,init会重读它;这个特性就使得对init的配置文件作过的更改不需要再重新启动系统就能起作用了。 /etc/inittab文件有点复杂。我们将从配置getty行的简单情况说起。

      etc/inittab中的行由四个冒号限定的域组成:

      id:runlevels:action:process

      下面对各个域进行了描述。另外,/etc/inittab可以包含空行以及以数字符号(’#’)开始的行;这些行均被忽略。

      id 这确定文件中的一行。对于getty行来说,指定了它在其上运行的终端(设备文件名/dev/tty后面的字符)。对于别的行来说,是没有意义的(除了有长度的限制),但它必须是唯一的。

      runlevels 该行应考虑的运行级别。运行级别以单个数字给出,没有分隔符。

      action 对于该行应采取的动作,也即,respawn再次运行下一个域中的命令,当它存在时,或者仅运行一次。

      process 要运行的命令。

      为了在第一个虚拟终端上(/dev/tty1)运行getty、在所有的正规多用户运行级别中(2-5),应该写入下面这行:

      1:2345:respawn:/sbin/getty 9600 tty1

      第一个域指出这是对应于/dev/tty1的行。第二个域说明它应用于运行级别2,3,4和5。第三个域是说在命令退出之后,应被再次执行(因此,用户可以登录、退出并且再次登录)。最后一个域是在第一个虚拟终端上运行getty的命令。

      如果你需要给系统增加终端或者拨入调制解调器线路,你应该给/etc/inittab增加更多的行,每一行对应一个终端或一条拨入线。详细信息,参见init、inittab以及getty的manual page。如果一个命令运行时失败了,并且init配置成重运行它,它会使用许多的系统资源:init运行它、它失败了、init再运行它、再次失败等等,没完没了。为了避免这样,init将追踪一个命令重运行了多少次,并且如果重运行的频率太高,它将被延时五分钟后再运行。

      一个运行级别(run level)是init以及整个系统的状态,它定义了能够提供什么系统服务。运行级别用数字来定义,见表7-1。对于如何使用用户定义运行级别(2到5)没有一致的意见。有些系统管理员使用运行级别来定义哪个子系统工作,也即,X是否能运行、网络是否能工作等等。其他人总是让所有子系统工作着或者单独地运行以及停止它们,而不改变它们的运行级别,因为运行级别对于控制他们的系统来说显得太粗率了。你必须自己决定,但是按照你的Linux发行版本的做法来做也许是最容易的了。

      在Linux中初始化脚本在/etc/inittab 文件(或称初始化表)中可以找到关于不同运行级别的描述。其描述如下:

      # Default runlevel. The runlevels used by RHS are:

      # 0 - halt (Do NOT set initdefault to this)

      # 1 - Single user mode

      # 2 - Multiuser, without NFS (The same as 3, if you do not have networking)

      # 3 - Full multiuser mode

      # 4 - unused

      # 5 - X11

      # 6 - reboot (Do NOT set initdefault to this)

      运行级别数

      0 终止系统

      1 单用户模式(用于特别管理)

      2-5 正常操作(用户定义)

      6 重启动

      运行级别通过如下行所示的行在/etc/inittab中配置:

      l2:2:wait:/etc/init.d/rc 2

      第一个域是任意给的符号,第二个域指出是运行级别2。第三个域说明当进入该运行级别时,init应该运行第四个域中的命令一次,并且init应该等待它的结束。在进入运行级别2时,在需要时/etc/init.d/rc命令运行或者停止服务。第四个域中的命令做所有设置一个运行级别的艰巨工作。它启动还没有运行的服务,并且停止在新的运行级别中不应再运行的服务。确切的命令是什么以及运行级别是如何配置的,依赖于各个Linux发行版本。当init开始运行时,它在/etc/inittab中查寻一行,该行指定了缺省的运行级别:

      id:2:initdefault:

      通过给内核一个single或emergency命令行参数,你可以在init运行开始时转到一个非缺省的运行级别上。例如,内核命令行参数可以通过LILO给出。这使得你可以选择单用户模式(运行级别 1)。当系统正在运行时,telinit命令可以改变运行级别。当运行级别改变时,init就运行/etc/inittab中相应的命令。

      /etc/inittab中的特殊配置

      /etc/inittab有些特殊的特性,它允许init对特别的环境作出响应。这些与众不同的特性在第三个域中由关键字标出。一些例子如下:

      powerwait 当系统电源失败时,允许init关闭系统。这里假设使用了UPS以及用于监视UPS和通知init电源失败的软件。

      ctrlaltdel 当用户在控制台上按了ctrl-alt-del组合键时,允许init重新(启动)引导系统。注意,系统管理员能够配置对ctrl-alt-del组合键的响应为其它的什么,例如,忽略它,如果系统是在一个公共的环境中(或者开始nethack。)

      sysinit 当系统引导时要执行的命令。例如,这个命令通常是清理/tmp。

      上面所列并不是全部。对于所有的关键字以及如何使用它们请参见inittab的manual page。

      启动(引导)进入单用户模式

      一个很重要的运行级别是单用户模式(single user mode)(运行级别1),在这个模式中只有系统管理员在使用机器并且只有很少的系统服务在运行,如登录服务。对于一些管理任务来说单用户模式是必须的,如在/usr分区上运行fsck,因为这需要该分区没被加载,除非几乎所有的系统服务都被终止了,否则不可能会有这种情况。通过telinit请求运行级别1,一个运行着的系统可以转换到单用户模式。在启动时,可以通过在内核的命令行上给出single或emergency来进入单用户模式:内核同样也将命令行给init,init会理解那个单词并且不会使用缺省的运行级别。(内核命令行输入的方法依赖于系统是如何引导的。)在加载文件系统之前,引导进入单用户模式有时是需要的,这样就可以手工运行fsck命令了,否则的话很可能损坏/usr分区(在一个有问题的文件系统上的任何操作会更进一步地损坏它,所以fsck要尽早地运行)。如果启动时fsck的自动检查失败了,启动描述文件init就会自动地进入单用户模式。这是试图避免系统使用一个文件系统,这个文件系统损坏的太严重以至于fsck都不能够自动地修复它。这样的毁坏情况是相当少的,通常是硬盘有问题或是在试验一个内核版本,但是有准备总比没有好。作为一个安全措施,一个正确配置的系统应该在运行单用户模式的shell之前要求口令。否则的话,只要给LILO输入适当的一行参数就很容易地以root身份进入系统。(当然,如果由于文件系统的问题而使/etc/passwd毁坏时,就不是这样了。如果是这样的话,你手头最好有张引导软盘。)

     
    在坛子里看到有人问有关inittab文件的问题,找了下资料,总结一下,大家多提意见和补充吧!
    init进程是系统中所有进程的父进程,init进程繁衍出完成通常操作所需的子进程,这些操作包括:设置机器名、检查和安装磁盘及文件系统、启动系统日志、配置网络接口并启动网络和邮件服务,启动打印服务等。Solaris中init进程的主要任务是按照inittab文件所提供的信息创建进程,由于进行系统初始化的那些进程都由init创建,所以init进程也称为系统初始化进程。下面具体说明inittab文件的格式。
    inittab文件中每一记录都从新的一行开始,每个记录项最多可有512个字符,每一项的格式通常如下:id:rstate:action:process,下面分别解释。

    1.id字段是最多4个字符的字符串,用来唯一标志表项。
    2.rstate(run state)字段定义该记录项被调用时的运行级别,rstate可以由一个或多个运行级别构成,也可以是空,空则代表运行级别0~6。当请求init改变运行级别时,那些rstate字段中不包括新运行级别的进程将收到SIGTERM警告信号,并且最后被杀死;只有a、b、c启动的命令外(a、b、c不是真正的运行级别)
    3.action字段告诉init执行的动作,即如何处理process字段指定的进程,action字段允许的值及对应的动作分别为:
    1)respawn:如果process字段指定的进程不存在,则启动该进程,init不等待处理结束,而是继续扫描inittab文件中的后续进程,当这样的进程终止时,init会重新启动它,如果这样的进程已存在,则什么也不做。
    2)wait:启动process字段指定的进程,并等到处理结束才去处理inittab中的下一记录项。
    3)once:启动process字段指定的进程,不等待处理结束就去处理下一记录项。当这样的进程终止时,也不再重新启动它,在进入新的运行级别时,如果这样的进程仍在运行,init也不重新启动它。
    4)boot:只有在系统启动时,init才处理这样的记录项,启动相应进程,并不等待处理结束就去处理下一个记录项。当这样的进程终止时,系统也不重启它。
    5)bootwait:系统启动后,当第一次从单用户模式进入多用户模式时处理这样的记录项,init启动这样的进程,并且等待它的处理结束,然后再进行下一个记录项的处理,当这样的进程终止时,系统也不重启它。
    6)powerfail:当init接到断电的信号(SIGPWR)时,处理指定的进程。
    7)powerwait:当init接到断电的信号(SIGPWR)时,处理指定的进程,并且等到处理结束才去检查其他的记录项。
    8)off:如果指定的进程正在运行,init就给它发SIGTERM警告信号,在向它发出信号SIGKILL强制其结束之前等待5秒,如果这样的进程不存在,则忽略这一项。
    9)ondemand:功能通respawn,不同的是,与具体的运行级别无关,只用于rstate字段是a、b、c的那些记录项。
    10)sysinit:指定的进程在访问控制台之前执行,这样的记录项仅用于对某些设备的初始化,目的是为了使init在这样的设备上向用户提问有关运行级别的问题,init需要等待进程运行结束后才继续。
    11)initdefault:指定一个默认的运行级别,只有当init一开始被调用时才扫描这一项,如果rstate字段指定了多个运行级别,其中最大的数字是默认的运行级别,如果rstate字段是空的,init认为字段是0123456,于是进入级别6,这样便陷入了一个循环,如果inittab文件中没有包含initdefault的记录项,则在系统启动时请求用户为它指定一个初始运行级别
    4.Process字段中进程可以是任意的守候进程、可执行脚本或程序。
    [color=red]另外:在任何时候,可以在文件inittab中添加新的记录项,级别Q/q不改变当前的运行级别,重新检查inittab文件,可以通过命令init Q或init q使init进程立即重新读取并处理文件inittab. 
     
    [编辑本段]
    passwd命令
      名称:passwd
      使用权限:所有使用者
      使用方式:passwd [-k] [-l] [-u [-f]] [-d] [-S] [username]
      说明:用来更改使用者的密码
      参数:
      -k
      -l 关闭账号密码。效果相当于usermod -L,只有root才有权使用此项。
      -u 恢复账号密码。效果相当于usermod -U,同样只有root才有权使用。
      -g 修改组密码。gpasswd的等效命令。
      -f
      -d 关闭使用者的密码认证功能, 使用者在登入时将可以不用输入密码, 只有具备 root 权限的使用者方可使用.
      -S 显示指定使用者的密码认证种类, 只有具备 root 权限的使用者方可使用.
      [username] 指定帐号名称.
     
    passwd文件
      通常在Linux系统中,用户的关键信息被存放在系统的/etc/passwd文件中,系统的每一个合法用户账号对应于该文件中的一行记录。这行记录定义了每个用户账号的属性。下面是一个passwd文件的示例(部分摘录):
      root:x:0:0:root:/root:/bin/bash
      bin:x:1:1:bin:/bin:/sbin/nologin
      daemon:x:2:2:daemon:/sbin:/sbin/nologin
      desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin
      mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash
      在该文件中,每一行用户记录的各个数据段用“:”分隔,分别定义了用户的各方面属性。各个字段的顺序和含义如下:
      注册名:口令:用户标识号:组标识号:用户名:用户主目录:命令解释程序shell
      (1)注册名(login_name):用于区分不同的用户。在同一系统中注册名是惟一的。在很多系统上,该字段被限制在8个字符(字母或数字)的长度之内;并且要注意,通常在Linux系统中对字母大小写是敏感的。这与MSDOS/Windows是不一样的。
      (2)口令(passwd):系统用口令来验证用户的合法性。超级用户root或某些高级用户可以使用系统命令passwd来更改系统中所有用户的口令,普通用户也可以在登录系统后使用passwd命令来更改自己的口令。
      现在的Unix/Linux系统中,口令不再直接保存在passwd文件中,通常将passwd文件中的口令字段使用一个“x”来代替,将/etc /shadow作为真正的口令文件,用于保存包括个人口令在内的数据。当然shadow文件是不能被普通用户读取的,只有超级用户才有权读取。
      此外,需要注意的是,如果passwd字段中的第一个字符是“*”的话,那么,就表示该账号被查封了,系统不允许持有该账号的用户登录。
      (3)用户标识号(UID):UID是一个数值,是Linux系统中惟一的用户标识,用于区别不同的用户。在系统内部管理进程和文件保护时使用UID字段。在Linux系统中,注册名和UID都可以用于标识用户,只不过对于系统来说UID更为重要;而对于用户来说注册名使用起来更方便。在某些特定目的下,系统中可以存在多个拥有不同注册名、但UID相同的用户,事实上,这些使用不同注册名的用户实际上是同一个用户。
      (4)组标识号(GID):这是当前用户的缺省工作组标识。具有相似属性的多个用户可以被分配到同一个组内,每个组都有自己的组名,且以自己的组标 识号相区分。像UID一样,用户的组标识号也存放在passwd文件中。在现代的Unix/Linux中,每个用户可以同时属于多个组。除了在passwd文件中指定其归属的基本组之外,还在/etc/group文件中指明一个组所包含用户。
      (5)用户名(user_name):包含有关用户的一些信息,如用户的真实姓名、办公室地址、联系电话等。在Linux系统中,mail和finger等程序利用这些信息来标识系统的用户。
      (6)用户主目录(home_directory):该字段定义了个人用户的主目录,当用户登录后,他的Shell将把该目录作为用户的工作目录。在Unix/Linux系统中,超级用户root的工作目录为/root;而其它个人用户在/home目录下均有自己独立的工作环境,系统在该目录下为每个用户配置了自己的主目录。个人用户的文件都放置在各自的
      主目录下。
      (7)命令解释程序(Shell):Shell是当用户登录系统时运行的程序名称,通常是一个Shell程序的全路径名,
      如/bin/bash。为了阻止一个特定用户登录系统,可用/dev/null作为其shell,或例子中的/sbin/nologin。
      需要注意的是,系统管理员通常没有必要直接修改passwd文件,Linux提供一些账号管理工具帮助系统管理员来创建和维护用户账号。

     
    通常在Linux系统中,用户的关键信息被存放在系统的/etc/passwd文件中,系统的每一个合法用户账号对应于该文件中的一行记录。这行记录定义了每个用户账号的属性。下面是一个passwd文件的示例(部分摘录):
    广告插播信息
    维库最新热卖芯片: LP2985IM5X-3.0 MB3776A N80C186XL10 AM29F002NBT-120JC TC8568AM LT1776 IR2113 LA7920 OPA4227PA STRS6309

      root:x:0:0:root:/root:/bin/bash

      bin:x:1:1:bin:/bin:/sbin/nologin

      daemon:x:2:2:daemon:/sbin:/sbin/nologin

      desktop:x:80:80:desktop:/var/lib/menu/kde:/sbin/nologin

      mengqc:x:500:500:mengqc:/home/mengqc:/bin/bash

      在该文件中,每一行用户记录的各个数据段用“:”分隔,分别定义了用户的各方面属性。各个字段的顺序和含义如下:

      注册名:口令:用户标识号:组标识号:用户名:用户主目录:命令解释程序

      (1)注册名(login_name):用于区分不同的用户。在同一系统中注册名是惟一的。在很多系统上,该字段被限制在8个字符(字母或数字)的长度之内;并且要注意,通常在Linux系统中对字母大小写是敏感的。这与MSDOS/Windows是不一样的。

      (2)口令(passwd):系统用口令来验证用户的合法性。超级用户root或某些高级用户可以使用系统命令passwd来更改系统中所有用户的口令,普通用户也可以在登录系统后使用passwd命令来更改自己的口令。

      现在的Unix/Linux系统中,口令不再直接保存在passwd文件中,通常将passwd文件中的口令字段使用一个“x”来代替,将/etc /shadow作为真正的口令文件,用于保存包括个人口令在内的数据。当然shadow文件是不能被普通用户读取的,只有超级用户才有权读取。

      此外,需要注意的是,如果passwd字段中的第一个字符是“*”的话,那么,就表示该账号被查封了,系统不允许持有该账号的用户登录。

      (3)用户标识号(UID):UID是一个数值,是Linux系统中惟一的用户标识,用于区别不同的用户。在系统内部管理进程和文件保护时使用 UID字段。在Linux系统中,注册名和UID都可以用于标识用户,只不过对于系统来说UID更为重要;而对于用户来说注册名使用起来更方便。在某些特 定目的下,系统中可以存在多个拥有不同注册名、但UID相同的用户,事实上,这些使用不同注册名的用户实际上是同一个用户。

      (4)组标识号(GID):这是当前用户的缺省工作组标识。具有相似属性的多个用户可以被分配到同一个组内,每个组都有自己的组名,且以自己的组标 识号相区分。像UID一样,用户的组标识号也存放在passwd文件中。在现代的Unix/Linux中,每个用户可以同时属于多个组。除了在 passwd文件中指定其归属的基本组之外,还在/etc/group文件中指明一个组所包含用户。

      (5)用户名(user_name):包含有关用户的一些信息,如用户的真实姓名、办公室地址、联系电话等。在Linux系统中,mail和finger等程序利用这些信息来标识系统的用户。

      (6)用户主目录(home_directory):该字段定义了个人用户的主目录,当用户登录后,他的Shell将把该目录作为用户的工作目录。 在Unix/Linux系统中,超级用户root的工作目录为/root;而其它个人用户在/home目录下均有自己独立的工作环境,系统在该目录下为每 个用户配置了自己的主目录。个人用户的文件都放置在各自的

      主目录下。

      (7)命令解释程序(Shell):Shell是当用户登录系统时运行的程序名称,通常是一个Shell程序的全路径名,

      如/bin/bash。

      需要注意的是,系统管理员通常没有必要直接修改passwd文件,Linux提供一些账号管理工具帮助系统管理员来创建和维护用户账号。

      Linux口令管理之/etc/passwd文件

      /etc/passwd文件是Linux/UNIX安全的关键文件之一.该文件用于用户登录时校验 用户的口令,当然应当仅对root可写.文件中每行的一般格式为:

      LOGNAME:PASSWORD:UID:GID:USERINFO:HOME:SHELL

      每行的头两项是登录名和加密后的口令,后面的两个数是UID和GID,接着的 一项是系统管理员想写入的有关该用户的任何信息,最后两项是两个路径名: 一个是分配给用户的HOME目录,第二个是用户登录后将执行的shell(若为空格则 缺省为/bin/sh).

      (1)口令时效

      /etc/passwd文件的格式使系统管理员能要求用户定期地改变他们的口令. 在口令文件中可以看到,有些加密后的口令有逗号,逗号后有几个字符和一个 冒号.如:

      steve:xyDfccTrt180x,M.y8:0:0:admin:/:/bin/sh

      restrict:pomJk109Jky41,.1:0:0:admin:/:/bin/sh

      pat:xmotTVoyumjls:0:0:admin:/:/bin/sh

      可以看到,steve的口令逗号后有4个字符,restrict有2个,pat没有逗号.

      逗号后第一个字符是口令有效期的最大周数,第二个字符决定了用户再次 修改口信之前,原口令应使用的最小周数(这就防止了用户改了新口令后立刻 又改回成老口令).其余字符表明口令最新修改时间.

      要能读懂口令中逗号后的信息,必须首先知道如何用passwd_esc计数,计 数的方法是:

      .=0 /=1 0-9=2-11 A-Z=12-37 a-z=38-63

      系统管理员必须将前两个字符放进/etc/passwd文件,以要求用户定期的 修改口令,另外两个字符当用户修改口令时,由passwd命令填入.

      注意:若想让用户修改口令,可在最后一次口令被修改时,放两个".",则下 一次用户登录时将被要求修改自己的口令.

      有两种特殊情况:

      . 最大周数(第一个字符)小于最小周数(第二个字符),则不允许用户修改 口令,仅超级用户可以修改用户的口令.

      . 第一个字符和第二个字符都是".",这时用户下次登录时被要求修改口 令,修改口令后,passwd命令将"."删除,此后再不会要求用户修改口令.

      (2)UID和GID

      /etc/passwd中UID信息很重要,系统使用UID而不是登录名区别用户.一般 来说,用户的UID应当是独一无二的,其他用户不应当有相同的UID数值.根据惯 例,从0到99的UID保留用作系统用户的UID(root,bin,uucp等).

      如果在/etc/passwd文件中有两个不同的入口项有相同的UID,则这两个用 户对相互的文件具有相同的存取权限.

      /etc /group文件含有关于小组的信息,/etc/passwd中的每个GID在本文件中 应当有相应的入口项,入口项中列出了小组名和小组中的用户.这样可方便地了 解每个小组的用户,否则必须根据GID在/etc/passwd文件中从头至尾地寻找同组 用户.

      /etc/group文件对小组的许可权限的控制并不是必要的,因为系统用UID,GID (取自/etc/passwd)决定文件存取权限,即使/etc/group文件不存在于系统中,具 有相同的GID用户也可以小组的存取许可权限共享文件.

      小组就像登录用户一样可以有口令.如果/etc/group文件入口项的第二个域 为非空,则将被认为是加密口令,newgrp命令将要求用户给出口令,然后将口令加 密,再与该域的加密口令比较.

      给 小组建立口令一般不是个好作法.第一,如果小组内共享文件,若有某人猜 着小组口令,则该组的所有用户的文件就可能泄漏;其次,管理小组口令很费事, 因为对于小组没有类似的passwd命令.可用/usr/lib/makekey生成一个口令写入 /etc/group.

      以下情况必须建立新组:

      (1)可能要增加新用户,该用户不属于任何一个现有的小组.

      (2)有的用户可能时常需要独自为一个小组.

      (3)有的用户可能有一个SGID程序,需要独自为一个小组.

      (4)有时可能要安装运行SGID的软件系统,该软件系统需要建立一个新组.

      要 增加一个新组,必须编辑该文件,为新组加一个入口项. 由于用户登录时,系统从/etc/passwd文件中取GID,而不是从/etc/group中 取GID,所以group文件和口令文件应当具有一致性.对于一个用户的小组,UID和 GID应当是相同的.多用户小组的GID应当不同于任何用户的UID,一般为5位数,这 样在查看/etc/passwd文件时,就可根据5位数据的GID识别多用户小组,这将减少 增加新组,新用户时可能产生的混淆.

    某些系统管理员会将shadow档隐藏到其他隐蔽的目录下。但多数情况下,你可以在/etc目录下找到。有一些shadow程序会将密码保存到master.passwd文件中。但只要你有root权限,总有地方可以找到它。

    =============我真的不希望我是分割线了,再解决不了就要烦了=======================

    明天看,照着过程继续看。。。

    ================================================================
     

    ——————
    无论在哪里做什么,只要坚持服务、创新、创造价值,其他的东西自然都会来的。
  • 相关阅读:
    POJ3624 01背包入门
    51Nod 1085 01背包
    linux配置安装tengine(centos6.5 )
    nginx列出目录
    Black and white painting
    Train Problem I
    快速排序的题
    数据结构实验之求二叉树后序遍历和层次遍历
    (转)最大子序列和问题 看着貌似不错
    数据结构实验之二叉树的建立与遍历
  • 原文地址:https://www.cnblogs.com/pied/p/1832081.html
Copyright © 2011-2022 走看看