问题的由来

我们知道,普通的C程序可以从命令行上接收参数,也可以使用、设置SEHLL环境变量(getenv,setenv),

/*一个简单的C例子 test.c*/

#include "stdio.h"

extern char** environ;   /*C库定义的全局变量,环境变量字符串数组的起始地址*/

int main(int argc, char* argv[])

{

int i;

char** p = environ;

for(i=0; i

printf("argv[%d]: %s\n", i, argv[i]);

printf("\n\nEnviroment Varibles\n\n");

while(*p != NULL)

printf("%s\n", *(p++));

return 0;

}

编译后在命令行上运行

:

./test arg1 arg2.

可以将运行结果与env输出结果比较

执行普通的C程序时,因为有SHELL的存在,命令行参数和环境变量可以由SHELL传给C程序,这一点似乎容易理解。然而,对于系统启动时运行的第一个用户程序init来说情况变得有些特别。init不是由用户在命令行上启动的(这时候根被还没有SHELL),而是由内核启动的,init程序里也使用了命令行参数和环境变量。

/*Busybox-1.19.3/init/init.c*/

int init_main(int argc UNUSED_PARAM, char **argv)

{

if (argv[1] && strcmp(argv[1], "-q") == 0) {

return kill(1, SIGHUP);

}

//……

console_init();

//……

}

static void console_init(void)

{

//.......

s = getenv("CONSOLE");

s = getenv("TERM");

//......

}

从上面的代码片段里可以看到init程序和普通应用程序一样,也使用到了命令行参数和环境变量,既然这个时候还没有SHELL,那么init使用的命令行参数和环境变量保存在何处、从何而来?

保存在何处

Linux采用了虚拟内存技术,进程里使用的均是虚拟地址,内核通过为进程建立不同的页表,可以将两个相互独立的进程的相同的虚拟地址映射到不同的物理地址上,因此,Linux下所有可执行文件运行时在地址空间里的映像布局结构是一样的,这简化了系统设计。

下图是C程序执行时,在进程的用户空间里的映像布局分布(没有画出C库及动态连接器映射的部分)。

程序

#include

#include

#include

extern char** environ;

int in_data_segment = 2;

int in_bss_segment;

int main(int argc, char* argv[])

{

char cmd[32];

char line[1024];

FILE *fp;

printf("addr of argv = %x\n\n", (void*)argv);

printf("addr of environ = %x\n", (void*)environ);

printf("addr of in_data_segment=%x\n", (void*)&in_data_segment);

printf("addr of in_bss_segment=%x\n\n", (void*)&in_bss_segment);

if ((fp = fopen("/proc/self/maps","r"))==NULL)

{

printf("open /proc/self/maps error\n");

return 0;

}

while(fgets(line, 1024, fp) != NULL)

fputs(line, stdout);

return 0;

}

程序运行结果

addr of argv = bfccb4f4

addr of environ = bfccb4fc

addr of in_data_segment = 80498e8

addr of in_bss_segment = 804992c

08048000-08049000 r-xp 00000000 75:00 106839     /home/temp/memlayout          /*text段*/

08049000-0804a000 rw-p 00000000 75:00 106839     /home/temp/memlayout        /*text、data、bss段*/

0804a000-0806b000 rw-p 0804a000 00:00 0               [heap]

b7f2f000-b809d000 r-xp 00000000 75:00 204907       /lib/libc-2.9.so

b809d000-b809f000 r--p 0016e000 75:00 204907     /lib/libc-2.9.so

b809f000-b80a0000 rw-p 00170000 75:00 204907     /lib/libc-2.9.so

b80a0000-b80a4000 rw-p b80a0000 00:00 0

b80a9000-b80ab000 rw-p b80a9000 00:00 0

b80ab000-b80cb000 r-xp 00000000 75:00 204900     /lib/ld-2.9.so

b80cb000-b80cc000 rw-p b80cb000 00:00 0

b80cc000-b80cd000 r--p 00020000 75:00 204900     /lib/ld-2.9.so

b80cd000-b80ce000 rw-p 00021000 75:00 204900     /lib/ld-2.9.so

bfcb8000-bfccd000 rw-p bffeb000 00:00 0          [stack]  /*命令行参数、环境变量、stack*/

ffffe000-fffff000 r-xp 00000000 00:00 0          [vdso] /*这部分属于内核空间*/

从上面的内存分布可以看出命令行参数和环境变量保存在进程地址空间的末端,下面的问题是参数和变量是从何而来,又是如何映射到了指定的位置。

从何而来

内核在完成一系列的软硬件初始化、成功挂载根文件系统之后,内核尝试在根文件系统里寻找init程序并加载运行,从这里进入到用户空间。

run_init_process("/sbin/init");

static void run_init_process(char *init_filename)

{

argv_init[0] = init_filename;

kernel_execve(init_filename, argv_init, envp_init);

}

kernel_execve是在内核空间里执行用户空间程序的接口,从调用形式上可以猜测,argv_init、 envp_init就是传递给init的命令行参数和环境变量参数。

/*体系结构相关,arch/arm/kernel/sys_arm.c*/

int kernel_execve(const char *filename, char *const argv[], char *const envp[])

{

struct pt_regs regs;

int ret;

memset(&s, 0, sizeof(struct pt_regs));

ret = do_execve((char *)filename, (char __user * __user *)argv,(char __user * __user *)envp, &s);

// do_execve调用成功的话kernel_execve就不会再返回内核空间而是到用户空//间去了,这通过操作栈来实现

}

int do_execve(char * filename, char __user *__user *argv, char __user *__user *envp,struct pt_regs * regs)

{

struct linux_binprm *bprm;

struct file *file;

//......

bprm = kzalloc(sizeof(*bprm), GFP_KERNEL);

file = open_exec(filename);

bprm->file = file;

retval = bprm_mm_init(bprm);

bprm->argc = count(argv, MAX_ARG_STRINGS);

bprm->envc = count(envp, MAX_ARG_STRINGS);

retval = copy_strings(bprm->envc, envp, bprm);

retval = copy_strings(bprm->argc, argv, bprm);

retval = search_binary_handler(bprm,regs);

}

copy_strings里会申请物理内存,将字符串拷贝到新申请的内存里。

search_binary_handler会根据可执行文件格式搜索系统已注册的的处理函数处理。

Linux下标准的可执行文件格式是ELF,除此之外,Linux还支持多种可执行文件格式。Linux用struct linux_binfmt数据结构来描述可执行文件格式,系统在启动时,由子系统调用register_binfmt来注册所支持的可执行文件格式,所有注册的struct linux_binfmt在内核里形成链表。内核在启动程序时,分析可执行文件的信息,在这个链表里搜索匹配的项。

struct linux_binfmt {

struct list_head lh;

struct module *module;

int (*load_binary)(struct linux_binprm *, struct  pt_regs * regs);

int (*load_shlib)(struct file *);

int (*core_dump)(struct coredump_params *cprm);

unsigned long min_coredump; /* minimal dump size */

int hasvdso;

};

对应到ELF格式,这里的load_binary的值是load_elf_binary

/*fs/binfmt_elf.c*/

static int load_elf_binary(struct linux_binprm *bprm, struct pt_regs *regs)

{

/* load_elf_binary 函数会根据bprm 的内容以及分析将要执行的elf格式的可执行文件的信息,将text、data、bss段映射到进程地址空间的相应位置,调用一个名为setup_arg_pages的函数,将之前保存到新申请的物理内存页的命令行参数和环境变量映射到进程地址空间的指定位置,构建出图一所示的内存分布,建立起运行环境,最后跳转到程序的入口处开始执行*/

start_thread(regs, elf_entry, bprm->p);

}

从上面的分析可以看出,传递给init的参数和环境变量的来自于argv_init和 envp_init。定义如下:

/*init/main.c*/

static char *argv_init[MAX_INIT_ARGS+2] = { "init", NULL, };

char *envp_init[MAX_INIT_ENVS+2] = { "HOME=/", "TERM=linux", NULL, };

这是两个内核全局变量,那么应该就处在内核空间,通过copy_strings、 setup_arg_pages的处理被映射到用户空间的指定位置,最终完成从内核到用户空间的传递。

除了定义时的默认值,bootloader启动内核时,还可以通过内核启动参数传递新的值给这两个变量。内核处理bootloader传递过来的启动参数时,分三类,

1. 内核子系统通过__setup注册的参数处理函数将会处理掉它能够识别的参数,如

__setup("init=", init_setup);启动参数里“init=”将被init_setup函数使用掉。

2. 剩下的形如”foo=bar”形式的参数解析为环境变量,保存到envp_init中。

3. 其余的参数保存到argv_init中。

小结

Bootloader引导Linux时,通过内核启动参数设置argv_init和envp_init的值

内核调用kernel_execv启动init程序,将argv_init和envp_init作为调用参数传递进去

do_execve里申请新的物理内存页,将argv_init和envp_init新申请到物理内存处,通过setup_arg_pages将物理内存页映射到进程地址空间的指定位置处

init程序运行起来以后,从进程地址空间的指定位置处获取命令行参数和环境变量

普通应用程序的情形

Linux下面一个应用程序运行起来分两个步骤。首先通过fork系统调用创建出一个新的进程,这个新创建的进程会继承父进程的地址空间里的一些内容。然后,在新创建的子进程中通过execve加载将要运行的可执行文件,加载的过程里会释放从父进程那里继承而来的旧的地址空间,并根据可执行文件的解析结果创建新的地址空间,自此,新的应用程序就运行起来了。execve函数原型:

int execve(const char*pathname,char*const argv[],char*const envp[]);

execve最终由内核系统调用处理函数:sys_execv处理。

/*体系结构相关,arch/arm/kernel/sys_arm.c*/

int sys_execve(char __user *filenamei, char __user * __user *argv,  char __user * __user *envp, struct pt_regs *regs)

{

int error;

char * filename;

//......

filename = getname(filenamei);

error = PTR_ERR(filename);

if (IS_ERR(filename))

goto out;

//......

error = do_execve(filename, argv, envp, regs);

//.....

putname(filename);

out:

return error;

}

可以看到,通过execve->sys_execve->do_execve执行普通用户程序和内核启动init程序,最终都到了do_execve,与init不同的是,argv和envp参数来源不一样。显然,普通用户程序的argv和envp参数只能是调用execve进程来指定。可以猜测,通过SHELL启动的用户程序,将由SHELL来准备好这两个参数。

/*以下例子来自于《UNIX环境高级编程》Figure 8.16*/

#include

#include

char *env_init[] = {"USER=unkown", "PATH=tmp", NULL};

int main(void)

{

pid_t pid;

if ((pid=fork())

{

printf("fork error\n");

return -1;

}

else if (pid == 0)  /*child*/

{

if (execle("/home/temp/exec/echo", "echo", "arg1", "arg2", (char*)0, env_init)

{

printf("execle error\n");

return -1;

}

}

if (waitpid(pid, NULL, 0)

{

printf("wait error");

return -1;

}

printf("test done!\n");

return 0;

}

/*/home/temp/exec/echo */

#include

extern char** environ;

int main(int argc, char* argv[])

{

int i;

char** ptr;

for(i=0; i

printf("argv[%d]: %s\n", i, argv[i]);

for(ptr=environ; *ptr!=NULL; ptr++)

printf("%s\n", *ptr);

}

运行结果:

argv[0]: echo

argv[1]: arg1

argv[2]: arg2

USER=unkown

PATH=tmp

test done!

linux启动exe程序命令行参数,Linux可执行文件的启动及命令行参数和环境变量的传递...相关推荐

  1. linux笔记:安装程序后,使用时显示找不到命令(command not found)

    在linux中,用各种工具下载安装程序后,使用时显示找不到命令(command not found)解决方法 其实这个问题与在windows安装程序后,在命令行下使用不了是一个原因:没有配置环境变量 ...

  2. Qt中直接启动.exe程序,错误提示:无法启动此程序,因为计算机中丢失Qt5Core.dll。尝试重新安装该程序以解决此问题

    双击.exe程序 错误现象: 方法一: 一般客户使用时,让他安装qt太麻烦 找到Qt安装目录下的bin目录 拷贝库文件:Qt5Core.dll,Qt5Gui.dll,Qt5Widgets.dll,li ...

  3. 【Linux】Linux系统编程(入门与系统编程)(三)(深入理解操作系统、进程、环境变量、内存分布)

    本博客操作系统最多涉及30%的理论,重点在于部分进程的内容,部分文件系统的内容,部分文件管理的内容不是主讲操作系统,我们的最终目的是理解系统中最高频的知识点,然后被完全利用指导我们编程. 下面是这三篇 ...

  4. 测试linux系统的程序员,日常测试Linux命令

    命令 cd 1. 如何进入上级目录 cd .. 2. 如何进入当前用户主目录 cd ~ 3. 如何进入上两级目录 cd ../.. 4. 进入当前目录命令 cd . 5. 如何进入目录 /usr/is ...

  5. linux系统安装exe程序,linux使用wine安装windows exe程序

    最近换到Ubuntu下工作,但是很怀念source insight,记得有款软件即wine可以支持在linux平台下运行exe程序. 1.下载wine 我使用的是ubuntu,可以在终端键入 sudo ...

  6. linux上开发应用程序_如何在Linux上安装软件应用程序

    linux上开发应用程序 如何在Linux上安装应用程序? 与许多操作系统一样,该问题不仅有一个答案. 应用程序可以来自许多来源-几乎无法计数-每个开发团队都可以以自己认为最佳的方式交付软件. 知道如 ...

  7. linux中把程序启到前台,Linux操作系统桌面应用与管理Q4rw2进程与作业管理-PPT精品文档.ppt...

    红旗Linux,情境四任务2:进程和作业管理,任务2-1,了解进程管理知识用命令实现进程管理,Linux进程管理,WINDOWS?任务管理器LINUX利用命令管理进程包括前.后台进程的管理以及终止等, ...

  8. linux系统 exe文件怎么打开方式,linux操作系统下,exe文件为什么打不开?

    跃然一笑 linux下默认是无法打开exe文件,需要下载wine支持exe,但并非所有exe都可以运行的.Wine ("Wine Is Not an Emulator" 的首字母缩 ...

  9. c#Process.Start无法启动exe程序的问题

    本文链接:https://blog.csdn.net/stypace/article/details/12083027 1.可能是参数不是绝对路径 2.如果要启动的程序为单独一个exe文件没有问题,而 ...

  10. 如何在虚拟机linux下运行程序吗,如何在Linux上运行Windows应用程序?

    一些人可能避免在Linux上运行Windows应用程序,但事实上有时候这么做大有帮助. 一些人无法摆脱Windows的最主要原因之一是可用的应用程序.有时候,这些应用程序是老式应用程序,没有开源替代版 ...

最新文章

  1. 爱奇艺一程序员用 10 万元“买”了个北京户口
  2. 数据库单表数据过亿_最受欢迎的三大数据库,你用过吗?
  3. pytorch 笔记: 复现论文 Stochastic Weight Completion for Road Networks using Graph Convolutional Networks
  4. Pytorch中DataLoader类
  5. java 大文件 处理_用Java处理大文件
  6. c# 写文件注意问题及用例展示
  7. 怎么激活linux系统远程桌面休眠状态,如何从命令行挂起/休眠?
  8. 可变字典 NSMutableDictionary
  9. python并行计算进程池通信_Python使用进程池管理进程和进程间通信
  10. dpkg命令_Linux 命令学习神器!命令看不懂直接给你解释!
  11. php unpack 详解,【PHP】 pack unpack 详解
  12. 乌镇·Conflux CTO伍鸣:让公链的“不可能三角”成为可能
  13. android 页面默认不弹软键盘_Android避免进入页面自动弹出软键盘(真正好用)
  14. 2.6java基础 数组
  15. CS269I:Incentives in Computer Science 学习笔记 Lecture 17 评分规则和同辈预测(诚实预报和反馈激励)
  16. UML ~ Unified Modeling Language ~ 统一建模语言。+ 软件设计原则。
  17. The Accelerator Wall: Limits of Chip Specialization
  18. Python 介绍和环境准备
  19. BZOJ1202 狡猾的商人 (Floyd)
  20. JVM#Java高墙之内存模型

热门文章

  1. basler 相机取图超时,basler 相机使用出现的问题
  2. uni-app 安卓实现监听 PDA 扫码枪等设备按钮
  3. STM32 USB Mass Storage 例程调试笔记
  4. 路由器重温——可靠性-接口备份和双机热备份配置管理
  5. c语言等号与符号优先级,【C语言】符号优先级
  6. 【音频处理】使用 Adobe Audition 录制电脑内部声音 ( 启用电脑立体声混音 | Adobe Audition 中设置音频设备 | Adobe Audition 内录 )
  7. DllMain——DLL程序入口点函数
  8. 芯片达人教你如何看数据手册
  9. 开心问答—首个基于迅雷链智能合约上执行的问答游戏
  10. 百度网盘直链原理解析