linux内核情景分析之execve()

用来描述用户态的cpu寄存器在内核栈中保存情况.可以获取用户空间的信息

  1. struct pt_regs {
  2. long ebx; //可执行文件路径的指针(regs.ebx中
  3. long ecx; //命令行参数的指针(regs.ecx中)
  4. long edx; //环境变量的指针(regs.edx中)。
  5. long esi;
  6. long edi;
  7. long ebp;
  8. long eax;
  9. int xds;
  10. int xes;
  11. long orig_eax;
  12. long eip;
  13. int xcs;
  14. long eflags;
  15. long esp;
  16. int xss;
  17. };
  1. asmlinkage int sys_execve(struct pt_regs regs)
  2. {
  3. int error;
  4. char * filename;
  5. filename = getname((char *) regs.ebx);//ebx为"/bin/echo",把字符串从用户空间拷贝到系统空间
  6. error = PTR_ERR(filename);//判断是否出错
  7. if (IS_ERR(filename))
  8. goto out;
  9. //文件名 参数 NULL 传入副本
  10. error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, &regs);//ecx为args,edx为NULL
  11. if (error == 0)
  12. current->ptrace &= ~PT_DTRACE;
  13. putname(filename);//将之前为文件名分配的空间释放掉
  14. out:
  15. return error;
  16. }

linux内核每种被注册的可执行程序格式都用linux_bin_fmt来存储,其中记录了可执行程序的加载和执行函数,同时我们需要一种方法来保存可执行程序的信息, 比如可执行文件的路径, 运行的参数和环境变量等信息,即linux_bin_prm结构

  1. /*
  2. * This structure is used to hold the arguments that are used when loading binaries.
  3. */
  4. struct linux_binprm {
  5. char buf[BINPRM_BUF_SIZE]; // 保存可执行文件的头128字节
  6. #ifdef CONFIG_MMU
  7. struct vm_area_struct *vma;
  8. unsigned long vma_pages;
  9. #else
  10. # define MAX_ARG_PAGES 32
  11. struct page *page[MAX_ARG_PAGES];
  12. #endif
  13. struct mm_struct *mm;
  14. unsigned long p; /* current top of mem , 当前内存页最高地址*/
  15. unsigned int
  16. cred_prepared:1,/* true if creds already prepared (multiple
  17. * preps happen for interpreters) */
  18. cap_effective:1;/* true if has elevated effective capabilities,
  19. * false if not; except for init which inherits
  20. * its parent‘s caps anyway */
  21. #ifdef __alpha__
  22. unsigned int taso:1;
  23. #endif
  24. unsigned int recursion_depth; /* only for search_binary_handler() */
  25. struct file * file; /* 要执行的文件 */
  26. struct cred *cred; /* new credentials */
  27. int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
  28. unsigned int per_clear; /* bits to clear in current->personality */
  29. int argc, envc; /* 命令行参数和环境变量数目 */
  30. const char * filename; /* Name of binary as seen by procps, 要执行的文件的名称 */
  31. const char * interp; /* Name of the binary really executed. Most
  32. of the time same as filename, but could be
  33. different for binfmt_{misc,script} 要执行的文件的真实名称,通常和filename相同 */
  34. unsigned interp_flags;
  35. unsigned interp_data;
  36. unsigned long loader, exec;
  37. };
  1. int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
  2. {
  3. struct linux_binprm bprm;//存储可执行文件信息
  4. struct file *file;
  5. int retval;
  6. int i;
  7. file = open_exec(filename);//打开目标文件 /bin/echo
  8. retval = PTR_ERR(file);
  9. if (IS_ERR(file))
  10. return retval;
  11. bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);//1024*32减去一个指针的大小,当前页最高地址
  12. memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0])); //page指针数组初始化为0
  13. bprm.file = file;
  14. bprm.filename = filename;
  15. bprm.sh_bang = 0;//可执行文件属性.0表示二进制
  16. bprm.loader = 0;
  17. bprm.exec = 0;//参数的起始地址(从上往下方向)
  18. if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) {//对指针数组argv[]中参数的个数进行计数,而bprm.p / sizeof(void *)表示允许的最大值,由于agrv[]是在用户空间而不在系统空间
  19. allow_write_access(file);//防止其他进程在读入可执行文件期间通过内存映射改变它的内容
  20. fput(file);
  21. return bprm.argc;
  22. }
  23. if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) {//同上环境变量
  24. allow_write_access(file);
  25. fput(file);
  26. return bprm.envc;
  27. }
  28. retval = prepare_binprm(&bprm);//见下面的代码,为bprm结构做准备,读取128个字节到缓冲区
  29. if (retval < 0)
  30. goto out;
  31. retval = copy_strings_kernel(1, &bprm.filename, &bprm);//由于bprm.filename已经在系统空间了,所以copy_strings_kernel从系统空间中拷贝
  32. if (retval < 0)
  33. goto out;
  34. bprm.exec = bprm.p;//参数的起始地址
  35. retval = copy_strings(bprm.envc, envp, &bprm);//从用户空间拷贝,参考copy_strings_kernel
  36. if (retval < 0)
  37. goto out;
  38. retval = copy_strings(bprm.argc, argv, &bprm);//从用户空间拷贝,参考copy_strings_kernel
  39. if (retval < 0)
  40. goto out;
  41. retval = search_binary_handler(&bprm,regs);//已经从可执行文件头部读入了128个字节存放在bprm的缓冲区,而且运行所需的参数和环境变量也已经搜集在bprm中,现在就由formats队列中的成员逐个来认领,谁要识别到它代表的可执行文件格式,运行的时候就交给它
  42. if (retval >= 0)
  43. /* execve success */
  44. return retval;
  45. out:
  46. /*出错,返回inode和释放argv的页 Something went wrong, return the inode and free the argument pages*/
  47. allow_write_access(bprm.file);
  48. if (bprm.file)
  49. fput(bprm.file);//释放文件对象
  50. for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
  51. struct page * page = bprm.page[i];
  52. if (page)
  53. __free_page(page);
  54. }
  55. return retval;
  56. }

