02.linux 进程

来源:互联网 发布:深入php 编辑:程序博客网 时间:2024/06/05 05:21


一. 进程概述

1. 概述
     1)进程,表示CPU执行程序的动作。它有自己的地址空间(在内存中,有独立的虚拟地址空间和内核地址空间),里面有代码区,数据区及堆栈等。在内核地址空间中,每个进程都有一个task_struct数据结构对应,这个数据结构称为进程描述符,用于记录进程状态。
     2)CPU有内核态和用户态,当处于内核态时,当前进程可以执行其内核地址空间的代码;当处于用户态时,当前进程可以执行其虚拟地址空间的代码。
   备注:
       1)进程,也可以表示运行程序的“执行上下文”。  
       2)android系统启动时,会创建一个init进程,我们可以称作其为本地进程,然后通过fork()复制init进程来创建其他本地进程,如vold进程,servicemanager进程,zygote进程 。其中,zygote进程初始化了VM虚拟机,因此,该进程可以跑java代码。所以,通过fork()复制zygote进程而创建的进程,我们可以称作java进程。
            从本质上来说,java进程也是本地进程,只是它在本地进程的基础上初始化了VM虚拟机,能够跑java代码。
               



2.关键数据结构
2.0 struct task_struct
          概述:进程描述符,用于标识一个进程
          结构:
struct task_struct {
pid_t pid; //当前进程的进程号
pid_t tgid; //当前进程中的线程号
/* process credentials */
const struct cred __rcu *cred;     //进程的主体凭证集,用于进程访问其他对象时提供自己的凭证集进行安全性检查
const struct cred __rcu *real_cred;  //进程的客体凭证集,用于进程被访问时的安全性检查
char comm[TASK_COMM_LEN]; //当前进程执行的程序文件的名称
void *stack; //指向进程描述符的线程描述符(thread_info )

struct fs_struct *fs; //filesystem information,记录进程所在的文件系统信息
struct files_struct *files; //open file information,记录当前进程打开的所有文件的文件描述符信息
struct nsproxy *nsproxy; //进程的命名空间

struct task_struct *real_parent; /* real parent process */
struct task_struct *parent; /* recipient of SIGCHLD, wait4() reports */
struct list_head children;    /* list of my children */
int link_count, total_link_count; //total_link_count,记录符号链接的深度
struct mm_struct *mm, *active_mm; //内存描述符,用于记录一个进程使用内存的信息
......
};
2.1 struct thread_info
概述:线程描述符
格式:
struct thread_info {
    unsigned long        flags;        /* low level flags */
    int            preempt_count;    /* 0 => preemptable, <0 => bug */
    mm_segment_t        addr_limit;    /* address limit */
    struct task_struct    *task;    //指向所属进程的进程描述符    /* main task structure */
    struct exec_domain    *exec_domain;    /* execution domain */
    __u32            cpu;        /* cpu */
    __u32            cpu_domain;    /* cpu domain */
    struct cpu_context_save    cpu_context;    /* cpu context */
    __u32            syscall;    /* syscall number */
    __u8            used_cp[16];    /* thread used copro */
    unsigned long        tp_value;
    struct crunch_state    crunchstate;
    union fp_state        fpstate __attribute__((aligned(8)));
    union vfp_state        vfpstate;
#ifdef CONFIG_ARM_THUMBEE
    unsigned long        thumbee_state;    /* ThumbEE Handler Base register */
#endif
    struct restart_block    restart_block;
};
2.2 struct mm_struct
          概述:内存描述符,用于记录一个进程使用内存的信息
          结构:
               struct mm_struct {
                   struct vm_area_struct * mmap;      //进程所拥有的所有线性区对象连成一个链表,该指针指此向链表头
                   struct rb_root mm_rb;                    //进程所拥有的所有线性区对象组成一个红黑树,该结构图指向红黑树的根
                   struct list_head mmlist;                  //所有进程的内存描述符通过mmlist链接成链表,进程0(swapper进程)的内存描述符为init_mm,它是该链表的链表头
                   atomic_t mm_users;                       //记录有多少个轻量级进程(线程)共享此内存描述符
                   atomic_t mm_count;                       //内存描述符的主使用计数器,如果为0,则要解除这个内存描述符。mm_users次使用计数器中的所有用户在mm_count中只作为一个单位
                   struct mm_struct *mm, *active_mm; //指向进程运行时所使用的内存描述符
                   struct vm_area_struct * mmap_cache;  //指向最后一个引用的线性区对象
                   pgd_t * pgd;    // 指向内核页表中的主内核页全局目录
                    ......
               }
获取:struct mm_struct *mm_alloc(void)          //该方法通过slab分配对象内存,最终调用kmem_cache_alloc()生成
释放:static inline void mmdrop(struct mm_struct * mm)          // 最终调用kmem_cache_free()释放
2.3 struct vm_area_struct
          概述:线性区描述符,用于标识一块线性区,线性区的首地址和大小必须是4k的倍数
          结构:
struct vm_area_struct {
       unsigned long vm_start;           //包含线性区的第一个线性地址
       unsigned long vm_end;           //包含线性区之外的第一个线性地址,vm_end - vm_star表示线性区的长度
       struct vm_area_struct *vm_next, *vm_prev;     //进程所拥有的所有线性区链接成一个链表(该链表的表头由该进程的内存描述符的mmap指向),vm_next指向链表的下一个线性区,vm_prev指向链表的上一个线性区
       struct rb_node vm_rb;     //进程所拥有的所有线性区对象组成一个红黑树(该红黑树的根节点由该进程的内存描述符的mm_rb 指向),vm_rb为红黑树的节点
       struct mm_struct *vm_mm;     //指向拥有这个线性区的进程的内存描述符
}
2.4 struct nsproxy  - 命名空间描述符
     概述:每个进程,都有一个自己的命名空间
