Archive for the ‘Linux’ category

物理内存管理:vmalloc()的设计实现

May 26th, 2015

对于内核空间,根据不同的映射规则,可以将整个内核空间划分为四大部分:物理内存映射区、vmalloc区、永久内核映射区和固定映射的线性地址区域。这里我们只来谈谈vmalloc()的分配。首先我们来看一下之前的一个图:

phy_addr

vmalloc()区域是从VMALLOC_START开始,一直到VMALLOC_END结束,中间有PAGE_SIZE大小的guard_page作为保护,以防止防止非法的内存访问,内核中使用vm_struct结构来表示每个vmalloc区,也就是说,每次调用vmalloc()函数在内核中申请一段连续的内存后,都对应着一个vm_struct,kernel 中所有的vmalloc区组成一个链表,链表头指针为vmlist

static struct vm_struct *vmlist __initdata;

struct vm_struct {
    struct vm_struct    *next;
    void            *addr;
    unsigned long       size;
    unsigned long       flags;
    struct page     **pages;
    unsigned int        nr_pages;                                                                                                
    phys_addr_t     phys_addr;
    const void      *caller;
};

这里我们只介绍几个重要的字段:
next:所有的vm_struct结构组成一个vmlist链表,该字段指向下一个节点;
addr:代表这段子区域的起始地址;
size:表示区域的大小;
flags:表示该非连续内存区的类型,VM_ALLOC表示由vmalloc()映射的内存区,VM_MAP表示通过vmap()映射的内存区,VM_IOREMAP表示通过ioremap()将硬件设备的内存映射到内核的一段内存区;
pages:指针数组,该数组的成员是struct page*类型的指针,每个成员都关联一个映射到该虚拟内存区的物理页框;
nr_pages:pages数组中page结构的总数;
phys_addr:通常为0,当使用ioremap()映射一个硬件设备的物理内存时才填充此字段;
caller:表示一个返回地址;

 

vmalloc()的实现

vmalloc()内部封装了很多层函数,调用层次就是:vmalloc()-> __vmalloc_node_flags() -> __vmalloc_node() -> __vmalloc_node_range() ,通过这几层调用,就会向最终的__vmalloc_node_range()传入很多参数,GFP_KERNEL|__GFP_HIGHMEM表明内存管理子系统将从高端内存区(ZONE_HIGHMEM)中分配内存空间;NUMA_NO_NODE表示当前不是NUMA架构。

void *vmalloc(unsigned long size)
{
    return __vmalloc_node_flags(size, NUMA_NO_NODE,
                    GFP_KERNEL | __GFP_HIGHMEM);
}
static inline void *__vmalloc_node_flags(unsigned long size,                             
                    int node, gfp_t flags)
{
    return __vmalloc_node(size, 1, flags, PAGE_KERNEL,
                    node, __builtin_return_address(0));
}
static void *__vmalloc_node(unsigned long size, unsigned long align,                    
                gfp_t gfp_mask, pgprot_t prot,
                int node, const void *caller)
{
    return __vmalloc_node_range(size, align, VMALLOC_START, VMALLOC_END,
                gfp_mask, prot, node, caller);
}

这里我们进入真正分配vmalloc区域的函数__vmalloc_node_range(),我们只要关注第三个第四个参数,就是vm区域的开始地址与结束地址,__vmalloc_node_range()一开始会先修正一下size对齐,PAGE_ALIGN将size的大小修改成页大小的倍数。进行size合法性的检查,如果size为0,或者size所占页框数大于系统当前空闲的页框数(totalram_pages),将返回NULL,申请失败。

如果分配的内存区大小合法,__get_vm_area_node()中的alloc_vmap_area()将在整个非连续内存区中查找一个size大小的子内存区。该函数先遍历整个vmlist链表,依次比对每个vmalloc区,直到找到满足要求的子内存区结束。在这里面还做好了内核页表的映射等操作。

建立好了vm_struct 结构,下面就要分配使用__vmalloc_area_node()为这个vmalloc内存区分配真正的物理页。