分析prepare_binprm

  1. int prepare_binprm(struct linux_binprm *bprm)
  2. {
  3. int mode;
  4. struct inode * inode = bprm->file->f_dentry->d_inode;//获取打开的文件节点
  5. mode = inode->i_mode;//类型
  6. /* Huh? We had already checked for MAY_EXEC, WTF do we check this? */
  7. if (!(mode & 0111)) /* with at least _one_ execute bit set最少可执行状态 */
  8. return -EACCES;
  9. if (bprm->file->f_op == NULL)//针对文件操作不允许为空
  10. return -EACCES;
  11. bprm->e_uid = current->euid;//继承uid
  12. bprm->e_gid = current->egid;//继承gid
  13. if(!IS_NOSUID(inode)) {//是否是SUID
  14. /* Set-uid? */
  15. if (mode & S_ISUID)//如果有SUID.那就设置
  16. bprm->e_uid = inode->i_uid;
  17. /* Set-gid? */
  18. /*
  19. * If setgid is set but no group execute bit then this
  20. * is a candidate for mandatory locking, not a setgid
  21. * executable.
  22. */
  23. if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
  24. bprm->e_gid = inode->i_gid;
  25. }
  26. /* We don‘t have VFS support for capabilities yet */
  27. cap_clear(bprm->cap_inheritable);
  28. cap_clear(bprm->cap_permitted);
  29. cap_clear(bprm->cap_effective);
  30. /* To support inheritance of root-permissions and suid-root
  31. * executables under compatibility mode, we raise all three
  32. * capability sets for the file.
  33. *
  34. * If only the real uid is 0, we only raise the inheritable
  35. * and permitted sets of the executable file.
  36. */
  37. if (!issecure(SECURE_NOROOT)) {
  38. if (bprm->e_uid == 0 || current->uid == 0) {
  39. cap_set_full(bprm->cap_inheritable);
  40. cap_set_full(bprm->cap_permitted);
  41. }
  42. if (bprm->e_uid == 0)
  43. cap_set_full(bprm->cap_effective);
  44. }
  45. memset(bprm->buf,0,BINPRM_BUF_SIZE);//从可执行文件中读入开头的128个字节到linux_binprm结构bprm中的缓冲区
  46. return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE);
  47. }

inux内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构,定义如下