struct nsproxy {
     atomic_t count;
     struct uts_namespace *uts_ns;     //UTS命名空间包含了运行内核的名称、版本、底层体系结构类型等信息
     struct ipc_namespace *ipc_ns;     //所有与进程间通信(IPC)有关的信息
     struct mnt_namespace *mnt_ns;     //指向当前进程已经装载的文件系统的命名空间
     struct pid_namespace *pid_ns;     //有关进程ID的信息
     struct net           *net_ns;          //所有网络相关的命名空间参数
};
2.5 struct files_struct
a. 概述:在内核中,用struct files_struct数据结构记录进程(struct task_struct)其打开的所有文件的对象信息
b. 格式:
include/linux/sched.h
struct files_struct {
    // read mostly part
    atomic_t count; //共享该表的进程数
    struct fdtable __rcu *fdt;
    struct fdtable fdtab;
    //written part on a separate cache line in SMP
    spinlock_t file_lock ____cacheline_aligned_in_smp;
    int next_fd;
    struct embedded_fd_set close_on_exec_init;
    struct embedded_fd_set open_fds_init;
    struct file __rcu * fd_array[NR_OPEN_DEFAULT]; //文件对象指针的初始化数组
};

struct fdtable {
    unsigned int max_fds; //文件对象的最大数
    struct file __rcu **fd; //current fd array ,指向文件对象指针数组,该数组的索引就是对应的文件对象的文件描述符(一个int类型的整数)
    fd_set *close_on_exec;
    fd_set *open_fds; //记录打开的文件对象的文件对象描述符,在open文件的时候设置("__set_bit(fd, fdt->open_fds);")
    struct rcu_head rcu;
    struct fdtable *next;
};
2.6 struct fs_struct
a. 概述:记录进程所在的文件系统信息
b. 格式:
struct fs_struct {
    int users;
    spinlock_t lock;
    seqcount_t seq;
    int umask;
    int in_exec;
    struct path root, pwd; //root记录当前进程所在的根目录,pwd记录当前进程所在目录
};
struct path {
    struct vfsmount *mnt;
    struct dentry *dentry;
};
2.7 thread_union
a. 概述:占据两个连续的页框,用于存储进程的内核栈与进程的线程描述符
b. 格式:
union thread_union {
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};
2.8 struct cred
a. 概述:进程凭证集
b. 格式:
struct cred {
    atomic_t    usage; //表示凭证的引用管理
    kuid_t        uid;   //实际用户ID     /* real UID of the task */
    kgid_t        gid;   //实际用户组ID     /* real GID of the task */
    kuid_t        suid;  //保存的用户uid      /* saved UID of the task */
    kgid_t        sgid;  //保存的用户组gid      /* saved GID of the task */
    kuid_t        euid;  //真正有效的用户id      /* effective UID of the task */
    kgid_t        egid;  //真正有效的用户组id      /* effective GID of the task */
    kuid_t        fsuid;        /* UID for VFS ops */
    kgid_t        fsgid;        /* GID for VFS ops */
    unsigned    securebits;   //安全管理标识,用来控制凭证的操作与继承 /* SUID-less security management */
    kernel_cap_t    cap_inheritable; //execve时,可以继承的权限/* caps our children can inherit */
    kernel_cap_t    cap_permitted;  //可以(通过capset)赋予cap_effective的权限   /* caps we're permitted */
    kernel_cap_t    cap_effective;   //进程实际使用的权限 /* caps we can actually use */
   //主要用于uid=0或euid=0时,execve可以继承的权限,cap_permitted=cap_inheritable+cap_bset,cap_effective=cap_permitted。可以将cap_bset中的权限通过调用capset赋给cap_inheritable
    kernel_cap_t    cap_bset;    /* capability bounding set */
    struct user_struct *user;   //主要表示用户信息,如用户进程数、打开文件数等 /* real user ID subscription */
    struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
    struct group_info *group_info;    /* supplementary groups for euid/fsgid */
    struct rcu_head    rcu;        /* RCU deletion hook */
};
2.9 struct user_struct
a. 概述
          表示进程的用户信息。在进程主凭证集中,表示创建该进程的用户的用户信息;在进程客观凭证集中,表示访问该进程的用户的用户信息。
b. 格式
struct user_struct {
    atomic_t __count;    /* reference count */
    atomic_t processes;    /* How many processes does this user have? */
    atomic_t files;        /* How many open files does this user have? */
    atomic_t sigpending;    /* How many pending signals does this user have? */
    unsigned long locked_shm; /* How many pages of mlocked shm ? */
    struct hlist_node uidhash_node;
    uid_t uid;
    struct user_namespace *user_ns;
    ......
};
2.10 struct cpu_context_save
a. 概述:
     记录进程执行时cpu寄存器的值
b. 格式:
struct cpu_context_save {
    __u32    r4;
    __u32    r5;
    __u32    r6;
    __u32    r7;
    __u32    r8;
    __u32    r9;
    __u32    sl;
    __u32    fp;
    __u32    sp;
    __u32    pc;
    __u32    extra[2];        /* Xscale 'acc' register, etc */
};