void *__vmalloc_node_range(unsigned long size, unsigned long align,
            unsigned long start, unsigned long end, gfp_t gfp_mask,
            pgprot_t prot, int node, const void *caller)
{
    struct vm_struct *area;
    void *addr;
    unsigned long real_size = size;
                                                                                                                                 
    size = PAGE_ALIGN(size);
    if (!size || (size >> PAGE_SHIFT) > totalram_pages)
        goto fail;
    
    area = __get_vm_area_node(size, align, VM_ALLOC | VM_UNINITIALIZED,
                  start, end, node, gfp_mask, caller);
    if (!area)
        goto fail;
    
    addr = __vmalloc_area_node(area, gfp_mask, prot, node);
    if (!addr) 
        return NULL;
...    
    return addr;
....

__vmalloc_area_node()的实现

首先根据传入的vm_struct *area 获取要分配的物理页大小,这里的大小包括了guard_page,所以要在get_vm_area_size()减去这个PAGE_SIZE,翻译过来就是nr_pages = (area->size – PAGE_SIZE) >> PAGE_SHIFT;抹去低位,也就是要分配的page数,然后nr_pages * sizeof(struct page *)也就是真正的物理page大小。

根据这个array_size大小,我们可以判断要分配的是大于PAGE_SIZE还是小于PAGE_SIZE,大于的话就递归分配pages数组,小于的话,通过kmalloc_node()为pages数组分配一段连续的空间,这段空间位于内核空间的物理内存线性映射区。然后将分配好的page加入area,更新area中的pages。

static void *__vmalloc_area_node(struct vm_struct *area, gfp_t gfp_mask,
                 pgprot_t prot, int node)
{
    const int order = 0;
    struct page **pages;
    unsigned int nr_pages, array_size, i;
    const gfp_t nested_gfp = (gfp_mask & GFP_RECLAIM_MASK) | __GFP_ZERO;
    const gfp_t alloc_mask = gfp_mask | __GFP_NOWARN;                                                                            

    nr_pages = get_vm_area_size(area) >> PAGE_SHIFT;
    array_size = (nr_pages * sizeof(struct page *));

    area->nr_pages = nr_pages;
    /* Please note that the recursion is strictly bounded. */
    if (array_size > PAGE_SIZE) {
        pages = __vmalloc_node(array_size, 1, nested_gfp|__GFP_HIGHMEM,
                PAGE_KERNEL, node, area->caller);
        area->flags |= VM_VPAGES;
    } else {
        pages = kmalloc_node(array_size, nested_gfp, node);
    }
    area->pages = pages;

接下来通过一个循环为pages数组中的每个页面描述符分配真正的物理页框。page结构并不是代表一个具体的物理页框,只是用来描述物理页框的数据结构而已。如果node未指定物理内存所在节点,那么alloc_page()分配一个页框,并将该页框对应的页描述符指针赋值给page临时变量;否则通过alloc_pages_node()在指定的节点上分配物理页框。接着将刚刚分配的物理页框对应的页描述符赋值给pages数组的第i个元素。这里一旦某个物理页框分配失败则直接返回NULL,表示本次vmalloc()操作失败。

for (i = 0; i < area->nr_pages; i++) {
        struct page *page;

        if (node == NUMA_NO_NODE)
            page = alloc_page(alloc_mask);
        else
            page = alloc_pages_node(node, alloc_mask, order);

        if (unlikely(!page)) {
            /* Successfully allocated i pages, free them in __vunmap() */
            area->nr_pages = i;
            goto fail;
        }
        area->pages[i] = page;
        if (gfp_mask & __GFP_WAIT)
            cond_resched();
    }

    if (map_vm_area(area, prot, pages))
        goto fail;
    return area->addr;

fail:
    warn_alloc_failed(gfp_mask, order,
              "vmalloc: allocation failure, allocated %ld of %ld bytes\n",
              (area->nr_pages*PAGE_SIZE), area->size);
    vfree(area->addr);
    return NULL;
}

这里当kernel分配完page,并将信息输入到area中后,这些分散的页框并没有映射到area所代表的那个连续vmalloc区中。使用map_vm_area()将完成映射,它依次修改内核页表,将pages数组中的每个页框分别映射到连续的vmalloc区中。

 

map_vm_area()工作原理会在下一篇文章中说明!

[1] http://lxr.free-electrons.com/source/mm/vmalloc.c#L1557

Docker 使用

May 19th, 2015

Update 2015-6-3

最近项目需要使用到docker进行live migration。之前接触过lxc,所以这时两款原理上相同的容器虚拟化产品。我是在fedora 20下使用这个产品,在fedora20的官方仓库中有一个重名的软件包docker,我们需要使用docker-io安装。

安装完成以后就可以通过systemctl 启动docker daemon

$ sudo systemctl start docker

如果想开机启动那么使用enable命令,当我们安装好docker以后,可以使用docker ps查看,在目前我使用的1.5版本docker。首先我先pull一个镜像:

[root@localhost contrib]# docker pull ubuntu:latest
[root@localhost contrib]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
ubuntu              14.04.2             07f8e8c5e660        2 weeks ago         188.3 MB
ubuntu              latest              07f8e8c5e660        2 weeks ago         188.3 MB
ubuntu              trusty              07f8e8c5e660        2 weeks ago         188.3 MB
ubuntu              trusty-20150427     07f8e8c5e660        2 weeks ago         188.3 MB
ubuntu              14.04               07f8e8c5e660        2 weeks ago         188.3 MB
busybox             buildroot-2014.02   8c2e06607696        4 weeks ago         2.43 MB
busybox             latest              8c2e06607696        4 weeks ago         2.43 MB

pull完成以后,就可以通过docker images查看本机拥有的镜像,这时候我们使用docker run -d -t -i ubuntu /bin/sh可以启动镜像。

we’ve also passed in two flags: -t and -i. The -t flag assigns a pseudo-tty or terminal inside our new container and the -i flag allows us to make an interactive connection by grabbing the standard in (STDIN) of the container.

这时我们可以使用docker ps来查看本机启动的容器。

[root@localhost contrib]# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
f05df7249fea        ubuntu:14.04        "/bin/bash"         9 seconds ago       Up 6 seconds                            silly_hypatia

 

docker不同于lxc,主要使用一个64bit的id唯一标示一个容器,也是通过这个CONTAINER ID来标示,至于容器的停止,使用docker stop [CONTAINER ID] 或者是docker stop [NAMES]。删除一个镜像是docker rmi REPOSITORY:TAG即可,我们也可以使用别名机制来代替具体的CONTAINER ID。

[root@localhost contrib]# docker run -d -i -t --name busybox busybox:latest
3772693b1a82f996b296addf2f2ec00636535c81aa61730f66717d1311ae1b20
[root@localhost contrib]# docker stop busybox
busybox
[root@localhost contrib]# docker run -d -i -t --name busybox busybox:latest
FATA[0000] Error response from daemon: Conflict. The name "busybox" is already in use by container 3772693b1a82. You have to delete (or rename) that container to be able to reuse that name.
[root@localhost contrib]# docker run -d -i -t busybox:latest
5c331e6cddb00e134f39831ce1a6c5a9763c5ba60e1a41232503fce49fa17b09
[root@localhost contrib]# docker ps
CONTAINER ID        IMAGE                       COMMAND             CREATED             STATUS              PORTS               NAMES
5c331e6cddb0        busybox:buildroot-2014.02   "/bin/sh"           8 seconds ago       Up 6 seconds                            elated_meitner
f05df7249fea        ubuntu:14.04                "/bin/bash"         3 hours ago         Up 3 hours                              silly_hypatia

我们知道对于容器的动态迁移,利用pid进行C/R操作是十分有必要的,要想知道容器的pid,那么我们使用docker inspect busybox 查询当前容器的信息。我们看到这个容器的pid是768。

# docker inspect busybox
.....
"Path": "/bin/sh",
    "ProcessLabel": "",
    "ResolvConfPath": "/var/lib/docker/containers/f89a11d1cfe0037993dc4862ee218d59ccdcc4c7377fa4ed8946ed744226d8f6/resolv.conf",
    "RestartCount": 0,
    "State": {
        "Error": "",
        "ExitCode": 0,
        "FinishedAt": "0001-01-01T00:00:00Z",
        "OOMKilled": false,
        "Paused": false,
        "Pid": 768,
        "Restarting": false,
        "Running": true,
        "StartedAt": "2015-06-02T22:48:09.975364683Z"
    },
    "Volumes": {},
    "VolumesRW": {}
....

 

 

参考:

http://criu.org/Docker

http://docs.docker.com/userguide/dockerizing/

物理内存管理:Page的逆向映射

May 18th, 2015

kernel中包含着庞大的元数据struct page,一个struct page管理一个4k大小的物理内存(默认),之前我们进行的是从task_struct向vm_area_struct一直到最后的struct page的寻找过程。

但是从RAS角度,如果一个page发生问题,那么如何从struct page向上找到对应的page table呢(找到page table也就找到了pid、task_struct….)?这时候就要使用逆向映射

为了尽可能减小strut page的大小,Andrea复用了page的struct address_space *mapping数据结构来表示不同类型的页,主要分为三个类型swap、page cache、anonymous page。这里我们只说明page cache 与 anonymous page的逆向映射。

0.什么是匿名页?

想说明匿名页,必须首先说明什么是映射页。在用户态下打开一个文件,kernel会使用map() 映射文件的某个部分,这个部分在用户态地址空间存在地址,这种页要被回收的时候,会检查是否是dirty,如果为dirty,则需要写回相应的磁盘文件。

而匿名页没有对应了打开的磁盘文件,比如进程的用户态堆和stack可以称为匿名页,当匿名页过长时间驻留内存时,kernel可以要把它保存到一个特定的磁盘分区,这就是swap分区!

首先我们看一下struct page的结构:

struct page {
          /* First double word block */
          unsigned long flags;            /* Atomic flags, some possibly
                                           * updated asynchronously */
          union {
                  struct address_space *mapping;  /* If low bit clear, points to
                                                   * inode address_space, or NULL.
                                                   * If page mapped as anonymous
                                                   * memory, low bit is set, and
                                                   * it points to anon_vma object:
                                                   * see PAGE_MAPPING_ANON below.
                                                   */
                  void *s_mem;                    /* slab first object */
          };

          /* Second double word */
          struct {
                  union {
                          pgoff_t index;          /* Our offset within mapping. */
 ....

struct address_space *mapping是确定页是映射还是匿名

  • mapping为空表示该页属于交换高速缓存swap;
  • mapping非空,且最低位是1,表示该页为匿名页,同时mapping字段中存放的是指向anon_vma描述符的指针;
  • mapping非空,且最低位是0,表示该页为映射页;同时mapping字段指向对应文件的address_space对象。

而第二部分的pgoff_t index是用来指明偏移量的,在映射页中是以页为单位偏移。

1.当我们判断这个页是映射页时,每个page的mapping都指向一个对应的address_space,这个结构是page cache的核心,可以通过这个找到具体的inode!

2.当我们判断这个页是匿名页时,那么这个page指向struct anon_vma

但是这里kernel在2010年提交过一个补丁,为了解决大量fork出来的子进程占用大量anon_vma结构,他们的结构完全相同,而且这种结构导致匿名映射寻找也是O(N)的复杂度。所以引入了struct anon_vma_chain

假设系统只有一个task_struct时,组织结构如下:

anon_map

struct anon_vma_chain {
         struct vm_area_struct *vma;
         struct anon_vma *anon_vma;
         struct list_head same_vma;   /* locked by mmap_sem & page_table_lock */
         struct rb_node rb;                      /* locked by anon_vma->rwsem */
         unsigned long rb_subtree_last;
 #ifdef CONFIG_DEBUG_VM_RB
         unsigned long cached_vma_start, cached_vma_last;
 #endif
};

这样子就形成了anon_vma与vm_area_struct N对N的组织形式,当父进程fork出一个子进程,那么这个子进程拥有一个vm_area_struct,当然也就拥有一个anon_vma,除非发生COW,否则父子进程共享vm_area_struct。

this patch changes the way anon_vmas and VMAs are linked, which allows us to associate multiple anon_vmas with a VMA. At fork time, each child process gets its own anon_vmas, in which its COWed pages will be instantiated. The parents’ anon_vma is also linked to the VMA, because non-COWed pages could be present in any of the children.

3.刚才说的是正向的一个匿名页查找,现在我们反过来,一直一个struct page,找到这个匿名页的page table。结合正向的结构图与相关struct成员,我们可以画出相应的结构图,这里page指向anon_vma就是通过mapping做到的:

 

anonvma2

 

而anon_vma (AV),anon_vma_chain entry (AVC),vm_area_struct(VMA)三者的关系如下

avchain1

当father process fork()出来子进程后,会产生新的struct anon_vma ,这个结构就会分裂,创建VMA->创建AVC->创建AV。

avchain4

 

 

 

参考:

http://lwn.net/Articles/335768/

http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=5beb49305251e5669852ed541e8e2f2f7696c53e

Linux内存管理:SLAB分配器

May 15th, 2015

之前学习过linux物理内存管理中的伙伴系统,他的分配单位是以page为单位分配。对于远远小于page的内存分配请求,比如几个字节~几百个字节,如果分配一个页框,那就是极大地浪费,会产生内部碎片,这时SLAB分配器就派上用场了。

与slab有相同地位的有slob(simple linked list of block)与slub两种备用类型的内存分配器,slob主要是使用内存块链表展开,使用最先适配算法,主要是使用在嵌入式系统,对于内存分配紧张的的系统。对于拥有大量物理内存的并行系统,slab会占用非常大的内存来存储元数据,而slub会将page打包成组,所以这里主要使用slub作为大型系统的内存分配方案。

slab、slob、slub 三者都拥有相同的内核分配函数接口。三者的选择主要在编译时就要选择确定下来。我们以slab作为下面说明的,slab会将某种特定类型的数据集中分配在一起,并且将这些对象放到高速缓存中,方便存取。

举例:进程描述符,当新进程创建时,内核直接从slab中获取一个初始化好的对象,当释放后,这个page不会放到伙伴系统,而是放入slab中。

slab分配器在最上层拥有节点是struct kmem_cache,他是slab分配器的核心结构体

struct kmem_cache {
    struct array_cache __percpu *cpu_cache;
/* 1) Cache tunables. Protected by slab_mutex */
    unsigned int batchcount;
    unsigned int limit;
    unsigned int shared;
    unsigned int size;
    struct reciprocal_value reciprocal_buffer_size;
/* 2) touched by every alloc & free from the backend */
    unsigned int flags;     /* constant flags */
    unsigned int num;       /* # of objs per slab */
/* 3) cache_grow/shrink */       

    /* order of pgs per slab (2^n) */
    unsigned int gfporder;
    /* force GFP flags, e.g. GFP_DMA */  

    gfp_t allocflags;
    size_t colour;          /* cache colouring range */
    unsigned int colour_off;    /* colour offset */
    struct kmem_cache *freelist_cache;
    unsigned int freelist_size;                                                                   

    /* constructor func */
    void (*ctor)(void *obj);
/* 4) cache creation/removal */
    const char *name;
    struct list_head list;
    int refcount;
    int object_size;
    int align;

/* 5) statistics */
#ifdef CONFIG_DEBUG_SLAB
.....
#endif /* CONFIG_DEBUG_SLAB */
#ifdef CONFIG_MEMCG_KMEM
    struct memcg_cache_params *memcg_params;
#endif

    struct kmem_cache_node *node[MAX_NUMNODES];
};

这里至于管理slab的节点的数据结构就是struct kmem_cache_node,每个kmem_cache结构中并不包含对具体slab的描述,而是通过kmem_cache_node结构组织各个slab。该结构的定义如下:

struct kmem_cache_node {
    spinlock_t list_lock;

#ifdef CONFIG_SLAB
    struct list_head slabs_partial; /* partial list first, better asm code */
    struct list_head slabs_full;
    struct list_head slabs_free;
    unsigned long free_objects;
    unsigned int free_limit;
    unsigned int colour_next;   /* Per-node cache coloring */
    struct array_cache *shared; /* shared per node */
    struct alien_cache **alien; /* on other nodes */
    unsigned long next_reap;    /* updated without locking */
    int free_touched;       /* updated without locking */
#endif
...
};

可以看到,该结构将当前缓存中的所有slab分为三个部分:空闲对象的slab链表slabs_free,非空闲对象的slab链表slabs_full以及部分空闲对象的slab链表slabs_partial。至于在链上的结构体,在kernel 3.11前后发生了重大变化,在3.11前的版本。使用struct slab来管理slab资源。

struct slab {
    union {
        struct {
            struct list_head list;
            unsigned long colouroff;
            void *s_mem;        /* including colour offset */
            unsigned int inuse; /* num of objs active in slab */
            kmem_bufctl_t free;
            unsigned short nodeid;
        };
        struct slab_rcu __slab_cover_slab_rcu;
    };
}

在3.11之后Joonsoo Kim 提出方案认为大量的slab对象严重占用内存,所以之后struct slab融合进struct page结构体。显著降低了元数据的内存使用量,具体查看 。他并对修改后的struct page 进行了介绍 。

也就是slabs_partial 链接的是一个个struct page结构体。根据这几个结构体的关系,我们可以总结出slab的结构

slab

在kernel 3.11 以前,结构图是:

1339687849_2988

最后还要说明struct array_cache 也是一个重要的结构体,cpu首先分配一项专有object是通过struct array_cache来进行分配,这个结构体是每个cpu都会存在一个。

struct array_cache {
	unsigned int avail;/*本地高速缓存中可用的空闲对象数*/
	unsigned int limit;/*空闲对象的上限*/
	unsigned int batchcount;/*一次转入和转出的对象数量*/
	unsigned int touched;   /*标识本地CPU最近是否被使用*/
	spinlock_t lock;
	void *entry[];	/*这是一个伪数组,便于对后面用于跟踪空闲对象的指针数组的访问
			 * Must have this definition in here for the proper
			 * alignment of array_cache. Also simplifies accessing
			 * the entries.
			 */
};

在每个array_cache的末端都用一个指针数组记录了slab中的空闲对象,分配对象时,采用LIFO方式,也就是将该数组中的最后一个索引对应的对象分配出去,以保证该对象还驻留在高速缓存中的可能性。实际上,每次分配内存都是直接与本地CPU高速缓存进行交互,只有当其空闲内存不足时,才会从kmem_list中的slab中引入一部分对象到本地高速缓存中,而kmem_list中的空闲对象也不足了,那么就要从伙伴系统中引入新的页来建立新的slab了,这一点也和伙伴系统的每CPU页框高速缓存很类似。

slab高速缓存分为两类,普通高速缓存和专用高速缓存。

  • 普通高速缓存并不针对内核中特定的对象,它首先会为kmem_cache结构本身提供高速缓存,这类缓存保存在cache_cache变量中,该变量即代表的是cache_chain链表中的第一个元素;
  • 专用高速缓存为内核提供了一种通用高速缓存。专用高速缓存是根据内核所需,通过指定具体的对象而创建。

最后使用这种slab分配非常简单

1.创建

struct kmem_cache *cachep = NULL;
cachep = kmem_cache_create("cache_name", sizeof(struct yourstruct), 0, SLAB_HWCACHE_ALIGN, NULL, NULL);

2.分配一个struct yourstruct的结构体空间时

调用kmem_cache_alloc函数,就可以获得一个足够使用的空间的指针(SLAB_HWCACHE_ALIGN,这个标志会让分配的空间对于硬件来说是对齐的,而不一定恰好等于sizeof(struct yourstruct)的结果)。范例代码如下:

struct yourstruct *bodyp = NULL;
bodyp = (struct yourstruct *) kmem_cache_alloc(cachep, GFP_ATOMIC &amp; ~__GFP_DMA);

3.销毁

kmem_cache_free(cachep, bodyp);

 

参考:

http://lwn.net/Articles/570504/
https://lwn.net/Articles/565097/
https://lwn.net/Articles/335768/
http://blog.csdn.net/vanbreaker/article/details/7664296

由lxc-checkpoint想到的 …

May 12th, 2015

在 LXC 1.1版本以后,lxc整合了criu的功能,使得可以checkpoint一个正在运行的容器。但是有时候我们会出现lxc.tty must be 0的文字,这个就意味着我们必须在lxc 的config中加入特定的选项

cat | sudo tee -a /var/lib/lxc/u1/config << EOF
# hax for criu
lxc.console = none
lxc.tty = 0
lxc.cgroup.devices.deny = c 5:1 rwm
EOF

但是我发现了一个问题:到底什么是tty,他是由什么管理的?

我们都知道在unix下有非常多的终端:bash、zsh、sh、ssh等,这些终端程序就是我们输入命令的窗口,至于配置用户的终端,当然是在/etc/passwd下面。

但是是哪个程序调用了/bin/bash呢?使用strace跟踪这个/bin/login(strace -f -o /tmp/strace.log /bin/login),可以发现里面存在execve系统调用,这个调用执行了 /bin/login程序。而login又是谁调用的呢?经过查看是getty。getty是在自己的主进程里头直接执行了/bin/login,这样/bin/login将把getty的进程空间替换掉。

而init需要读取/etc/inittab来做,inittab,目前不再被systemd使用,这里就有/etc/rc.d/一系列脚本完成。
根据系统启动原理,我们可以发现调用过程:

init –> init –> /sbin/getty –> /bin/login –> /bin/login –> /bin/bash

这里的execve调用以后,后者将直接替换前者,我们要知道一点:因为终端程序之间有父子关系的存在,当子进程exit之后,父进程要进行处理,否则就是zombie进程。因此当我们键入exit退出/bin/bash以后,也就相当于/sbin/getty都已经结束了, 因此最前面的init程序判断/sbin/getty退出了,又会创建一个子进程把/sbin/getty启动,进而又启动了/bin/login,又看 到了那个”XXX login:”

一般情况下,系统内置程序会比自己编写的更加优先被执行,按照系统内置规则,一般首先是程序别名,然后是shell function,之后是系统内置函数(builtin ),最后才是自己编写的函数(program )!

总的来说:先    alias –> shell function –> builtin –> program   后 

 

参考:

[1] man boot-scripts Linux启动过程
[2] man bootparam  Linux内核启动参数
[3] man 5 passwd
[4] man shadow