search_binary_handler,已经从可执行文件头部读入了128个字节存放在bprm的缓冲区,而且运行所需的参数和环境变量也已经搜集在bprm中,现在就由formats队列中的成员逐个来认领,谁要识别到它代表的可执行文件格式,运行的时候就交给它,代码如下

  1. struct linux_binfmt {
  2. struct list_head lh;
  3. struct module *module;
  4. int (*load_binary)(struct linux_binprm *);//通过读存放在可执行文件中的信息为当前进程建立一个新的执行环境
  5. int (*load_shlib)(struct file *);//用于动态的把一个共享库捆绑到一个已经在运行的进程, 这是由uselib()系统调用激活的
  6. int (*core_dump)(struct coredump_params *cprm);//在名为core的文件中, 存放当前进程的执行上下文. 这个文件通常是在进程接收到一个缺省操作为”dump”的信号时被创建的, 其格式取决于被执行程序的可执行类型
  7. unsigned long min_coredump; /* minimal dump size */
  8. };
  1. int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
  2. {
  3. int try,retval=0;
  4. struct linux_binfmt *fmt;
  5. ......
  6. //一共2层循环,外出循环式为了安装了模块后再试一次
  7. for (try=0; try<2; try++) {
  8. read_lock(&binfmt_lock);
  9. //所有的linux_binfmt对象都处于一个链表中, 第一个元素的地址存放在formats变量中
  10. for (fmt = formats ; fmt ; fmt = fmt->next) {
  11. int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
  12. if (!fn)//是否是可执行文件
  13. continue;
  14. if (!try_inc_mod_count(fmt->module))//是否是动态安装库
  15. continue;
  16. read_unlock(&binfmt_lock);
  17. retval = fn(bprm, regs);//加载,如果谁都不认识,返回-ENOEXEC表示对不上号
  18. if (retval >= 0) {
  19. put_binfmt(fmt);
  20. allow_write_access(bprm->file);
  21. if (bprm->file)
  22. fput(bprm->file);
  23. bprm->file = NULL;
  24. current->did_exec = 1;
  25. return retval;
  26. }
  27. read_lock(&binfmt_lock);
  28. put_binfmt(fmt);
  29. if (retval != -ENOEXEC)//只要不是对不上号,就退出循环
  30. break;
  31. if (!bprm->file) {
  32. read_unlock(&binfmt_lock);
  33. return retval;
  34. }
  35. }
  36. read_unlock(&binfmt_lock);
  37. if (retval != -ENOEXEC) {
  38. break;
  39. #ifdef CONFIG_KMOD
  40. }else{
  41. #define printable(c) (((c)==‘\t‘) || ((c)==‘\n‘) || (0x20<=(c) && (c)<=0x7e))
  42. char modname[20];
  43. if (printable(bprm->buf[0]) &&
  44. printable(bprm->buf[1]) &&
  45. printable(bprm->buf[2]) &&
  46. printable(bprm->buf[3]))
  47. break; /* -ENOEXEC */
  48. sprintf(modname, "binfmt-%04x", *(unsigned short *)(&bprm->buf[2]));
  49. request_module(modname);//试着将相应的模块装入,一共进行两次循环,正是为了在安装了模块以后再来试一次
  50. #endif
  51. }
  52. }
  53. return retval;
  54. }
  1. struct exec
  2. {
  3. unsigned long a_info; /* Use macros N_MAGIC, etc for access */
  4. unsigned a_text; /* length of text, in bytes */
  5. unsigned a_data; /* length of data, in bytes */
  6. unsigned a_bss; /* length of uninitialized data area for file, in bytes */
  7. unsigned a_syms; /* length of symbol table data in file, in bytes */
  8. unsigned a_entry; /* start address */
  9. unsigned a_trsize; /* length of relocation info for text, in bytes */
  10. unsigned a_drsize; /* length of relocation info for data, in bytes */
  11. };