3. 进程
3.1 进程的地址空间
3.1.1 概述:
1)进程的地址空间(内存空间),指的是进程所占有的内存的物理地址空间。每个进程所占有的内存物理地址空间由两部分组成:1.所用进程共享的低端内存;2. 各自进程独自占有的部分高端内存。
2)通常,内存物理地址不能够被直接使用,只有通过映射成线性地址,才能够被进程使用。
3.1.2 进程的线性地址空间
1)概述:
a. 在CPU启动MMU(内存管理单元)后,内存物理地址只有通过映射成为线性地址后,才能被进程使用(DMA区域除外,该区域可以不映射就直接使用)。
b. 进程的地址空间通过映射成为进程的线性地址空间,进程的线性地址空间可能没有内存物理地址对应,因为只有在申请内存的时候才会为线性地址空间分配内存物理地址空间,所以在32位CPU下,进程的线性地址空间,最大为4G。(进程的线性地址空间,指的是允许进程使用的全部线性地址的集合,它由若干个线性区(线性地址空间的一段区域)组成。通常. 进程的内存空间,指的就是进程的线性地址空间 )
c. 进程的线性地址空间分为内核区线性地址空间和应用区线性地址空间。进程有两种状态,当进程处于用户态时,进程运行在应用线性地址空间;当进程处于内核态时,进程运行在内核线性地址空间。在内核线性地址空间中,进程不是直接运行内核代码,而是由内核线程代替进程执行内核代码(通过系统调用),此时处于进程上下文中。
d. 所有的进程,它们的内核线性地址空间的是共享的,但是应用线性地址空间大小各不相同,并且各个进程的应用线性地址空间之间相互独立(如一个进程的线性地址空间为0x00000000-0x10000000,另外一个进程的线性地址空间也可以是这个区间,虽然线性地址空间一样,但是所映射到的内存却是不一样的)。
2)内存空间区域
a. 进程内核区线性地址空间:高于TASK_SIZE(x86平台为0xc0000000,arm平台为#define TASK_SIZE        (UL(CONFIG_PAGE_OFFSET) - UL(SZ_16M)),其中CONFIG_PAGE_OFFSET  = 0xc0000000)。每个进程的内核线性地址空间区域是共享的,这部分地址空间由内核的一个主内核页全局目录维护映射。
b. 进程应用区线性地址空间:低于TASK_SIZE。每个进程都有自己的页表独立维护。
3.2 进程的线性地址空间的结构
3.2.1 概述:
1)系统固件刷写在磁盘中,当点击一个apk应用程序时,系统会创建一个进程来执行该应用程序,并会将存储在磁盘中的该应用程序的代码加载到进程内存空间的代码段中,并把代码中定义的静态变量和已初始化的全局变量加载到进程的数据段中,把代码中定义的未初始化的全局变量加载到BSS段中 ,并全部置0。
2)代码中定义的局部变量(如函数中定义的变量,类中定义的变量),在没有实例化之前,都属于代码段。当实例化后,局部变量才会加载到进程的栈中。
3.2.2 空间结构:
1)代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存中的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入(修改)操作——它是不可写的。
2)数据段:数据段用来存放可执行文件中静态分配的变量和已初始化全局变量
3)BSS段[2]:BSS段包含了程序中未初始化的全局变量,在内存中 bss段全部置零
4)堆(heap)参考,4.进程的堆与栈 - > 4.2 进程的堆(Heap)
5)栈(Stack)参考,4.进程的堆与栈  -> 4.1 进程的栈(Stack)
                    
               
3.3 进程的线性区      
      3.3.1 进程线性区的分配与释放
                    1)分配:
unsigned long do_mmap_pgoff(struct file *file, unsigned long addr,
            unsigned long len, unsigned long prot,
            unsigned long flags, unsigned long pgoff,
            unsigned long *populate)
                              参数:
file - 如果新的线性区将把一个文件映射到内存,则使用文件描述符指针file
addr - 指定从addr开始查找一个空闲的区间
len - 线性地址区间的长度
prot - 这个线性区所包含页的访问权限
备注:请求调用,是一种动态内存分配技术,即推迟页框映射。在给进程分配了线性区,并不代表这些线性区就立刻分配了内存页框,当cpu访问到这些线性区地址时,通过缺页异常为这些线性区分配内存页框。
                    2)释放:
int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)
      3.3.2 缺页异常
                    1)概述:缺页异常处理程序必须区分以下两种情况,1,由编程错误引起的异常;2,由引用属于进程地址空间但尚未分配物理页框的页所引起的异常。
                    2)缺页异常处理函数:
static int __kprobes do_page_fault(unsigned long addr, unsigned int esr,struct pt_regs *regs)
3.4 进程创建
      3.4.1 创建普通进程
                    概述:
     1)用户在用户态通过系统调用fork()创建一个新的普通进程,最终通过clone()实现(flags指定SIGCHLD,其他所有的clone标志清0)
          备注:clone()最终由do_fork()实现,它首先为新进程创建进程描述符task_struct,然后为新进程分配线性地址空间及物理地址空间。
          用户态 fork()系统调用 - > 内核态 do_fork() ->用户态进程号PID
                1)long pid = alloc_pidmap();//进程号
                2)struct task_struct *p;
                     p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid); //进程描述符     
     2)无写时复制时,P1进程用fork()函数创建一个子进程P2,P1为P2创建线性地址空间。除了P1,P2共享代码段物理页框,P1为P2创建它自己的数据段,堆,栈对应的物理页框,且把P1对应的内容复制过去。
     
     3)有写时复制技术时,P1进程用fork()函数创建一个子进程P2,P1为P2创建线性地址空间,并且父子进程首先共享物理页框,这些共享的页框处于写保护状态(mm_users)。当其中一个进程试图写一个共享的页框时,就产生一个异常,这时内核为子进程分配页框,并把父页框中的内容复制到这个新页框中并标记为可写(原页框属主减1)。原来的页框仍然是写保护状态,当其他进程试图写入时,内核检查写进程是否是这个页框的唯一属主,如果是,就把这个页框标记为对这个进程是可写的。
     
                                    4)最终,父子进程各自都有独立的数据段,堆,栈(物理页框独立),其中代码段共享
                                    5)如果你要查看某个进程占用的内存区域,可以使用命令cat /proc/<pid>/maps获得
                                   参考:
http://blog.sina.com.cn/s/blog_7673d4a5010103x7.html
http://www.cnblogs.com/lonelycatcher/archive/2011/12/17/2291311.html
http://www.cnblogs.com/biyeymyhjob/archive/2012/07/20/2601655.html

        3.4.2 vfork()创建子进程
1)用户在用户态通过系统调用vfork()创建子进程,父子进程共享线性地址空间,进而共享物理页框。最终通过clone()实现(flags指定SIGCHLD和CLONE_VM及CLONE_VFORK标志,clone()的参数child_stack=父进程当前的栈指针)
2)为了防止父进程重新子进程需要的数据,阻塞父进程的执行,一直到子进程退出或执行一个新的重新为止
     
       3.4.3 创建轻量级进程(线程)
               概述:
