linux内核情景分析之execve()
Posted 笨拙的菜鸟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了linux内核情景分析之execve()相关的知识,希望对你有一定的参考价值。
用来描述用户态的cpu寄存器在内核栈中保存情况.可以获取用户空间的信息
struct pt_regs {
long ebx; //可执行文件路径的指针(regs.ebx中
long ecx; //命令行参数的指针(regs.ecx中)
long edx; //环境变量的指针(regs.edx中)。
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
asmlinkage int sys_execve(struct pt_regs regs)
{
int error;
char * filename;
- filename = getname((char *) regs.ebx);//ebx为"/bin/echo",把字符串从用户空间拷贝到系统空间
error = PTR_ERR(filename);//判断是否出错
if (IS_ERR(filename))
goto out;
//文件名 参数 NULL 传入副本
error = do_execve(filename, (char **) regs.ecx, (char **) regs.edx, ®s);//ecx为args,edx为NULL
if (error == 0)
current->ptrace &= ~PT_DTRACE;
putname(filename);//将之前为文件名分配的空间释放掉
out:
return error;
}
linux内核每种被注册的可执行程序格式都用linux_bin_fmt来存储,其中记录了可执行程序的加载和执行函数,同时我们需要一种方法来保存可执行程序的信息, 比如可执行文件的路径, 运行的参数和环境变量等信息,即linux_bin_prm结构
/*
* This structure is used to hold the arguments that are used when loading binaries.
*/
struct linux_binprm {
char buf[BINPRM_BUF_SIZE]; // 保存可执行文件的头128字节
#ifdef CONFIG_MMU
struct vm_area_struct *vma;
unsigned long vma_pages;
#else
# define MAX_ARG_PAGES 32
struct page *page[MAX_ARG_PAGES];
#endif
struct mm_struct *mm;
unsigned long p; /* current top of mem , 当前内存页最高地址*/
unsigned int
cred_prepared:1,/* true if creds already prepared (multiple
* preps happen for interpreters) */
cap_effective:1;/* true if has elevated effective capabilities,
* false if not; except for init which inherits
* its parent‘s caps anyway */
#ifdef __alpha__
unsigned int taso:1;
#endif
unsigned int recursion_depth; /* only for search_binary_handler() */
struct file * file; /* 要执行的文件 */
struct cred *cred; /* new credentials */
int unsafe; /* how unsafe this exec is (mask of LSM_UNSAFE_*) */
unsigned int per_clear; /* bits to clear in current->personality */
int argc, envc; /* 命令行参数和环境变量数目 */
const char * filename; /* Name of binary as seen by procps, 要执行的文件的名称 */
const char * interp; /* Name of the binary really executed. Most
of the time same as filename, but could be
different for binfmt_{misc,script} 要执行的文件的真实名称,通常和filename相同 */
unsigned interp_flags;
unsigned interp_data;
unsigned long loader, exec;
};
int do_execve(char * filename, char ** argv, char ** envp, struct pt_regs * regs)
{
struct linux_binprm bprm;//存储可执行文件信息
struct file *file;
int retval;
int i;
file = open_exec(filename);//打开目标文件 /bin/echo
retval = PTR_ERR(file);
if (IS_ERR(file))
return retval;
bprm.p = PAGE_SIZE*MAX_ARG_PAGES-sizeof(void *);//1024*32减去一个指针的大小,当前页最高地址
memset(bprm.page, 0, MAX_ARG_PAGES*sizeof(bprm.page[0])); //page指针数组初始化为0
bprm.file = file;
bprm.filename = filename;
bprm.sh_bang = 0;//可执行文件属性.0表示二进制
bprm.loader = 0;
bprm.exec = 0;//
参数的起始地址(从上往下方向)if ((bprm.argc = count(argv, bprm.p / sizeof(void *))) < 0) {//对指针数组argv[]中参数的个数进行计数,而bprm.p / sizeof(void *)表示允许的最大值,由于agrv[]是在用户空间而不在系统空间
allow_write_access(file);//防止其他进程在读入可执行文件期间通过内存映射改变它的内容
fput(file);
return bprm.argc;
}
if ((bprm.envc = count(envp, bprm.p / sizeof(void *))) < 0) {//同上环境变量
allow_write_access(file);
fput(file);
return bprm.envc;
}
retval = prepare_binprm(&bprm);//见下面的代码,为bprm结构做准备,读取128个字节到缓冲区
if (retval < 0)
goto out;
retval = copy_strings_kernel(1, &bprm.filename, &bprm);//由于bprm.filename已经在系统空间了,所以copy_strings_kernel从系统空间中拷贝
if (retval < 0)
goto out;
bprm.exec = bprm.p;//参数的起始地址
retval = copy_strings(bprm.envc, envp, &bprm);//从用户空间拷贝,参考copy_strings_kernel
if (retval < 0)
goto out;
retval = copy_strings(bprm.argc, argv, &bprm);//从用户空间拷贝,参考copy_strings_kernel
if (retval < 0)
goto out;
retval = search_binary_handler(&bprm,regs);//已经从可执行文件头部读入了128个字节存放在bprm的缓冲区,而且运行所需的参数和环境变量也已经搜集在bprm中,现在就由formats队列中的成员逐个来认领,谁要识别到它代表的可执行文件格式,运行的时候就交给它
if (retval >= 0)
/* execve success */
return retval;
out:
/*出错,返回inode和释放argv的页 Something went wrong, return the inode and free the argument pages*/
allow_write_access(bprm.file);
if (bprm.file)
fput(bprm.file);//释放文件对象
for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
struct page * page = bprm.page[i];
if (page)
__free_page(page);
}
return retval;
}
分析prepare_binprm
int prepare_binprm(struct linux_binprm *bprm)
{
int mode;
struct inode * inode = bprm->file->f_dentry->d_inode;//获取打开的文件节点
mode = inode->i_mode;//类型
/* Huh? We had already checked for MAY_EXEC, WTF do we check this? */
if (!(mode & 0111)) /* with at least _one_ execute bit set最少可执行状态 */
return -EACCES;
if (bprm->file->f_op == NULL)//针对文件操作不允许为空
return -EACCES;
bprm->e_uid = current->euid;//继承uid
bprm->e_gid = current->egid;//继承gid
if(!IS_NOSUID(inode)) {//是否是SUID
/* Set-uid? */
if (mode & S_ISUID)//如果有SUID.那就设置
bprm->e_uid = inode->i_uid;
/* Set-gid? */
/*
* If setgid is set but no group execute bit then this
* is a candidate for mandatory locking, not a setgid
* executable.
*/
if ((mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
bprm->e_gid = inode->i_gid;
}
/* We don‘t have VFS support for capabilities yet */
cap_clear(bprm->cap_inheritable);
cap_clear(bprm->cap_permitted);
cap_clear(bprm->cap_effective);
/* To support inheritance of root-permissions and suid-root
* executables under compatibility mode, we raise all three
* capability sets for the file.
*
* If only the real uid is 0, we only raise the inheritable
* and permitted sets of the executable file.
*/
if (!issecure(SECURE_NOROOT)) {
if (bprm->e_uid == 0 || current->uid == 0) {
cap_set_full(bprm->cap_inheritable);
cap_set_full(bprm->cap_permitted);
}
if (bprm->e_uid == 0)
cap_set_full(bprm->cap_effective);
}
memset(bprm->buf,0,BINPRM_BUF_SIZE);//从可执行文件中读入开头的128个字节到linux_binprm结构bprm中的缓冲区
return kernel_read(bprm->file,0,bprm->buf,BINPRM_BUF_SIZE);
}
inux内核对所支持的每种可执行的程序类型都有个struct linux_binfmt的数据结构,定义如下
search_binary_handler,已经从可执行文件头部读入了128个字节存放在bprm的缓冲区,而且运行所需的参数和环境变量也已经搜集在bprm中,现在就由formats队列中的成员逐个来认领,谁要识别到它代表的可执行文件格式,运行的时候就交给它,代码如下
struct linux_binfmt {
struct list_head lh;
struct module *module;
int (*load_binary)(struct linux_binprm *);//
通过读存放在可执行文件中的信息为当前进程建立一个新的执行环境int (*load_shlib)(struct file *);//
用于动态的把一个共享库捆绑到一个已经在运行的进程, 这是由uselib()系统调用激活的int (*core_dump)(struct coredump_params *cprm);//
在名为core的文件中, 存放当前进程的执行上下文. 这个文件通常是在进程接收到一个缺省操作为”dump”的信号时被创建的, 其格式取决于被执行程序的可执行类型unsigned long min_coredump; /* minimal dump size */
};
int search_binary_handler(struct linux_binprm *bprm,struct pt_regs *regs)
{
int try,retval=0;
struct linux_binfmt *fmt;
......
//一共2层循环,外出循环式为了安装了模块后再试一次
for (try=0; try<2; try++) {
read_lock(&binfmt_lock);
//
所有的linux_binfmt对象都处于一个链表中, 第一个元素的地址存放在formats变量中for (fmt = formats ; fmt ; fmt = fmt->next) {
int (*fn)(struct linux_binprm *, struct pt_regs *) = fmt->load_binary;
if (!fn)//是否是可执行文件
continue;
if (!try_inc_mod_count(fmt->module))//是否是动态安装库
continue;
read_unlock(&binfmt_lock);
retval = fn(bprm, regs);//加载,如果谁都不认识,返回-ENOEXEC表示对不上号
if (retval >= 0) {
put_binfmt(fmt);
allow_write_access(bprm->file);
if (bprm->file)
fput(bprm->file);
bprm->file = NULL;
current->did_exec = 1;
return retval;
}
read_lock(&binfmt_lock);
put_binfmt(fmt);
if (retval != -ENOEXEC)//只要不是对不上号,就退出循环
break;
if (!bprm->file) {
read_unlock(&binfmt_lock);
return retval;
}
}
read_unlock(&binfmt_lock);
if (retval != -ENOEXEC) {
break;
#ifdef CONFIG_KMOD
}else{
#define printable(c) (((c)==‘\t‘) || ((c)==‘\n‘) || (0x20<=(c) && (c)<=0x7e))
char modname[20];
if (printable(bprm->buf[0]) &&
printable(bprm->buf[1]) &&
printable(bprm->buf[2]) &&
printable(bprm->buf[3]))
break; /* -ENOEXEC */
sprintf(modname, "binfmt-%04x", *(unsigned short *)(&bprm->buf[2]));
request_module(modname);//试着将相应的模块装入,一共进行两次循环,正是为了在安装了模块以后再来试一次
#endif
}
}
return retval;
}
struct exec
{
unsigned long a_info; /* Use macros N_MAGIC, etc for access */
unsigned a_text; /* length of text, in bytes */
unsigned a_data; /* length of data, in bytes */
unsigned a_bss; /* length of uninitialized data area for file, in bytes */
unsigned a_syms; /* length of symbol table data in file, in bytes */
unsigned a_entry; /* start address */
unsigned a_trsize; /* length of relocation info for text, in bytes */
unsigned a_drsize; /* length of relocation info for data, in bytes */
};
我们假设/bin/echo是a.out格式。所以fn(bprm, regs),执行的代码是load_aout_binary。
static int load_aout_binary(struct linux_binprm * bprm, struct pt_regs * regs)
{
struct exec ex;
unsigned long error;
unsigned long fd_offset;
unsigned long rlim;
int retval;
ex = *((struct exec *) bprm->buf); //128字节可执行文件头部
if ((N_MAGIC(ex) != ZMAGIC && N_MAGIC(ex) != OMAGIC &&
N_MAGIC(ex) != QMAGIC && N_MAGIC(ex) != NMAGIC) ||
N_TRSIZE(ex) || N_DRSIZE(ex) ||
bprm->file->f_dentry->d_inode->i_size < ex.a_text+ex.a_data+N_SYMSIZE(ex)+N_TXTOFF(ex)) {
return -ENOEXEC;//匹配不会返回-ENOEXEC
}
fd_offset = N_TXTOFF(ex);//根据代码的特性取得代码段在目标文件中的起始位置
/* Check initial limits. This avoids letting people circumvent
* size limits imposed on them by creating programs with large
* arrays in the data or bss.
*/
rlim = current->rlim[RLIMIT_DATA].rlim_cur;//获取限制条件
if (rlim >= RLIM_INFINITY)
rlim = ~0;
if (ex.a_data + ex.a_bss > rlim)//代码段和bss段的总和不能超过限制
return -ENOMEM;
/* Flush all traces of the currently running executable */
retval = flush_old_exec(bprm);//到了"与过去告别"的时候了,这种"告别过去"意味着放弃从父进程"继承"下来的全部用户空间,不管是通过复制还是通过指针共享继承下来的
if (retval)
return retval;
/* OK, This is the point of no return */
#if !defined(__sparc__)
set_personality(PER_LINUX);
#else
set_personality(PER_SUNOS);
#if !defined(__sparc_v9__)
memcpy(current->thread.core_exec, &ex, sizeof(struct exec));
#endif
#endif
current->mm->end_code = ex.a_text +
(current->mm->start_code = N_TXTADDR(ex));//代码段起始和结束地址,详细请看紧邻本代码段的相关代码
current->mm->end_data = ex.a_data +
(current->mm->start_data = N_DATADDR(ex));//数据段起始和结束地址,数据段起始地址就是代码段结束地址
current->mm->brk = ex.a_bss +
(current->mm->start_brk = N_BSSADDR(ex));//bss段起始和结束地址,bss起始地址就是数据段结束地址
current->mm->rss = 0;
current->mm->mmap = NULL;
compute_creds(bprm);//确定是否具有其权限
current->flags &= ~PF_FORKNOEXEC;
#ifdef __sparc__
if (N_MAGIC(ex) == NMAGIC) {//其他格式
loff_t pos = fd_offset;//elf代码段偏移地址
/* Fuck me plenty... */
error = do_brk(N_TXTADDR(ex), ex.a_text);
bprm->file->f_op->read(bprm->file, (char *) N_TXTADDR(ex),
ex.a_text, &pos);
error = do_brk(N_DATADDR(ex), ex.a_data);
bprm->file->f_op->read(bprm->file, (char *) N_DATADDR(ex),
ex.a_data, &pos);
goto beyond_if;
}
#endif
if (N_MAGIC(ex) == OMAGIC) {//如果魔术是OMAGIC
unsigned long text_addr, map_size;
loff_t pos;
text_addr = N_TXTADDR(ex);//代码段起始地址
#if defined(__alpha__) || defined(__sparc__)
pos = fd_offset;//根据代码的特性取得代码段在目标文件中的起始位置
map_size = ex.a_text+ex.a_data + PAGE_SIZE - 1;//代码段和数据段的大小
#else
pos = 32;
map_size = ex.a_text+ex.a_data;
#endif
error = do_brk(text_addr & PAGE_MASK, map_size);//为正文段和数据段合在一起分配空间
if (error != (text_addr & PAGE_MASK)) {//出错
send_sig(SIGKILL, current, 0);//杀死当前进程
return error;
}
error = bprm->file->f_op->read(bprm->file, (char *)text_addr,
ex.a_text+ex.a_data, &pos);//然后就把这两部分从文件中读进来,这样就有了vm_struct结构,对应的页目录表项,页表项,页面,而且页面已经放入了文件中的代码段和数据段
if (error < 0) {
send_sig(SIGKILL, current, 0);
return error;
}
flush_icache_range(text_addr, text_addr+ex.a_text+ex.a_data);
} else {//如果魔术不是OMAGIC
static unsigned long error_time, error_time2;
if ((ex.a_text & 0xfff || ex.a_data & 0xfff) &&
(N_MAGIC(ex) != NMAGIC) && (jiffies-error_time2) > 5*HZ)
{
printk(KERN_NOTICE "executable not page aligned\n");
error_time2 = jiffies;
}
if ((fd_offset & ~PAGE_MASK) != 0 &&
(jiffies-error_time) > 5*HZ)
{
printk(KERN_WARNING
"fd_offset is not page aligned. Please convert program: %s\n",
bprm->file->f_dentry->d_name.name);
error_time = jiffies;
}
if (!bprm->file->f_op->mmap||((fd_offset & ~PAGE_MASK) != 0)) {//如果没有mmap函数或者代码段及数据段的长度不与页面大小对齐
loff_t pos = fd_offset;
do_brk(N_TXTADDR(ex), ex.a_text+ex.a_data);//和上面的方式一样,正文段和数据段合在一起分配空间
bprm->file->f_op->read(bprm->file,(char *)N_TXTADDR(ex),
ex.a_text+ex.a_data, &pos);//然后就把这两部分从文件中读进来,这样就有了vm_struct结构,对应的页目录表项,页表项,页面,而且页面已经放入了文件中的代码段和数据段
flush_icache_range((unsigned long) N_TXTADDR(ex),
(unsigned long) N_TXTADDR(ex) +
ex.a_text+ex.a_data);
goto beyond_if;
}
down(current->mm->mmap_sem);
error = do_mmap(bprm->file, N_TXTADDR(ex), ex.a_text,//如果有mmap,将文件的代码段映射到进程的用户空间
PROT_READ | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE,
fd_offset);
up(current->mm->mmap_sem);
if (error != N_TXTADDR(ex)) {
send_sig(SIGKILL, current, 0);
return error;
}
down(current->mm->mmap_sem);
error = do_mmap(bprm->file, N_DATADDR(ex), ex.a_data,///如果有mmap,将文件的数据段映射到进程的用户空间
PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_FIXED | MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE,
fd_offset + ex.a_text);//数据段在文件中的偏移是fd_offset + ex.a_text
up(current->mm->mmap_sem);
if (error != N_DATADDR(ex)) {
send_sig(SIGKILL, current, 0);
return error;
}
}
beyond_if:
set_binfmt(&aout_format);//current->binfmt = aout_format;
set_brk(current->mm->start_brk, current->mm->brk);//为可执行文件的bss段分配空间并建立起页面映射
retval = setup_arg_pages(bprm); //在用户空间的堆栈区顶部为进程建立起一个虚拟区间,并将执行参数以及环境变量所占的物理页面与此虚拟区间建立起映射
if (retval < 0) {
/* Someone check-me: is this error path enough? */
send_sig(SIGKILL, current, 0);
return retval;
}
current->mm->start_stack =
(unsigned long) create_aout_tables((char *) bprm->p, bprm);//把一些变量放入用户堆栈中,返回的是STACK_TOP减去图中参数所占空间,也就是堆栈开始的虚拟地址
#ifdef __alpha__
regs->gp = ex.a_gpvalue;
#endif
//起始地址 ,栈指针
start_thread(regs, ex.a_entry, current->mm->start_stack);//设置系统堆栈中的eip,esp
if (current->ptrace & PT_PTRACED)
send_sig(SIGTRAP, current, 0);
return 0;
}
复制信号处理函数指针数组,放弃用户空间或者不跟父进程共享内存空间,如果此进程实际为一个线程组的线程,那就从线程组中脱离,改变信号处理模式为默认处理,关闭file文件对象
int flush_old_exec(struct linux_binprm * bprm)
{
char * name;
int i, ch, retval;
struct signal_struct * oldsig;
/*
* Make sure we have a private signal table
*/
oldsig = current->sig;//获取当前进程信号处理函数
retval = make_private_signals();//如果子进程通过指针来共享父进程的信号处理表,就要把它复制过来
if (retval) goto flush_failed;
/*
* Release all of the old mmap stuff
*/
retval = exec_mmap();//从父进程继承下来的用户空间就是在这里放弃的
if (retval) goto mmap_failed;
/* This is the point of no return */
release_old_signals(oldsig);//如果子进程通过指针来共享父进程的信号处理表,那么就要减少oldsig->count的计数
current->sas_ss_sp = current->sas_ss_size = 0;
if (current->euid == current->uid && current->egid == current->gid)
current->dumpable = 1;
name = bprm->filename;
for (i=0; (ch = *(name++)) != ‘\0‘;) {
if (ch == ‘/‘)
i = 0;
else
if (i < 15)
current->comm[i++] = ch;
}
current->comm[i] = ‘\0‘;//comm[],用于保存进程所执行的程序名,所以还要把bprm->filename的目标程序路径名的最后一段抄过去
flush_thread();//不用关心
de_thread(current);//如果当前进程是一个线程,从线程组中脱离出来
if (bprm->e_uid != current->euid || bprm->e_gid != current->egid ||
permission(bprm->file->f_dentry->d_inode,MAY_READ))
current->dumpable = 0;
/* An exec changes our domain. We are no longer part of the thread
group */
current->self_exec_id++;
flush_signal_handlers(current);//原来指向用户空间子程序的,现在设为SIG_DEL,表示采取预设的响应方式
flush_old_files(current->files);//对原有已打开文件的处理
return 0;
mmap_failed:
flush_failed:
spin_lock_irq(current->sigmask_lock);
if (current->sig != oldsig)
kfree(current->sig);
current->sig = oldsig;
spin_unlock_irq(current->sigmask_lock);
return retval;
}
flush_old_exec中的make_private_signal
static inline int make_private_signals(void)
{
struct signal_struct * newsig;
if (atomic_read(current->sig->count) <= 1)//如果只是把父进程的信号处理表指针复制过来,而通过这指针来共享父进程的信号处理表,那么current->sig->count大于1
return 0;
newsig = kmem_cache_alloc(sigact_cachep, GFP_KERNEL);//自立门户,和copy_sighand()基本相同
if (newsig == NULL)
return -ENOMEM;
spin_lock_init(&newsig->siglock);
atomic_set(&newsig->count, 1);
memcpy(newsig->action, current->sig->action, sizeof(newsig->action));//拷贝
spin_lock_irq(current->sigmask_lock);
current->sig = newsig;//获得新的
spin_unlock_irq(current->sigmask_lock);
return 0;
}
flush_old_exec的exec_mmap函数
static int exec_mmap(void)
{
struct mm_struct * mm, * old_mm;
old_mm = current->mm;
if (old_mm && atomic_read(&old_mm->mm_users) == 1) {//如果共享计数为1时,表明对此空间的使用是独占的,也就是说这是从父进程复制过来的
flush_cache_mm(old_mm);//释放缓存
mm_release();//针对vfork.父进程减少信号量休眠,以后让子进程up将其唤醒
exit_mmap(old_mm);//释放mm_struct数据结构以下的所有vm_area_struct数据结构(但是不包括mm_struct结构本身),并将页面表项设置为0
flush_tlb_mm(old_mm);//释放tlb的缓存
return 0;
}
mm = mm_alloc();//如果只是将指向mm_struct数据结构的指针复制给子进程,让子进程通过这个指针来共享父进程的用户空间,那就可以跳过释放用户空间这一步,直接就为子进程分配新的用户空间
if (mm) {
struct mm_struct *active_mm = current->active_mm;//
if (init_new_context(current, mm)) {//空语句
mmdrop(mm);
return -ENOMEM;
}
/* Add it to the list of mm‘s */
spin_lock(&mmlist_lock);
list_add(&mm->mmlist, &init_mm.mmlist);
spin_unlock(&mmlist_lock);
task_lock(current);
current->mm = mm;//新分配空户空间初始化
current->active_mm = mm;//新分配的空户空间
task_unlock(current);
activate_mm(active_mm, mm);//切换到新的的用户空间
mm_release();//将父进程唤醒
if (old_mm) {
if (active_mm != old_mm) BUG();//如果不一致,说明线程成为了进程
mmput(old_mm);//减少mm->mm_users计数,因为已经不共享了
return 0;
}
mmdrop(active_mm);//如果已经没有进程使用这空间.将旧的空间给释放掉
return 0;
}
return -ENOMEM;
}
根据close_on_exec位图,里面存储着表示哪些文件在执行一个新目标程序时应予关闭的信息。根据这个位图的指示将这些文件关闭,并且将此位图清成全0。
static inline void flush_old_files(struct files_struct * files)
{
long j = -1;
write_lock(&files->file_lock);
for (;;) {
unsigned long set, i;
j++;
i = j * __NFDBITS;
if (i >= files->max_fds || i >= files->max_fdset)
break;
set = files->close_on_exec->fds_bits[j];
if (!set)
continue;
files->close_on_exec->fds_bits[j] = 0;
write_unlock(&files->file_lock);
for ( ; set ; i++,set >>= 1) {
if (set & 1) {
sys_close(i);
}
}
write_lock(&files->file_lock);
}
write_unlock(&files->file_lock);
}
void mmput(struct mm_struct *mm)
{
if (atomic_dec_and_lock(&mm->mm_users, &mmlist_lock)) {//减去1,是否为0
list_del(&mm->mmlist);
spin_unlock(&mmlist_lock);
exit_mmap(mm);//将页表设置为0
mmdrop(mm);//将页表项与页表,本身mm_struct全部释放
}
}
int setup_arg_pages(struct linux_binprm *bprm)
{
unsigned long stack_base;
struct vm_area_struct *mpnt;
int i;
stack_base = STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE;//STACK_TOP为3GB
bprm->p += stack_base;//bprm->p原来是MAX_ARG_PAGES*PAGE_SIZE-参数的数量,现在加上STACK_TOP - MAX_ARG_PAGES*PAGE_SIZE,最后是STACK_TOP - 参数的数量
if (bprm->loader)
bprm->loader += stack_base;
bprm->exec += stack_base;
mpnt = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);//从slab中获取一页
if (!mpnt)
return -ENOMEM;
down(¤t->mm->mmap_sem);
{
mpnt->vm_mm = current->mm;//内存描述符
mpnt->vm_start = PAGE_MASK & (unsigned long) bprm->p;//堆栈段开始地址
mpnt->vm_end = STACK_TOP;//堆栈段结束地址
mpnt->vm_page_prot = PAGE_COPY;
mpnt->vm_flags = VM_STACK_FLAGS;
mpnt->vm_ops = NULL;
mpnt->vm_pgoff = 0;
mpnt->vm_file = NULL;
mpnt->vm_private_data = (void *) 0;
insert_vm_struct(current->mm, mpnt);//插入
current->mm->total_vm = (mpnt->vm_end - mpnt->vm_start) >> PAGE_SHIFT;
}
for (i = 0 ; i < MAX_ARG_PAGES ; i++) {
struct page *page = bprm->page[i];//从page[0]开始
if (page) {
bprm->page[i] = NULL;
current->mm->rss++;
put_dirty_page(current,page,stack_base);//建立从堆栈虚拟空间到页面之前的映射
}
stack_base += PAGE_SIZE;//加一个页面的长度,4096个字节
}
up(¤t->mm->mmap_sem);
return 0;
}
static unsigned long * create_aout_tables(char * p, struct linux_binprm * bprm)
{
char **argv, **envp;
unsigned long * sp;
int argc = bprm->argc;//程序参数个数
int envc = bprm->envc;//环境字符串个数
- //sp指向当前堆栈页面的栈顶指针
- sp = (unsigned long *) ((-(unsigned long)sizeof(char *)) & (unsigned long) p);
#ifdef __sparc__
/* This imposes the proper stack alignment for a new process. */
sp = (unsigned long *) (((unsigned long) sp) & ~7);
if ((envc+argc+3)&1) --sp;
#endif
#ifdef __alpha__
/* whee.. test-programs are so much fun. */
put_user(0, --sp);
put_user(0, --sp);
if (bprm->loader) {
put_user(0, --sp);
put_user(0x3eb, --sp);
put_user(bprm->loader, --sp);
put_user(0x3ea, --sp);
}
put_user(bprm->exec, --sp);
put_user(0x3e9, --sp);
#endif
//sp减去envc+1作为环境字符串指针数组的起始地址,最后一个数组元素为0
sp -= envc+1;
envp = (char **) sp;//指向环境字符串数组的起始地址
sp -= argc+1;//同上
argv = (char **) sp;
#if defined(__i386__) || defined(__mc68000__) || defined(__arm__)
//栈顶sp继续向低地址放扩展,存放envp,argv,argc
put_user((unsigned long) envp,--sp);
put_user((unsigned long) argv,--sp);
#endif
put_user(argc,--sp);
current->mm->arg_start = (unsigned long) p;
//循环对堆栈中的环境字符串指针数组初始化
while (argc-->0) {
char c;
put_user(p,argv++);
do {
get_user(c,p++);
} while (c);
}
put_user(NULL,argv);
current->mm->arg_end = current->mm->env_start = (unsigned long) p;//env_start和arg_end
while (envc-->0) {
char c;
put_user(p,envp++);
do {
get_user(c,p++);
} while (c);
}
put_user(NULL,envp);
current->mm->env_end = (unsigned long) p;//env_end
return sp;//返回的是STACK_TOP减去图中参数所占空间
}
以上是关于linux内核情景分析之execve()的主要内容,如果未能解决你的问题,请参考以下文章
结合中断上下文切换和进程上下文切换分析Linux内核一般执行过程
结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程
结合中断上下文切换和进程上下文切换分析Linux内核的一般执行过程