我们假设/bin/echo是a.out格式。所以fn(bprm, regs),执行的代码是load_aout_binary。

  1. static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs)
  2. {
  3. struct exec ex;
  4. unsigned long error;
  5. unsigned long fd_offset;
  6. unsigned long rlim;
  7. int retval;
  8. ex = *((struct exec *) bprm->buf); //128字节可执行文件头部
  9. if ((N_MAGIC(ex) != ZMAGIC && N_MAGIC(ex) != OMAGIC &&
  10. N_MAGIC(ex) != QMAGIC && N_MAGIC(ex) != NMAGIC) ||
  11. N_TRSIZE(ex) || N_DRSIZE(ex) ||
  12. bprm->file->f_dentry->d_inode->i_size < ex.a_text+ex.a_data+N_SYMSIZE(ex)+N_TXTOFF(ex)) {
  13. return -ENOEXEC;//匹配不会返回-ENOEXEC
  14. }
  15. fd_offset = N_TXTOFF(ex);//根据代码的特性取得代码段在目标文件中的起始位置
  16. /* Check initial limits. This avoids letting people circumvent
  17. * size limits imposed on them by creating programs with large
  18. * arrays in the data or bss.
  19. */
  20. rlim = current->rlim[RLIMIT_DATA].rlim_cur;//获取限制条件
  21. if (rlim >= RLIM_INFINITY)
  22. rlim = ~0;
  23. if (ex.a_data + ex.a_bss > rlim)//代码段和bss段的总和不能超过限制
  24. return -ENOMEM;
  25. /* Flush all traces of the currently running executable */
  26. retval = flush_old_exec(bprm);//到了"与过去告别"的时候了,这种"告别过去"意味着放弃从父进程"继承"下来的全部用户空间,不管是通过复制还是通过指针共享继承下来的
  27. if (retval)
  28. return retval;
  29. /* OK, This is the point of no return */
  30. #if !defined(__sparc__)
  31. set_personality(PER_LINUX);
  32. #else
  33. set_personality(PER_SUNOS);
  34. #if !defined(__sparc_v9__)
  35. memcpy(current->thread.core_exec, &ex, sizeof(struct exec));
  36. #endif
  37. #endif
  38. current->mm->end_code = ex.a_text +
  39. (current->mm->start_code = N_TXTADDR(ex));//代码段起始和结束地址,详细请看紧邻本代码段的相关代码
  40. current->mm->end_data = ex.a_data +
  41. (current->mm->start_data = N_DATADDR(ex));//数据段起始和结束地址,数据段起始地址就是代码段结束地址
  42. current->mm->brk = ex.a_bss +
  43. (current->mm->start_brk = N_BSSADDR(ex));//bss段起始和结束地址,bss起始地址就是数据段结束地址
  44. current->mm->rss = 0;
  45. current->mm->mmap = NULL;
  46. compute_creds(bprm);//确定是否具有其权限
  47. current->flags &= ~PF_FORKNOEXEC;
  48. #ifdef __sparc__
  49. if (N_MAGIC(ex) == NMAGIC) {//其他格式
  50. loff_t pos = fd_offset;//elf代码段偏移地址
  51. /* Fuck me plenty... */
  52. error = do_brk(N_TXTADDR(ex), ex.a_text);
  53. bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
  54. ex.a_text, &pos);
  55. error = do_brk(N_DATADDR(ex), ex.a_data);
  56. bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex),
  57. ex.a_data, &pos);
  58. goto beyond_if;
  59. }
  60. #endif
  61. if (N_MAGIC(ex) == OMAGIC) {//如果魔术是OMAGIC
  62. unsigned long text_addr, map_size;
  63. loff_t pos;
  64. text_addr = N_TXTADDR(ex);//代码段起始地址
  65. #if defined(__alpha__) || defined(__sparc__)
  66. pos = fd_offset;//根据代码的特性取得代码段在目标文件中的起始位置
  67. map_size = ex.a_text+ex.a_data + PAGE_SIZE - 1;//代码段和数据段的大小
  68. #else
  69. pos = 32;
  70. map_size = ex.a_text+ex.a_data;
  71. #endif
  72. error = do_brk(text_addr & PAGE_MASK, map_size);//为正文段和数据段合在一起分配空间
  73. if (error != (text_addr & PAGE_MASK)) {//出错
  74. send_sig(SIGKILL, current, 0);//杀死当前进程
  75. return error;
  76. }
  77. error = bprm->file->f_op->read(bprm->file, (char *)text_addr,
  78. ex.a_text+ex.a_data, &pos);//然后就把这两部分从文件中读进来,这样就有了vm_struct结构,对应的页目录表项,页表项,页面,而且页面已经放入了文件中的代码段和数据段
  79. if (error < 0) {
  80. send_sig(SIGKILL, current, 0);
  81. return error;
  82. }
  83. flush_icache_range(text_addr, text_addr+ex.a_text+ex.a_data);
  84. } else {//如果魔术不是OMAGIC
  85. static unsigned long error_time, error_time2;
  86. if ((ex.a_text & 0xfff || ex.a_data & 0xfff) &&
  87. (N_MAGIC(ex) != NMAGIC) && (jiffies-error_time2) > 5*HZ)
  88. {
  89. printk(KERN_NOTICE "executable not page aligned\n");
  90. error_time2 = jiffies;
  91. }
  92. if ((fd_offset & ~PAGE_MASK) != 0 &&
  93. (jiffies-error_time) > 5*HZ)
  94. {
  95. printk(KERN_WARNING
  96. "fd_offset is not page aligned. Please convert program: %s\n",
  97. bprm->file->f_dentry->d_name.name);
  98. error_time = jiffies;
  99. }
  100. if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) {//如果没有mmap函数或者代码段及数据段的长度不与页面大小对齐
  101. loff_t pos = fd_offset;
  102. do_brk(N_TXTADDR(ex), ex.a_text+ex.a_data);//和上面的方式一样,正文段和数据段合在一起分配空间
  103. bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex),
  104. ex.a_text+ex.a_data, &pos);//然后就把这两部分从文件中读进来,这样就有了vm_struct结构,对应的页目录表项,页表项,页面,而且页面已经放入了文件中的代码段和数据段
  105. flush_icache_range((unsigned long) N_TXTADDR(ex),
  106. (unsigned long) N_TXTADDR(ex) +
  107. ex.a_text+ex.a_data);
  108. goto beyond_if;
  109. }
  110. down(current->mm->mmap_sem);
  111. error = do_mmap(bprm->file, N_TXTADDR(ex), ex.a_text,//如果有mmap,将文件的代码段映射到进程的用户空间
  112. PROT_READ | PROT_EXEC,
  113. MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE,
  114. fd_offset);
  115. up(current->mm->mmap_sem);
  116. if (error != N_TXTADDR(ex)) {
  117. send_sig(SIGKILL, current, 0);
  118. return error;
  119. }
  120. down(current->mm->mmap_sem);
  121. error = do_mmap(bprm->file, N_DATADDR(ex), ex.a_data,///如果有mmap,将文件的数据段映射到进程的用户空间
  122. PROT_READ | PROT_WRITE | PROT_EXEC,
  123. MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE,
  124. fd_offset + ex.a_text);//数据段在文件中的偏移是fd_offset + ex.a_text
  125. up(current->mm->mmap_sem);
  126. if (error != N_DATADDR(ex)) {
  127. send_sig(SIGKILL, current, 0);
  128. return error;
  129. }
  130. }
  131. beyond_if:
  132. set_binfmt(&aout_format);//current->binfmt = aout_format;
  133. set_brk(current->mm->start_brk, current->mm->brk);//为可执行文件的bss段分配空间并建立起页面映射
  134. retval = setup_arg_pages(bprm); //在用户空间的堆栈区顶部为进程建立起一个虚拟区间,并将执行参数以及环境变量所占的物理页面与此虚拟区间建立起映射
  135. if (retval < 0) {
  136. /* Someone check-me: is this error path enough? */
  137. send_sig(SIGKILL, current, 0);
  138. return retval;
  139. }
  140. current->mm->start_stack =
  141. (unsigned long) create_aout_tables((char *) bprm->p, bprm);//把一些变量放入用户堆栈中,返回的是STACK_TOP减去图中参数所占空间,也就是堆栈开始的虚拟地址
  142. #ifdef __alpha__
  143. regs->gp = ex.a_gpvalue;
  144. #endif
  145. //起始地址 ,栈指针
  146. start_thread(regs, ex.a_entry, current->mm->start_stack);//设置系统堆栈中的eip,esp
  147. if (current->ptrace & PT_PTRACED)
  148. send_sig(SIGTRAP, current, 0);
  149. return 0;
  150. }