1)用户在用户态通过pthread_create()函数创建用户态线程,最终通过系统调用clone()创建轻量级进程(线程) 。(线程与进程共享进程描述符)
int pthread_create(pthread_t* thread_out, pthread_attr_t const* attr,
                   void* (*start_routine)(void*), void* arg) {
  int flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM |
      CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID;
  int rc = clone(__pthread_start, child_stack, flags, thread, &(thread->tid), tls, &(thread->tid));
......
} //start_routine参数是线程执行的函数代码,arg是传给该函数的参数
2)进程与线程共享物理内存页框,共享线性地址空间,但是各个线性在共享的物理页框中,创建各自独立的栈
do_fork()->copy_process()->dup_task_struct()fork.c中dup_task_struct()的实现:
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
     struct task_struct *tsk;
     struct thread_info *ti;
     unsigned long *stackend;
     int node = tsk_fork_get_node(orig);
     int err;
     tsk = alloc_task_struct_node(node);
     if (!tsk)return NULL;
     ti = alloc_thread_info_node(tsk, node);/*就是这里,果然分配内核栈了*/
     if (!ti)goto free_tsk;err = arch_dup_task_struct(tsk, orig);/*这里分配task_struct结构*/
     if (err)goto free_ti;
     tsk->stack = ti;
     ...
}
3)内核线程通过kernel_thread()函数创建,最终由do_fork()实现。
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
     return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn,
          (unsigned long)arg, NULL, NULL);
}
4)每个用户态线程,在内核空间都有一个内核线程相对应

3.5 android的内核线程,linux进程,普通进程,轻量级进程
3.5.1 内核线程
a. 概述:
1)内核线程,只运行在内核态,可以在全系统范围内竞争处理器资源,唯一使用的资源是内核栈和上下文切换时保持寄存器的空间。
2)内核线程只访问内核线性地址空间,不访问用户线性地址空间。
3)每个内核线程都拥有自己独立的进程描述符、PID、进程正文段、内核堆栈
4)内核线程的创建:kernel_thread()参见,7. 进程/线程创建的内核接口 - do_fork 。关键flag,CLONE_VM
5)内核线程,也是一个普通进程,只是,该普通进程只能运行在内核态
3.5.2 普通进程
a. 概述:
1)普通进程,能够运行在内核态,也能够运行在用户态。
2)普通进程是在内核线程的基础上创建来的,所以每个普通进程在内核都有一个内核线程唯一对应
3)在内核中,首先创建内核线程(在内核中,子进程也称作内核线程,即只在内核中运行的进程),当内核线程执行linux应用程序/sbin/init("run_init_process("/sbin/init")" ),启动linux进程,在linux进程中创建java虚拟机VM,形成完整的普通进程。此时普通进程可以执行c代码和Java代码。
4)每个普通进程都拥有自己独立的进程描述符、PID、进程正文段、内核堆栈 、用户空间的数据段,用户空间堆栈
5)普通进程的创建:
init进程创建:
1. 创建内核线程(init内核线程),参见,7. 进程/线程创建的内核接口 - do_fork 。关键flag,CLONE_VM
2. 启动普通进程(init进程 - /sbin/init),参见,1-5 android知识点 -> 01.android 启动流程 -> 二.android启动模块分析 -> 3. init进程分析
kernel_init() //kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
-> init_post();
     ->run_init_process("/sbin/init");
          -> kernel_execve()     //初始化进程用户线性地址空间,并执行可执行程序/sbin/init
其他进程创建:通过do_fork (),拷贝init进程,关键flag,没有CLONE_VM即子进程与父进程不共享内存空间(系统调用:fork(),vfork(),clone())
3.5.3 轻量级进程(线程):
a. 概述:
1)轻量级进程与普通进程不同的是,它在调用do_fork创建进程时,flag有CLONE_VM,即轻量级进程与其父进程共享内存空间。(在父进程的内部再创建一个进程,并且很小)
2)轻量级进程在内核中都有一个内核线程唯一对应,
3)轻量级进程创建:pthread_create ()-> 系统调用clone()参见,3.4.3 创建轻量级进程(线程)
                    a. 轻量级进程在通过pthread_create () 创建时,将本进程hook到VM虚拟机,则该进程能够执行java代码,此时该进程称作java线程。
                    b. 轻量级进程在通过pthread_create () 创建时,没有将本进程hook到VM虚拟机,则该进程不能够执行java代码,只能执行c/c++代码,此时该进程称作native线程(本地线程)。
                    c. 参考:http://gityuan.com/2016/09/24/android-thread/
3.5.4 linux进程
a. 概述
1)linux进程,就是在linux层执行linux应用的进程
2)linux进程可以分为两种,一种是只能执行c/c++代码的linux进程,称作native进程(本地进程);一种是创建了VM虚拟机,能够执行java,c/c++代码的linux应用进程,称作普通进程
3)参考,01.Android -> 1-5 android知识点 -> 16.android Thread - 线程

3.5.5 范例
1)父进程(pid =42)创建一个子进程,子进程的pid = 43,子进程的tgid(线程组id)=子进程的pid = 43
2)父进程(pid =42)创建一个线程,线程的pid = 44,线程的tgid = 父进程的tgid = 42
            USER VIEW
 <-- PID 43 --> <----------------- PID 42 ----------------->
                     +---------+
                     | process |
                    _| pid=42  |_
                  _/ | tgid=42 | \_ (new thread) _
       _ (fork) _/   +---------+                  \
      /                                        +---------+
+---------+                                    | process |
| process |                                    | pid=44  |
| pid=43  |                                    | tgid=42 |
| tgid=43 |                                    +---------+
+---------+
 <-- PID 43 --> <--------- PID 42 --------> <--- PID 44 --->
                     KERNEL VIEW
3.5.6 Linux通过进程查看线程的方法
1)htop按t(显示进程线程嵌套关系)和H(显示线程) ,然后F4过滤进程名。
2)ps -eLf | grep java(快照,带线程命令,e是显示全部进程,L是显示线程,f全格式输出)
3)pstree -p <pid>(显示进程树,不加pid显示所有)
4)top -Hp <pid> (实时)
5)ps -T -p <pid>(快照) 推荐程度按数字从小到大。