复制信号处理函数指针数组,放弃用户空间或者不跟父进程共享内存空间,如果此进程实际为一个线程组的线程,那就从线程组中脱离,改变信号处理模式为默认处理,关闭file文件对象

  1. int flush_old_exec(struct linux_binprm * bprm)
  2. {
  3. char * name;
  4. int i, ch, retval;
  5. struct signal_struct * oldsig;
  6. /*
  7. * Make sure we have a private signal table
  8. */
  9. oldsig = current->sig;//获取当前进程信号处理函数
  10. retval = make_private_signals();//如果子进程通过指针来共享父进程的信号处理表,就要把它复制过来
  11. if (retval) goto flush_failed;
  12. /*
  13. * Release all of the old mmap stuff
  14. */
  15. retval = exec_mmap();//从父进程继承下来的用户空间就是在这里放弃的
  16. if (retval) goto mmap_failed;
  17. /* This is the point of no return */
  18. release_old_signals(oldsig);//如果子进程通过指针来共享父进程的信号处理表,那么就要减少oldsig->count的计数
  19. current->sas_ss_sp = current->sas_ss_size = 0;
  20. if (current->euid == current->uid && current->egid == current->gid)
  21. current->dumpable = 1;
  22. name = bprm->filename;
  23. for (i=0; (ch = *(name++)) != ‘\0‘;) {
  24. if (ch == ‘/‘)
  25. i = 0;
  26. else
  27. if (i < 15)
  28. current->comm[i++] = ch;
  29. }
  30. current->comm[i] = ‘\0‘;//comm[],用于保存进程所执行的程序名,所以还要把bprm->filename的目标程序路径名的最后一段抄过去
  31. flush_thread();//不用关心
  32. de_thread(current);//如果当前进程是一个线程,从线程组中脱离出来
  33. if (bprm->e_uid != current->euid || bprm->e_gid != current->egid ||
  34. permission(bprm->file->f_dentry->d_inode,MAY_READ))
  35. current->dumpable = 0;
  36. /* An exec changes our domain. We are no longer part of the thread
  37. group */
  38. current->self_exec_id++;
  39. flush_signal_handlers(current);//原来指向用户空间子程序的,现在设为SIG_DEL,表示采取预设的响应方式
  40. flush_old_files(current->files);//对原有已打开文件的处理
  41. return 0;
  42. mmap_failed:
  43. flush_failed:
  44. spin_lock_irq(current->sigmask_lock);
  45. if (current->sig != oldsig)
  46. kfree(current->sig);
  47. current->sig = oldsig;
  48. spin_unlock_irq(current->sigmask_lock);
  49. return retval;
  50. }