4.进程的堆与栈
4.1 进程的栈(Stack)
4.1.0 概述
1)进程的栈, 存放函数中的局部变量(但不包括static声明的变量,static意味着在数据段中存放变量) 和函数的传参(在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中) 。
2)进程的栈, 还存储着进程执行时cpu所处当前模式下的寄存器的值 
3)进程的栈, 还存储着基本类型值内存对象,基本类型的值包括:Undefined、Null、Boolean、Number、String
4)栈主要用于由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
4.1.1 栈的地址空间:
1)进程能处于用户态和内核态,所以进程在处于不同状态时,所使用的栈是不同的。
2)用户态进程所用的栈(用户栈),在进程的线性地址空间中有分配线性地址,在内存中有对应的内存空间与之对应
3)内核态进程所用的栈(内核栈),存储在内存中的两个连续的页框中,在创建进程的时候创建(alloc_thread_info_node(tsk, node);)
4)在内核中,进程的内核栈通常与进程的线程描述符(thread_info)紧密结合在一起,占据两个页框的物理内存空间。而且,这两个页框的起始起始地址是2^13对齐的。所以,内核通过简单的屏蔽掉sp的低13位有效位就可以获得thread_info结构的基地址了。
union thread_union {
    struct thread_info thread_info;
    unsigned long stack[THREAD_SIZE/sizeof(long)];
};
内核栈:
          
4.1.2 应用
1)cpu的sp寄存器,存储的是当前进程的栈的栈顶单元地址。如果当前进程处于用户态,则sp指向用户栈;如果当前进程处于内核态,则sp指向内核栈
2)通过sp寄存器,获取进程的线程描述符
static inline struct thread_info *current_thread_info(void)
{
    register unsigned long sp asm ("sp");
    return (struct thread_info *)(sp & ~(THREAD_SIZE - 1)); //屏蔽掉sp的低13位有效位就可以获得thread_info结构的基地址
}
4.2 进程的堆(Heap)
4.2.1 概述:
1)进程的堆,用于存放进程运行时被动态分配的内存对象(不包括基本类型值内存对象),动态分配的内存对象可以由new,malloc创建,由delete,free删除(当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减) )
2)引用类型值,指的是指针变量指向的对象,这些对象都储存在进程的堆中。
4.2.2 堆的分配与释放 
           分配:
1)malloc(size) - 请求size个字节的动态内存,如果分配成功,返回所分配内存单元第一个字节的线性地址
2)calloc(n,size) - 请求含有n个大小为size的元素的一个数组,如果分配成功,就把数组元素初始化为0,并返回第一个元素的线性地址
3)realloc(ptr,size) - 改变由前面的malloc()或calloc()分配的内存区字段的大小
4)free(addr) -  释放由malloc()或calloc()分配的起始地址为addr的线性区
4.3 堆与栈的区别
     4.3.1 内存分配方面:
    堆:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式是类似于链表。可能用到的关键字如下:new、malloc、delete、free等等。
    栈:由编译器(Compiler)自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
     4.3.2 申请方式方面:
    堆:需要程序员自己申请,并指明大小。在c中malloc函数如p1 = (char *)malloc(10);在C++中用new运算符,但是注意p1、p2本身是在栈中的。因为他们还是可以认为是局部变量。
    栈:由系统自动分配。 例如,声明在函数中一个局部变量 int b;系统自动在栈中为b开辟空间。
     4.3.3 系统响应方面:
    堆:操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样代码中的delete语句才能正确的释放本内存空间。另外由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
    栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
     4.3.4 大小限制方面:
    堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
    栈:在Windows下, 栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是固定的(是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
     4.3.5 效率方面:
    堆:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。
    栈:由系统自动分配,速度较快。但程序员是无法控制的。
     4.3.6 存放内容方面:
    堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
    栈:在函数调用时第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈,然后是函数中的局部变量。 注意: 静态变量是不入栈的。当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
     4.3.7 存取效率方面:
    堆:char *s1 = "Hellow Word";是在编译时就确定的;
    栈:char s1[] = "Hellow Word"; 是在运行时赋值的;用数组比用指针速度要快一些,因为指针在底层汇编中需要用edx寄存器中转一下,而数组在栈上直接读取

     参考:http://blog.csdn.net/u012468263/article/details/49720645

5.进程的命名空间
5.1 概述:
          1)每个进程都有自己的命名空间,在内核中用struct nsproxy数据结构表示;
          2)进程的命名空间(nsproxy )由uts命名空间,ipc命名空间,文件系统命名空间(mnt_namespace ),pid命名空间,user命名空间,net命名空间组成。
          3)不同的进程的命名空间可以共享,也可以相互独立,也可以分层
4)格式:
struct nsproxy {
        atomic_t count;
        struct uts_namespace *uts_ns;
        struct ipc_namespace *ipc_ns;
        struct mnt_namespace *mnt_ns;
        struct pid_namespace *pid_ns;
        struct user_namespace *user_ns;
        struct net *net_ns;
};
5)参考:http://blog.csdn.net/preterhuman_peak/article/details/40857117
               
5.2 swapper进程(进程0)的命名空间
1)概述

2)流程
struct task_struct init_task = INIT_TASK(init_task);     //init_task是init0进程的进程描述符
EXPORT_SYMBOL(init_task);

#define INIT_TASK(tsk)     \
{                                             \
     .state          = 0,                              \
     .nsproxy     = &init_nsproxy,          \                              //进程0的命名空间
     .mm          = NULL,                              \
     .active_mm     = &init_mm,                    \
     .......
}
struct nsproxy init_nsproxy = {
     .count     = ATOMIC_INIT(1),
     .uts_ns     = &init_uts_ns,
#if defined(CONFIG_POSIX_MQUEUE) || defined(CONFIG_SYSVIPC)
     .ipc_ns     = &init_ipc_ns,
#endif
     .mnt_ns     = NULL,                         //进程0的文件系统命名空间初始为空,即无装载文件系统命名空间
     .pid_ns     = &init_pid_ns,
#ifdef CONFIG_NET
     .net_ns     = &init_net,
#endif
};
5.3 init进程(进程1)的命名空间

5.4 进程命名空间访问
5.4.1 文件系统命名空间(mnt_namespace )访问
a.概述
1)当一个进程访问一个文件时,首先在当前进程的命名空间(nsproxy)中的文件系统命名空间(mnt_namespace )里找到文件系统的根目录对象,然后检索到指定文件
2)在一个进程的命名空间内,文件系统的命名空间内只能有一个根目录对象("/")
5.5 进程新命名空间的创建
               概述:
          1)通过系统调用fork()创建子进程的时候,设置flags标志位为CLONE_FS,可以共享父进程的命名空间中的文件系统
          2)通过系统调用fork()创建子进程的时候,设置flags标志位为CLONE_NEWNS,自己创建子进程自己的命名空间,该命名空间与父进程相互独立。

6. 获取当前进程信息
6.1 方法:
1) kernel\arch\arm\include\asm\thread_info.h
/*
 * how to get the thread information struct from C
 */
static inline struct thread_info *current_thread_info(void) __attribute_const__;
static inline struct thread_info *current_thread_info(void)
{
    register unsigned long sp asm ("sp");
    return (struct thread_info *)(sp & ~(THREAD_SIZE - 1));
}
2) kernel\arch\arm\include\asm\current.h
static inline struct task_struct *get_current(void) __attribute_const__;
static inline struct task_struct *get_current(void)
{
    return current_thread_info()->task;
}
#define current (get_current())
3. kernel\include\linux\fdtable.h
#define fcheck(fd)    fcheck_files(current->files, fd)    //取出当前进程的已经open的文件集中的一个文件的文件描述符
static inline struct file * fcheck_files(struct files_struct *files, unsigned int fd)
{
    struct file * file = NULL;
    struct fdtable *fdt = files_fdtable(files);
    if (fd < fdt->max_fds)
        file = rcu_dereference_check_fdtable(files,fdt->fd[fd]);
    return file;
}
6.2 范例
     1)
#include <linux/slab.h>
#include <linux/dcache.h>
#include <linux/fdtable.h>

int i;
char * tpath   = NULL ;
tpath = (char*)kmalloc(512, 0);
memset(tpath,'\0',512);
for(i=0; i<12; i++){
         struct file *file;
 rcu_read_lock();
 file = fcheck(i);
 if (file) {
         printk("fd %d, name: %s\n",i, d_path(&file->f_path,tpath,512)); //打印当前进程其中一个已经打开的文件的文件绝对路径
 }
 rcu_read_unlock();
}
kfree(tpath);

kernel/arch/arm/kernel/process.c
void __show_regs(struct pt_regs *regs)  //读取arm寄存器信息
{
}


7. 进程/内核线程创建的内核接口 - do_fork
1.
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND); //创建init内核线程,并启动init进程
kernel\arch\arm\kernel\process.c
pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags)
{
    struct pt_regs regs;
    memset(&regs, 0, sizeof(regs));
    regs.ARM_r4 = (unsigned long)arg;
    regs.ARM_r5 = (unsigned long)fn;
    regs.ARM_r6 = (unsigned long)kernel_thread_exit;
    regs.ARM_r7 = SVC_MODE | PSR_ENDSTATE | PSR_ISETSTATE;
    regs.ARM_pc = (unsigned long)kernel_thread_helper;
    regs.ARM_cpsr = regs.ARM_r7 | PSR_I_BIT;
    return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
}
struct pt_regs {
    long uregs[18];
};
#define ARM_cpsr    uregs[16]
#define ARM_pc        uregs[15]
#define ARM_lr        uregs[14]
#define ARM_sp        uregs[13]
#define ARM_ip        uregs[12]
#define ARM_fp        uregs[11]
#define ARM_r10        uregs[10]
#define ARM_r9        uregs[9]
#define ARM_r8        uregs[8]
#define ARM_r7        uregs[7]
#define ARM_r6        uregs[6]
#define ARM_r5        uregs[5]
#define ARM_r4        uregs[4]
#define ARM_r3        uregs[3]
#define ARM_r2        uregs[2]
#define ARM_r1        uregs[1]
#define ARM_r0        uregs[0]
#define ARM_ORIG_r0    uregs[17]
2.
kernel\kernel\fork.c
clone_flags :
    kernel\include\linux\sched.h
  CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
  CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
  CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
  CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy
  CLONE_SIGHAND 子进程与父进程共享相同的信号处理(signal handler)表
  CLONE_PTRACE 若父进程被trace,子进程也被trace
  CLONE_VFORK 父进程被挂起,直至子进程释放虚拟内存资源
  CLONE_VM 子进程与父进程运行于相同的内存空间
  CLONE_PID 子进程在创建时PID与父进程一致
  CLONE_THREAD Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
stack_start : 子进程用户态堆栈的开始地址
regs : 当发生系统调用时,进程需从用户态切换到内核态,此结构体用来保存当前进程在用户态时的通用寄存器中的值,并传给内核态堆栈中
stack_size :未使用,总被设置为0
parent_tidptr :父进程在用户态下pid的地址
child_tidptr :子进程在用户态下pid的地址

long do_fork(unsigned long clone_flags,unsigned long stack_start,struct pt_regs *regs,
          unsigned long stack_size,int __user *parent_tidptr,int __user *child_tidptr)
{
struct task_struct *p;   
//1.创建子进程的进程描述符(在内核,子进程又称作内核线程)
p = copy_process(clone_flags, stack_start, regs, stack_size,child_tidptr, NULL, trace);
//2.唤醒子进程
wake_up_new_task(p);
}