flush_old_exec中的make_private_signal

  1. static inline int make_private_signals(void)
  2. {
  3. struct signal_struct * newsig;
  4. if (atomic_read(current->sig->count) <= 1)//如果只是把父进程的信号处理表指针复制过来,而通过这指针来共享父进程的信号处理表,那么current->sig->count大于1
  5. return 0;
  6. newsig = kmem_cache_alloc(sigact_cachep, GFP_KERNEL);//自立门户,和copy_sighand()基本相同
  7. if (newsig == NULL)
  8. return -ENOMEM;
  9. spin_lock_init(&newsig->siglock);
  10. atomic_set(&newsig->count, 1);
  11. memcpy(newsig->action, current->sig->action, sizeof(newsig->action));//拷贝
  12. spin_lock_irq(current->sigmask_lock);
  13. current->sig = newsig;//获得新的
  14. spin_unlock_irq(current->sigmask_lock);
  15. return 0;
  16. }

flush_old_exec的exec_mmap函数

  1. static int exec_mmap(void)
  2. {
  3. struct mm_struct * mm, * old_mm;
  4. old_mm = current->mm;
  5. if (old_mm && atomic_read(&old_mm->mm_users) == 1) {//如果共享计数为1时,表明对此空间的使用是独占的,也就是说这是从父进程复制过来的
  6. flush_cache_mm(old_mm);//释放缓存
  7. mm_release();//针对vfork.父进程减少信号量休眠,以后让子进程up将其唤醒
  8. exit_mmap(old_mm);//释放mm_struct数据结构以下的所有vm_area_struct数据结构(但是不包括mm_struct结构本身),并将页面表项设置为0
  9. flush_tlb_mm(old_mm);//释放tlb的缓存
  10. return 0;
  11. }
  12. mm = mm_alloc();//如果只是将指向mm_struct数据结构的指针复制给子进程,让子进程通过这个指针来共享父进程的用户空间,那就可以跳过释放用户空间这一步,直接就为子进程分配新的用户空间
  13. if (mm) {
  14. struct mm_struct *active_mm = current->active_mm;//
  15. if (init_new_context(current, mm)) {//空语句
  16. mmdrop(mm);
  17. return -ENOMEM;
  18. }
  19. /* Add it to the list of mm‘s */
  20. spin_lock(&mmlist_lock);
  21. list_add(&mm->mmlist, &init_mm.mmlist);
  22. spin_unlock(&mmlist_lock);
  23. task_lock(current);
  24. current->mm = mm;//新分配空户空间初始化
  25. current->active_mm = mm;//新分配的空户空间
  26. task_unlock(current);
  27. activate_mm(active_mm, mm);//切换到新的的用户空间
  28. mm_release();//将父进程唤醒
  29. if (old_mm) {
  30. if (active_mm != old_mm) BUG();//如果不一致,说明线程成为了进程
  31. mmput(old_mm);//减少mm->mm_users计数,因为已经不共享了
  32. return 0;
  33. }
  34. mmdrop(active_mm);//如果已经没有进程使用这空间.将旧的空间给释放掉
  35. return 0;
  36. }
  37. return -ENOMEM;
  38. }

根据close_on_exec位图,里面存储着表示哪些文件在执行一个新目标程序时应予关闭的信息。根据这个位图的指示将这些文件关闭,并且将此位图清成全0。

  1. static inline void flush_old_files(struct files_struct * files)
  2. {
  3. long j = -1;
  4. write_lock(&files->file_lock);
  5. for (;;) {
  6. unsigned long set, i;
  7. j++;
  8. i = j * __NFDBITS;
  9. if (i >= files->max_fds || i >= files->max_fdset)
  10. break;
  11. set = files->close_on_exec->fds_bits[j];
  12. if (!set)
  13. continue;
  14. files->close_on_exec->fds_bits[j] = 0;
  15. write_unlock(&files->file_lock);
  16. for ( ; set ; i++,set >>= 1) {
  17. if (set & 1) {
  18. sys_close(i);
  19. }
  20. }
  21. write_lock(&files->file_lock);
  22. }
  23. write_unlock(&files->file_lock);
  24. }
  1. void mmput(struct mm_struct *mm)
  2. {
  3. if (atomic_dec_and_lock(&mm->mm_users, &mmlist_lock)) {//减去1,是否为0
  4. list_del(&mm->mmlist);
  5. spin_unlock(&mmlist_lock);
  6. exit_mmap(mm);//将页表设置为0
  7. mmdrop(mm);//将页表项与页表,本身mm_struct全部释放
  8. }
  9. }
  1. int setup_arg_pages(struct linux_binprm *bprm)
  2. {
  3. unsigned long stack_base;
  4. struct vm_area_struct *mpnt;
  5. int i;
  6. stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;//STACK_TOP为3GB
  7. bprm->p += stack_base;//bprm->p原来是MAX_ARG_PAGES*PAGE_SIZE-参数的数量,现在加上STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE,最后是STACK_TOP - 参数的数量
  8. if (bprm->loader)
  9. bprm->loader += stack_base;
  10. bprm->exec += stack_base;
  11. mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//从slab中获取一页
  12. if (!mpnt)
  13. return -ENOMEM;
  14. down(¤t->mm->mmap_sem);
  15. {
  16. mpnt->vm_mm = current->mm;//内存描述符
  17. mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p;//堆栈段开始地址
  18. mpnt->vm_end = STACK_TOP;//堆栈段结束地址
  19. mpnt->vm_page_prot = PAGE_COPY;
  20. mpnt->vm_flags = VM_STACK_FLAGS;
  21. mpnt->vm_ops = NULL;
  22. mpnt->vm_pgoff = 0;
  23. mpnt->vm_file = NULL;
  24. mpnt->vm_private_data = (void *) 0;
  25. insert_vm_struct(current->mm, mpnt);//插入
  26. current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
  27. }
  28. for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
  29. struct page *page = bprm->page[i];//从page[0]开始
  30. if (page) {
  31. bprm->page[i] = NULL;
  32. current->mm->rss++;
  33. put_dirty_page(current,page,stack_base);//建立从堆栈虚拟空间到页面之前的映射
  34. }
  35. stack_base += PAGE_SIZE;//加一个页面的长度,4096个字节
  36. }
  37. up(&current->mm->mmap_sem);
  38. return 0;
  39. }
  1. static unsigned long * create_aout_tables(char * p, struct linux_binprm * bprm)
  2. {
  3. char **argv, **envp;
  4. unsigned long * sp;
  5. int argc = bprm->argc;//程序参数个数
  6. int envc = bprm->envc;//环境字符串个数
  7. //sp指向当前堆栈页面的栈顶指针
  8. sp = (unsigned long *) ((-(unsigned long)sizeof(char *)) & (unsigned long) p);
  9. #ifdef __sparc__
  10. /* This imposes the proper stack alignment for a new process. */
  11. sp = (unsigned long *) (((unsigned long) sp) & ~7);
  12. if ((envc+argc+3)&1) --sp;
  13. #endif
  14. #ifdef __alpha__
  15. /* whee.. test-programs are so much fun. */
  16. put_user(0, --sp);
  17. put_user(0, --sp);
  18. if (bprm->loader) {
  19. put_user(0, --sp);
  20. put_user(0x3eb, --sp);
  21. put_user(bprm->loader, --sp);
  22. put_user(0x3ea, --sp);
  23. }
  24. put_user(bprm->exec, --sp);
  25. put_user(0x3e9, --sp);
  26. #endif
  27. //sp减去envc+1作为环境字符串指针数组的起始地址,最后一个数组元素为0
  28. sp -= envc+1;
  29. envp = (char **) sp;//指向环境字符串数组的起始地址
  30. sp -= argc+1;//同上
  31. argv = (char **) sp;
  32. #if defined(__i386__) || defined(__mc68000__) || defined(__arm__)
  33. //栈顶sp继续向低地址放扩展,存放envp,argv,argc
  34. put_user((unsigned long) envp,--sp);
  35. put_user((unsigned long) argv,--sp);
  36. #endif
  37. put_user(argc,--sp);
  38. current->mm->arg_start = (unsigned long) p;
  39. //循环对堆栈中的环境字符串指针数组初始化
  40. while (argc-->0) {
  41. char c;
  42. put_user(p,argv++);
  43. do {
  44. get_user(c,p++);
  45. } while (c);
  46. }
  47. put_user(NULL,argv);
  48. current->mm->arg_end = current->mm->env_start = (unsigned long) p;//env_start和arg_end
  49. while (envc-->0) {
  50. char c;
  51. put_user(p,envp++);
  52. do {
  53. get_user(c,p++);
  54. } while (c);
  55. }
  56. put_user(NULL,envp);
  57. current->mm->env_end = (unsigned long) p;//env_end
  58. return sp;//返回的是STACK_TOP减去图中参数所占空间
  59. }