2.1
static struct task_struct *copy_process(unsigned long clone_flags,unsigned long stack_start,
                    struct pt_regs *regs,unsigned long stack_size,int __user *child_tidptr,
                    struct pid *pid,int trace)
{
struct task_struct *p;
//1)创建子进程的进程描述符(task_struct),内核栈(thread_union),进程的线程描述符(thread_info),并初始化。(所以到目前为止,父进程和子进程是没有任何区别的)
    p = dup_task_struct(current);
//2)根据clone_flags,设定子进程的凭证集cred     
    retval = copy_creds(p, clone_flags);
//3)子进程初始化
    rcu_copy_process(p); //rcu相关初始化
    init_sigpending(&p->pending);
    p->utime = cputime_zero;
    posix_cpu_timers_init(p);
    p->softirqs_enabled = 1;
    sched_fork(p); //调度相关初始化
//4)根据clone_flags,重新分配或者共享父进程的内容
    if ((retval = copy_semundo(clone_flags, p)))
        goto bad_fork_cleanup_audit;
    if ((retval = copy_files(clone_flags, p)))
        goto bad_fork_cleanup_semundo;
    if ((retval = copy_fs(clone_flags, p)))
        goto bad_fork_cleanup_files;
    if ((retval = copy_sighand(clone_flags, p)))
        goto bad_fork_cleanup_fs;
    if ((retval = copy_signal(clone_flags, p)))
        goto bad_fork_cleanup_sighand;
    if ((retval = copy_mm(clone_flags, p)))
        goto bad_fork_cleanup_signal;
    if ((retval = copy_namespaces(clone_flags, p)))
        goto bad_fork_cleanup_mm;
    if ((retval = copy_io(clone_flags, p)))
        goto bad_fork_cleanup_namespaces;
//5)设置子进程特定值(上级传下的特定参数)       
    retval = copy_thread(clone_flags, stack_start, stack_size, p,regs);
    if (retval)
        goto bad_fork_cleanup_io;
//6)其他初始化       
//如果设置了同在一个线程组则继承TGID。对于普通进程来说TGID和PID相等,对于线程来说,同一线程组内的所有线程的TGID都相等,这使得这些多线程可以通过调用getpid()获得相同的PID。       
    p->pid = pid_nr(pid);
    p->tgid = p->pid;
    if (clone_flags & CLONE_THREAD)
        p->tgid = current->tgid;   
    //如果这两个标志设定了,那么子进程和父进程有相同的父进程       
    if (clone_flags & (CLONE_PARENT|CLONE_THREAD)) {
        p->real_parent = current->real_parent;
        p->parent_exec_id = current->parent_exec_id;
    } else {//父进程为子进程的父进程
        p->real_parent = current;
        p->parent_exec_id = current->self_exec_id;
    }   
    ......
}

2.1.1
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
    struct task_struct *tsk;
    struct thread_info *ti;
    //如果使用了NUMA技术,则返回orig中的pref_node_fork字段;否则返回numa_node_id,因为没有定义NUMA所以是0 
    int node = tsk_fork_get_node(orig);
    //1. 从进程描述符高速缓存中分配一个进程描述符(子进程的进程描述符)
    tsk = alloc_task_struct_node(node);
    //2. 内核在内存中分配连续的2个页框,用于存储内核栈与进程的线程描述符,返回首页框对应的页描述符地址(子进程的内核栈与线程描述符)
    ti = alloc_thread_info_node(tsk, node);
    //3. 将父进程的进程描述符current赋值给子进程的进程描述符
    err = arch_dup_task_struct(tsk, orig); //orig = current
    tsk->stack = ti; //设置子进程的进程信息描述符
    //4. 将父进程的线程描述符赋值给子进程的线程描述符,并重置task字段指向子进程描述符
    setup_thread_stack(tsk, orig);
    ......
}
# define alloc_task_struct_node(node)        \
        kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node)
static struct kmem_cache *task_struct_cachep;
task_struct_cachep =kmem_cache_create("task_struct", sizeof(struct task_struct),
            ARCH_MIN_TASKALIGN, SLAB_PANIC | SLAB_NOTRACK, NULL);
static struct thread_info *alloc_thread_info_node(struct task_struct *tsk, int node)
{
    gfp_t mask = GFP_KERNEL;
    struct page *page = alloc_pages_node(node, mask, THREAD_SIZE_ORDER); //THREAD_SIZE_ORDER=1
    return page ? page_address(page) : NULL;
}
int __attribute__((weak)) arch_dup_task_struct(struct task_struct *dst, struct task_struct *src)
{
    *dst = *src;
    return 0;
}
static inline void setup_thread_stack(struct task_struct *p, struct task_struct *org)
{
    //将父进程thread_info中的内容完全复制到子进程相应的字段中 
    *task_thread_info(p) = *task_thread_info(org);
    //将子进程thread_info的task_struct字段重新指向子进程的进程描述符
    task_thread_info(p)->task = p;
}
#define task_thread_info(task)    ((struct thread_info *)(task)->stack)
2.1.2
void sched_fork(struct task_struct *p)
{
    int cpu = get_cpu();
    set_task_cpu(p, cpu); //将进程指定到特定cpu上运行
......
}
int copy_thread(unsigned long clone_flags, unsigned long stack_start,
        unsigned long stk_sz, struct task_struct *p, struct pt_regs *regs)
{
    struct thread_info *thread = task_thread_info(p);
    struct pt_regs *childregs = task_pt_regs(p);
    *childregs = *regs;
    childregs->ARM_r0 = 0;
    childregs->ARM_sp = stack_start;
    memset(&thread->cpu_context, 0, sizeof(struct cpu_context_save));
    thread->cpu_context.sp = (unsigned long)childregs;
    thread->cpu_context.pc = (unsigned long)ret_from_fork;
    clear_ptrace_hw_breakpoint(p);
    if (clone_flags & CLONE_SETTLS)
        thread->tp_value = regs->ARM_r3;
    thread_notify(THREAD_NOTIFY_COPY, thread);
    return 0;
}
#define task_pt_regs(p) \
    ((struct pt_regs *)(THREAD_START_SP + task_stack_page(p)) - 1)

二. 进程在android层中的描述


1. ProcessState与IPCThreadState
1.1 概述:
1)在进程应用线性地址空间的代码段中,每个android 进程都有一个ProcessState实例,每一个线程都有一个IPCThreadState实例,这两个实例可以用来进程间通信






九. 相关知识点

1. linux cred管理
1.1 概述:
1)Linux系统中,一个对象操作另一个对象时通常要做安全性检查。如一个进程操作一个文件,要检查进程是否有权限操作该文件。
2)linux内核使用credential机制,进行对象访问的安全性检查。主体提供自己权限的证书,客体提供访问自己所需权限的证书,根据主客体提供的证书及操作做安全性检查。
3)参考:http://blog.csdn.net/morphad/article/details/9089601
1.2 名词解释:
1)客体:指用户空间程序直接可以操作的系统对象,如进程、文件、消息队列、信号量、共享内存等;每个客体都有一组凭证,每种客体有不同的凭证集
2)主体:操作客体的对象;除进程外大多数系统对象都不是主体,但在特殊环境下某些对象是主体,如文件在设置F_SETOWN后可以发送SIGIO信号到进程,这时文件就是主体,进程就是客体
3)客体所有者:客体凭证集有一部分表示客体所有者;如文件中uid表示文件的所有者
4)客体上下文:客体被访问时所需权限凭证集;主体上下文:主体的权限凭证集
5)规则:主体操作客体时,用于安全检查
1.3 进程凭证集
1.3.1 概述:
1)在linux内核中,用struct cred数据结构表示进程的凭证集
2)struct cred数据结构,即可以表示主体权限凭证集,也可表示客体被访问时所需权限凭证集;进程描述符中cred和real_cred字段分别指向主体与客体的证书
1.3.2 数据结构
     参见,一. 进程概述 -> 2.关键数据结构 -> 2.8 struct cred