来自为知笔记(Wiz)

时间: 2024-11-12 14:59:54

linux内核情景分析之execve()的相关文章

Linux内核情景分析的alloc_pages

NUMA结构的alloc_pages ==================== mm/numa.c 43 43 ==================== 43 #ifdef CONFIG_DISCONTIGMEM ==================== mm/numa.c 91 128 ==================== 91 /* 92 * This can be refined. Currently, tries to do round robin, instead 93 * sho

Linux内核情景分析之异常访问,用户堆栈的扩展

情景假设: 在堆内存中申请了一块内存,然后释放掉该内存,然后再去访问这块内存.也就是所说的野指针访问. 当cpu产生页面错误时,会把失败的线性地址放在cr2寄存器.线性地址缺页异常的4种情况 1.如果cpu访问的行现地址在内核态,那么很可能访问的是非连续区,需要vmalloc_fault处理. 2.缺页异常发生在中断或者内核线程时,直接失败,因为不可修改页表 3.地址在一个区间内,那就可能是已经物理地址映射了但权限问题(错误处理)或者其物理地址没有分配(分配物理内存) 4.如果找到一个在线性地址

linux内核情景分析之exit与Wait

//第一层系统调用 asmlinkage long sys_exit(int error_code) { do_exit((error_code&0xff)<<8); } 其主体是do_exit,接下来我们来看看do_exit的实现 NORET_TYPE void do_exit(long code) { struct task_struct *tsk = current;//获取当前进程描述符 if (in_interrupt())//禁止中断时调用do_exit panic(&qu

linux内核情景分析之强制性调度

从系统调用返回到用户空间是否调度,从ret_with_reschedule可看出,是否真正调度,取决于当前进程的pcb中的need_resched是否设置为1,那如何设置为1取决于以下几种情况: 时间中断处理程序,发现当前进程运行时间过长:每次发生时间中断,都要递减该进程的时间片,一旦count为0,强制调度,剥夺当前进程运行 void update_process_times(int user_tick) { struct task_struct *p = current; int cpu =

Linux源代码情景分析读书笔记 物理页面的分配

函数 alloc_pages流程图 Linux源代码情景分析读书笔记 物理页面的分配,布布扣,bubuko.com

linux 内核源代码分析 - 获取数组的大小

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) 測试程序: #include<stdio.h> #include<stdlib.h> struct dev { int a; char b; float c; }; struct dev devs[]= { { 1,'a',7.0, }, { 1,'a',7.0, }, { 1,'a',7.0, }, }; int main() { printf("int is %d \

Linux内核源代码分析方法

Linux内核源代码分析方法   一.内核源代码之我见 Linux内核代码的庞大令不少人"望而生畏",也正由于如此,使得人们对Linux的了解仅处于泛泛的层次.假设想透析Linux,深入操作系统的本质,阅读内核源代码是最有效的途径.我们都知道,想成为优秀的程序猿,须要大量的实践和代码的编写.编程固然重要,可是往往仅仅编程的人非常easy把自己局限在自己的知识领域内.假设要扩展自己知识的广度,我们须要多接触其它人编写的代码,尤其是水平比我们更高的人编写的代码.通过这样的途径,我们能够跳出

Linux内核Crash分析

转载自:http://linux.cn/article-3475-1.html 在工作中经常会遇到一些内核crash的情况,本文就是根据内核出现crash后的打印信息,对其进行了分析,使用的内核版本为:Linux2.6.32. 每一个进程的生命周期内,其生命周期的范围为几毫秒到几个月.一般都是和内核有交互,例如用户空间程序使用系统调用进入内核空间.这时使用的不再是用户空 间的栈空间,使用对应的内核栈空间.对每一个进程来说,Linux内核都会把两个不同的数据结构紧凑的存放在一个单独为进程分配的存储

linux内核与分析 心得与体会

作业目录: (1)计算机是如何工作的:http://www.cnblogs.com/20135335hs/p/5213394.html (2)操作系统是如何工作的:http://www.cnblogs.com/20135335hs/p/5248078.html (3)Linux系统启动过程:http://www.cnblogs.com/20135335hs/p/5271708.html (4)系统调用的方法:http://www.cnblogs.com/20135335hs/p/5297310.