2 linux 用户
     
2.1 文件的用户
     2.1.1 文件的uid(file.f_cred.uid)
     1.概述:
    1)表示拥有该文件的用户
     2.1.2 文件的gid(file.f_cred.gid)
     1.概述:
    1)表示拥有该文件的用户组
     2.1.3 文件的suid(file.f_cred.suid)
     1.概述:
    1)用于在访问该文件时修改访问进程的euid,使访问进程有权限访问该文件。
     2.设置:
    chmod u+s filename 设置SUID位
    chmod u-s filename 去掉SUID设置
    chmod g+s filename 设置SGID位
    chmod g-s filename 去掉SGID设置
     3.权限解释
    1)-rwsr-xr-x 表示SUID和所有者权限中可执行位被设置
    2)-rwSr--r-- 表示SUID被设置,但所有者权限中可执行位没有被设置
    3)-rwxr-sr-x 表示SGID和同组用户权限中可执行位被设置
    4)-rw-r-Sr-- 表示SGID被设置,但同组用户权限中可执行位没有被设置
    5)S与s的区别,当对文件本身拥有执行权限时设置suid(或者sgid)时,会变成小写s,表示同时具有执行权限和suid(或者sgid);否则会变成大写S,表示不具备执行权限,没有执行权限的suid和sgid实际变得没有效果(因为程序不能执行,euid和egid不能改变为文件的所有者id)。
     2.1.4 范例
     1)
        kernel/drivers/video/rockchip/spirit# ll
        -rwxr-xr-x 1 root root 20841 12月 15 17:25 spirit-gpio.c
        解析:
             1. 文件spirit-gpio.c的拥有者为root用户(第一个root)
             2. root用户组(第二个root)拥有文件spirit-gpio.c
     2)
root@zby:/# ll /usr/bin/passwd
-rwsr-xr-x 1 root root 47032  7月 16  2015 /usr/bin/passwd*
解析:
    1. 如果/usr/bin/passwd文件没有设置suid位(-rwxr-xr-x),则访问该文件的进程的euid必须为root才可以访问该文件;
    2. 如果/usr/bin/passwd文件设置了suid位(-rwsr-xr-x),此时访问进程的euid不是root,当该进程访问此文件时,exec会根据/usr/bin/passwd(此文件为执行文件,所以用到exec)的SUID位会把进程的euid设成root, 此时这个进程都获得了root权限
     2.1.5 文件的权限
    1)在UNIX的实现中,文件权限用12个二进制位表示,如果该位置上的值是1,表示有相应权限:
             S G T r w x r w x r w x
    2)第11位为SUID位,第10位为SGID位,第9位为sticky位,第8-0位对应于上面的三组rwx位。
             11 10 9 8 7 6 5 4 3 2 1 0
    3)   
             -rwsr-xr-x的值为: 1 0 0 1 1 1 1 0 1 1 0 1
             -rw-r-Sr--的值为: 0 1 0 1 1 0 1 0 0 1 0 0
    4)Sticky bit 粘住位
概述:如果对一个目录设置了粘住位,则只有对该目录具有写权限的用户在满足下列条件之一的情况下,才能删除或更改该目录下的文件:
    a.拥有此文件
    b.拥有此目录
    c.是超级用户   
2.2 进程的用户
     2.2.1 进程的uid(task_struct.cred.uid)
     1. 概述:
    1)表示创建该进程的用户,通常称作实际用户ID
     2.2.2 进程的euid(task_struct.cred.euid)
     1. 概述:   
    1)表示该进程是否有权限访问某个文件或是操作其他进程,通常称作有效用户ID。
    2)当进程的euid与访问文件的uid相同,则该进程就有权限访问这个文件
     2. 范例:
    1)当进程的euid = root,就能够访问spirit-gpio.c文件
2.3 系统的用户
     2.3.1 创建系统用户 - ison :
     root@zby:/home/share# useradd ison    //仅仅是添加用户, 不会在home目录添加帐号(sudo adduser ison 这样的命令会在home目录下添加一个帐号)
     2.3.2 删除系统用户
     root@zby:/home/share#  userdel -r ison  //加上-r的选项,在删除用户的同时一起把这个用户的宿主目录和邮件目录删除 
     2.3.3 修改系统用户密码
     root@zby:/home/share# passwd ison
     2.3.4 创建用户组 - ison_group
     root@zby:/home# groupadd ison_group
     2.3.5 删除用户组
     root@zby:/home# groupdel ison_group
     2.3.6 将系统用户ison加入到ison_group
      root@zby:/home# adduser ison ison_group
正在添加用户"ison"到"ison_group"组...
正在将用户“ison”加入到“ison_group”组中
完成。  
     2.3.7 查看系统中所有的系统用户
     root@zby:/home# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
     2.3.8 查看系统中所有的用户组
方法1:
     root@zby:/home#cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog,zby
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
uucp:x:10:
方法2:
     root@zby:/home/share# groupmod <按“Tab key”3次>
adm            colord         fuse           lp             nogroup        rtkit          ssl-cert       utempter       
audio          crontab        games          lpadmin        nopasswdlogin  sambashare     staff          utmp           
avahi          daemon         gnats          mail           operator       saned          sudo           uucp           
avahi-autoipd  dialout        irc            man            plugdev        sasl           sys            video          
backup         dip            kmem           messagebus     proxy          scanner        syslog         voice          
bin            disk           libuuid        mlocate        pulse          shadow         tape           whoopsie       
bluetooth      fax            lightdm        netdev         pulse-access   src            tty            www-data       
cdrom          floppy         list           news           root           ssh