精华内容
下载资源
问答
  • 也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...

    http://blog.csdn.net/pipisorry/article/details/25134579

    exec函数族

    也许有不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?而且,就我们的常识来说,当我们执行一个程序的时候,新产生的进程的内容应就是程序的内容才对。是我们理解错了吗?显然不是,要解决这些疑惑,就必须提到我们下面要介绍的exec系统调用。
    1.10.1 简介
    说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:
    #include <unistd.h>
    int execl(const char *path, const char *arg, ...);
    int execlp(const char *file, const char *arg, ...);
    int execle(const char *path, const char *arg, ..., char *const envp[]);
    int execv(const char *path, char *const argv[]);
    int execvp(const char *file, char *const argv[]);
    int execve(const char *path, char *const argv[], char *const envp[]);
    其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
    exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。
    这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
    与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样。只有调用失败了,它们才会返回一个-1,从原程序的调用点接着往下执行。
    现在我们应该明白了,Linux下是如何执行新程序的,每当有进程认为自己不能为系统和拥护做出任何贡献了,他就调用任何一个exec,让自己以新的面貌重生;
    或者,更普遍的情况是,如果一个进程想执行另一个程序,它就可以fork出一个新进程,然后调用任何一个exec,这样看起来就好像通过执行应用程序而产生了一个新进程一样。
    事实上第二种情况被应用得如此普遍,以至于Linux专门为其作了优化,我们已经知道,fork会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去,这些拷贝的动作很消耗时间,而如果fork完之后我们马上就调用exec,这些辛辛苦苦拷贝来的东西又会被立刻抹掉,这看起来非常不划算,于是人们设计了一种"写时拷贝(copy-on-write)"技术,使得fork结束后并不立刻复制父进程的内容,而是到了真正实用的时候才复制,这样如果下一条语句是exec,它就不会白白作无用功了,也就提高了效率。
    1.10.2 稍稍深入
    在学习它们之前,先来了解一下我们习以为常的main函数。
    int main(int argc, char *argv[], char *envp[])
    这才是main函数真正完整的形式。
    参数argc指出了运行该程序时命令行参数的个数,
    数组argv存放了所有的命令行参数,
    数组envp存放了所有的环境变量。环境变量指的是一组值,从用户登录后就一直存在,很多应用程序需要依靠它来确定系统的一些细节,我们最常见的环境变量是PATH,它指出了应到哪里去搜索应用程序,如/bin;HOME也是比较常见的环境变量,它指出了我们在系统中的个人目录。环境变量一般以字符串"XXX=xxx"的形式存在,XXX表示变量名,xxx表示变量的值。
    值得一提的是,argv数组和envp数组存放的都是指向字符串的指针,这两个数组都以一个NULL元素表示数组的结尾。
    我们可以通过以下这个程序来观看传到argc、argv和envp里的都是什么东西:
    /* main.c */
    int main(int argc, char *argv[], char *envp[]){
     printf("\n### ARGC ###\n%d\n", argc);
     printf("\n### ARGV ###\n");
     while(*argv)
      printf("%s\n", *(argv++));
     printf("\n### ENVP ###\n");
     while(*envp)
      printf("%s\n", *(envp++));
     return 0;
    }
     编译它:$ cc main.c -o main
    运行时,我们故意加几个没有任何作用的命令行参数:
    $ ./main -xx 000
    ### ARGC ###
    3
    ### ARGV ###
    ./main
    -xx
    000
    ### ENVP ###
    PWD=/home/lei
    REMOTEHOST=dt.laser.com
    HOSTNAME=localhost.localdomain
    QTDIR=/usr/lib/qt-2.3.1
    LESSOPEN=|/usr/bin/lesspipe.sh %s
    KDEDIR=/usr
    USER=lei
    LS_COLORS=
    MACHTYPE=i386-redhat-linux-gnu
    MAIL=/var/spool/mail/lei
    INPUTRC=/etc/inputrc
    LANG=en_US
    LOGNAME=lei
    SHLVL=1
    SHELL=/bin/bash
    HOSTTYPE=i386
    OSTYPE=linux-gnu
    HISTSIZE=1000
    TERM=ansi
    HOME=/home/lei
    PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/lei/bin
    _=./main
    我们看到,程序将"./main"作为第1个命令行参数,所以我们一共有3个命令行参数。这可能与大家平时习惯的说法有些不同,小心不要搞错了。
    现在回过头来看一下exec函数族,先把注意力集中在execve上:
    int execve(const char *path, char *const argv[], char *const envp[]);
    对比一下main函数的完整形式,看出问题了吗?是的,这两个函数里的argv和envp是完全一一对应的关系。
    execve第1个参数path是被执行应用程序的完整路径,
    第2个参数argv就是传给被执行应用程序的命令行参数
    第3个参数envp是传给被执行应用程序的环境变量。
    留心看一下这6个函数还可以发现,前3个函数都是以execl开头的,后3个都是以execv开头的,它们的区别在于,
    execv开头的函数是以"char *argv[]"这样的形式传递命令行参数,
    execl开头的函数采用了我们更容易习惯的方式,把参数一个一个列出来,然后以一个NULL表示结束。这里的NULL的作用和argv数组里的NULL作用是一样的。
    在全部6个函数中,只有execle和execve使用了char *envp[]传递环境变量,其它的4个函数都没有这个参数,
    这并不意味着它们不传递环境变量,这4个函数将把默认的环境变量不做任何修改地传给被执行的应用程序。
    而execle和execve会用指定的环境变量去替代默认的那些。
    除execlp和execvp之外的4个函数都要求,它们的第1个参数path必须是一个完整的路径,如"/bin/ls";
    而execlp和execvp的第1个参数file可以简单到仅仅是一个文件名,如"ls",这两个函数可以自动到环境变量PATH制定的目录里去寻找。
    1.10.3 实战
    /* exec.c */
    #include <unistd.h>
    main(){
     char *envp[]={"PATH=/tmp",   "USER=lei",   "STATUS=testing",   NULL};
     char *argv_execv[]={"echo", "excuted by execv", NULL};
     char *argv_execvp[]={"echo", "executed by execvp", NULL};
     char *argv_execve[]={"env", NULL};
     if(fork()==0)
      if(execl("/bin/echo", "echo", "executed by execl", NULL)<0)
       perror("Err on execl");
     if(fork()==0)
      if(execlp("echo", "echo", "executed by execlp", NULL)<0)
       perror("Err on execlp");
     if(fork()==0)
      if(execle("/usr/bin/env", "env", NULL, envp)<0)
       perror("Err on execle");
     if(fork()==0)
      if(execv("/bin/echo", argv_execv)<0)
       perror("Err on execv");
     if(fork()==0)
      if(execvp("echo", argv_execvp)<0)
       perror("Err on execvp");
     if(fork()==0)
      if(execve("/usr/bin/env", argv_execve, envp)<0)
       perror("Err on execve");
    }
     
    程序里调用了2个Linux常用的系统命令,echo和env。echo会把后面跟的命令行参数原封不动的打印出来,env用来列出所有环境变量。
    由于各个子进程执行的顺序无法控制,所以有可能出现一个比较混乱的输出--各子进程打印的结果交杂在一起,而不是严格按照程序中列出的次序。
    编译并运行:
    $ cc exec.c -o exec
    $ ./exec
    executed by execl
    PATH=/tmp
    USER=lei
    STATUS=testing
    executed by execlp
    excuted by execv
    executed by execvp
    PATH=/tmp
    USER=lei
    STATUS=testing
     
    果然不出所料,execle输出的结果跑到了execlp前面。
    大家在平时的编程中,如果用到了exec函数族,一定记得要加错误判断语句。因为与其他系统调用比起来,exec很容易受伤,被执行文件的位置,权限等很多因素都能导致该调用的失败。最常见的错误是:
    找不到文件或路径,此时errno被设置为ENOENT;
    数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
    没有对要执行文件的运行权限,此时errno被设置为EACCES。
     
    exec用被执行的程序完全替换调用它的程序的影像。fork创建一个新的进程就产生了一个新的PID,
    exec启动一个新程序,替换原有的进程,因此这个新的被exec执行的进程的PID不会改变,
    和调用exec函数的进程一样。
    int   exec…装入和运行其它程序:
    int   execl(  char *pathname,char *arg0,char *arg1,...,char *argn,NULL)
    int   execle( char *pathname,char *arg0,char *arg1,...,char *argn,NULL,char *envp[])
    int   execlp( char *pathname,char *arg0,char *arg1,...,NULL)
    int   execlpe(char *pathname,char *arg0,char *arg1,...,NULL,char *envp[])
    int   execv(  char *pathname,char *argv[])
    int   execve( char *pathname,char *argv[],char *envp[])
    int   execvp( char *pathname,char *argv[])
    int   execvpe(char *pathname,char *argv[],char *envp[])
             exec函数族装入并运行程序pathname,并将参数
              arg0(arg1,arg2,argv[],envp[])传递给子程序,出错返回-1
           在exec函数族中,后缀l、v、p、e添加到exec后,
           所指定的函数将具有某种操作能力
          有后缀  p时,函数可以利用DOS的PATH变量查找子程序文件。
    假如你希望执行命令 /bin/cat /etc/passwd /etc/group,
        l时,希望接收以逗号分隔的参数列表,列表以NULL指针作为结束标志
            execl( "/bin/cat","/etc/passed","/etc/group",NULL);
        v时,希望接收到一个以NULL结尾的字符串数组的指针
            char* argv[] = {"/bin/cat","/etc/passed","/etc/group",NULL}
            execv( "/bin/cat", argv );
        e时,函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境。
            envp也是一个以NULL结尾的字符串数组指针
            
        
    execl(执行文件)
    相关函数
        fork,execle,execlp,execv,execve,execvp
    表头文件
        #include<unistd.h>
    定义函数
        int execl(const char * path,const char * arg,....);
    函数说明
        execl()用来执行参数path字符串所代表的文件路径,接下来的参数代表执行该文件时传递过去的argv(0)、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
    返回值
        如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
    范例
        #include<unistd.h>
    main()
    {
    execl(“/bin/ls”,”ls”,”-al”,”/etc/passwd”,(char * )0);
    }
    执行
        /*执行/bin/ls -al /etc/passwd */
    -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd
     
        
    execlp(从PATH 环境变量中查找文件并执行)
    相关函数
        fork,execl,execle,execv,execve,execvp
    表头文件
        #include<unistd.h>
    定义函数
        int execlp(const char * file,const char * arg,……);
    函数说明
        execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。
    返回值
        如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
    错误代码
        参考execve()。
    范例
        /* 执行ls -al /etc/passwd execlp()会依PATH 变量中的/bin找到/bin/ls */
    #include<unistd.h>
    main()
    {
    execlp(“ls”,”ls”,”-al”,”/etc/passwd”,(char *)0);
    }
    执行
        -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd
     
        
    execv(执行文件)
    相关函数
        fork,execl,execle,execlp,execve,execvp
    表头文件
        #include<unistd.h>
    定义函数
        int execv (const char * path, char * const argv[ ]);
    函数说明
        execv()用来执行参数path字符串所代表的文件路径,与execl()不同的地方在于execve()只需两个参数,第二个参数利用数组指针来传递给执行文件。
    返回值
        如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
    错误代码
        请参考execve()。
    范例
        /* 执行/bin/ls -al /etc/passwd */
    #include<unistd.h>
    main()
    {
    char * argv[ ]={“ls”,”-al”,”/etc/passwd”,(char*) }};
    execv(“/bin/ls”,argv);
    }
    执行
        -rw-r--r-- 1 root root 705 Sep 3 13 :52 /etc/passwd
     
        
    execve(执行文件)
    相关函数
        fork,execl,execle,execlp,execv,execvp
    表头文件
        #include<unistd.h>
    定义函数
        int execve(const char * filename,char * const argv[ ],char * const envp[ ]);
    函数说明
        execve()用来执行参数filename字符串所代表的文件路径,第二个参数系利用数组指针来传递给执行文件,
        argv要传递给程序的完整参数列表,包括argv[0],它一般是执行程序的名字;最后一个参数则为传递给执行文件的新环境变量数组。
    返回值
        如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno 中。
    错误代码
        EACCES
    1. 欲执行的文件不具有用户可执行的权限。
    2. 欲执行的文件所属的文件系统是以noexec 方式挂上。
    3.欲执行的文件或script翻译器非一般文件。
    EPERM
    1.进程处于被追踪模式,执行者并不具有root权限,欲执行的文件具有SUID 或SGID 位。
    2.欲执行的文件所属的文件系统是以nosuid方式挂上,欲执行的文件具有SUID 或SGID 位元,但执行者并不具有root权限
    /****************************************************************************
    **
    **    execve.c--Illustrate the usage of execve
    **
    *****************************************************************************/
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    int main( void )
    {
        char* args[] = { "/bin/ls", NULL };
        if ( -1 == (execve("/bin/ls", args, NULL)) )
        {
            perror( "execve" );
            exit( EXIT_FAILURE);
        }
        
        puts( "shouldn't get here" );
        exit( EXIT_SUCCESS );
    }
    [root@localhost src]# gcc execve.c 
    [root@localhost src]# ./a.out 
    a.out  child_fork.c  execve.c  fork.c  getpid.c


    linux中fork创建的新子进程和父进程之间的区别

    [fork出的子进程和父进程]

    from:http://blog.csdn.net/pipisorry/article/details/25134579

    ref:Linux下Fork与Exec使用
    Linux进程学习总结
    进程控制函数之exec()函数的学习


    展开全文
  • 每个人知道 PID,究竟什么是 PID?为什么你想要 PID?你打算用 PID 做什么?你脑子里同样的问题吗?如果是这样,你就找对地方了解这些细节了。• 来源:linux.cn• 作者:Magesh Maruthamuthu• 译者:geekpi •...

    5619c42b58fd632d9031887cb5b3ccb5.png

    每个人都知道 PID,究竟什么是 PID?为什么你想要 PID?你打算用 PID 做什么?你脑子里有同样的问题吗?如果是这样,你就找对地方了解这些细节了。

    • 来源:linux.cn

    • 作者:Magesh Maruthamuthu• 译者:geekpi •

    (本文字数:2707,阅读时长大约:3 分钟)

    每个人都知道 PID,究竟什么是 PID?为什么你想要 PID?你打算用 PID 做什么?你脑子里有同样的问题吗?如果是这样,你就找对地方了解这些细节了。

    我们查询 PID 主要是用来杀死一个没有响应的程序,它类似于 Windows 任务管理器一样。 Linux GUI 也提供相同的功能,但 CLI 是执行 kill 操作的有效方法。

    什么是进程 ID?

    PID 代表 进程标识号(process identification),它在大多数操作系统内核(如 Linux、Unix、macOS 和 Windows)中使用。它是在操作系统中创建时自动分配给每个进程的唯一标识号。一个进程是一个正在运行的程序实例。

    建议阅读: 如何查看 Apache Web 服务器在 Linux 中的运行时间

    除了 init 进程外其他所有的进程 ID 每次都会改变,因为 init 始终是系统上的第一个进程,并且是所有其他进程的父进程。它的 PID 是 1。

    PID 默认的最大值是 32768。可以在你的系统上运行 cat /proc/sys/kernel/pid_max 来验证。在 32 位系统上,32768 是最大值,但是我们可以在 64 位系统上将其设置为最大 222(约 4 百万)内的任何值。

    你可能会问,为什么我们需要这么多的 PID?因为我们不能立即重用 PID,这就是为什么。另外为了防止可能的错误。

    系统正在运行的进程的 PID 可以通过使用 pidof、pgrep、ps 和 pstree 命令找到。

    方法 1:使用 pidof 命令

    pidof 用于查找正在运行的程序的进程 ID。它在标准输出上打印这些 id。为了演示,我们将在 Debian 9(stretch)系统中找出 Apache2 的进程 ID。

    # pidof apache2

    3754 2594 2365 2364 2363 2362 2361

    从上面的输出中,你可能会遇到难以识别进程 ID 的问题,因为它通过进程名称显示了所有的 PID(包括父进程和子进程)。因此,我们需要找出父 PID(PPID),这是我们要查找的。它可能是第一个数字。在本例中,它是 3754,并按降序排列。

    方法 2:使用 pgrep 命令

    pgrep 遍历当前正在运行的进程,并将符合选择条件的进程 ID 列到标准输出中。

    # pgrep apache2

    2361

    2362

    2363

    2364

    2365

    2594

    3754

    这也与上面的输出类似,但是它将结果从小到大排序,这清楚地说明父 PID 是最后一个。在本例中,它是 3754。

    注意: 如果你有多个进程的进程 ID,那么在使用 pidof 和 pgrep 识别父进程 ID 时就可能不会很顺利。

    方法 3:使用 pstree 命令

    pstree 将运行的进程显示为一棵树。树的根是某个 pid,如果省略了 pid 参数,那么就是 init。如果在 pstree 命令中指定了用户名,则显示相应用户拥有的所有进程。

    pstree 会将相同的分支放在方括号中,并添加重复计数的前缀来可视化地合并到一起。

    # pstree -p | grep "apache2"

    |- apache2(3754) -|-apache2(2361)

    | |-apache2(2362)

    | |-apache2(2363)

    | |-apache2(2364)

    | |-apache2(2365)

    | `-apache2(2594)

    要单独获取父进程,请使用以下格式。

    # pstree -p | grep "apache2" | head -1

    |- apache2(3754) -|-apache2(2361)

    pstree 命令非常简单,因为它分别隔离了父进程和子进程,但这在使用 pidof 和 pgrep 时命令不容易做到。

    方法 4:使用 ps 命令

    ps 显示活动进程的选择信息。它显示进程 ID(pid=PID)、与进程关联的终端(tname=TTY)、以 [DD-]hh:mm:ss 格式(time=TIME)显示的累计 CPU 时间、以及执行名(ucmd = CMD)。输出默认是未排序的。

    # ps aux | grep "apache2"

    www-data 2361 0.0 0.4 302652 9732 ? S 06:25 0:00 /usr/sbin/apache2 -k start

    www-data 2362 0.0 0.4 302652 9732 ? S 06:25 0:00 /usr/sbin/apache2 -k start

    www-data 2363 0.0 0.4 302652 9732 ? S 06:25 0:00 /usr/sbin/apache2 -k start

    www-data 2364 0.0 0.4 302652 9732 ? S 06:25 0:00 /usr/sbin/apache2 -k start

    www-data 2365 0.0 0.4 302652 8400 ? S 06:25 0:00 /usr/sbin/apache2 -k start

    www-data 2594 0.0 0.4 302652 8400 ? S 06:55 0:00 /usr/sbin/apache2 -k start

    root 3754 0.0 1.4 302580 29324 ? Ss Dec11 0:23 /usr/sbin/apache2 -k start

    root 5648 0.0 0.0 12784 940 pts/0 S+ 21:32 0:00 grep apache2

    从上面的输出中,我们可以根据进程的启动日期轻松地识别父进程 ID(PPID)。在此例中,apache2 启动于 Dec 11,它是父进程,其他的是子进程。apache2 的 PID 是 3754。

    展开全文
  • 僵尸进程2

    2007-05-31 00:30:00
    也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
    也许有不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?而且,就我们的常识来说,当我们执行一个程序的时候,新产生的进程的内容应就是程序的内容才对。是我们理解错了吗?显然不是,要解决这些疑惑,就必须提到我们下面要介绍的exec系统调用。
      
      简介
      
      说是exec系统调用,实际上在Linux中,并不存在一个exec()的函数形式,exec指的是一组函数,一共有6个,分别是:
      
      #include
      int execl(const char *path, const char *arg, ...);
      int execlp(const char *file, const char *arg, ...);
      int execle(const char *path, const char *arg, ..., char *const envp[]);
      int execv(const char *path, char *const argv[]);
      int execvp(const char *file, char *const argv[]);
      int execve(const char *path, char *const argv[], char *const envp[]);
      
      
      
      其中只有execve是真正意义上的系统调用,其它都是在此基础上经过包装的库函数。
      
      exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。这里的可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
      
      与一般情况不同,exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息仍保持原样,颇有些神似"三十六计"中的"金蝉脱壳"。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回一个- 1,从原程序的调用点接着往下执行。
      
      现在我们应该明白了,Linux下是如何执行新程序的,每当有进程认为自己不能为系统和拥护做出任何贡献了,他就可以发挥最后一点余热,调用任何一个exec,让自己以新的面貌重生;或者,更普遍的情况是,如果一个进程想执行另一个程序,它就可以fork出一个新进程,然后调用任何一个exec,这样看起来就好像通过执行应用程序而产生了一个新进程一样。
      
      事实上第二种情况被应用得如此普遍,以至于Linux专门为其作了优化,我们已经知道,fork会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去,这些拷贝的动作很消耗时间,而如果fork完之后我们马上就调用exec,这些辛辛苦苦拷贝来的东西又会被立刻抹掉,这看起来非常不划算,于是人们设计了一种"写时拷贝(copy-on-write)"技术,使得fork结束后并不立刻复制父进程的内容,而是到了真正实用的时候才复制,这样如果下一条语句是 exec,它就不会白白作无用功了,也就提高了效率。
      
      稍稍深入
      
      上面6条函数看起来似乎很复杂,但实际上无论是作用还是用法都非常相似,只有很微小的差别。在学习它们之前,先来了解一下我们习以为常的main函数。
      
      下面这个main函数的形式可能有些出乎我们的意料:
      
      int main(int argc, char *argv[], char *envp[])
      
      
      
      它可能与绝大多数教科书上描述的都不一样,但实际上,这才是main函数真正完整的形式。
      
      参数argc指出了运行该程序时命令行参数的个数,数组argv存放了所有的命令行参数,数组envp存放了所有的环境变量。环境变量指的是一组值,从用户登录后就一直存在,很多应用程序需要依靠它来确定系统的一些细节,我们最常见的环境变量是PATH,它指出了应到哪里去搜索应用程序,如/bin; HOME也是比较常见的环境变量,它指出了我们在系统中的个人目录。环境变量一般以字符串"XXX=xxx"的形式存在,XXX表示变量名,xxx表示变量的值。
      
      值得一提的是,argv数组和envp数组存放的都是指向字符串的指针,这两个数组都以一个NULL元素表示数组的结尾。
      
      我们可以通过以下这个程序来观看传到argc、argv和envp里的都是什么东西:
      
      /* main.c */
      int main(int argc, char *argv[], char *envp[])
      {
       printf("n### ARGC ###n%dn", argc);
       printf("n### ARGV ###n");
       while(*argv)
       printf("%sn", *(argv++));
       printf("n### ENVP ###n");
       while(*envp)
       printf("%sn", *(envp++));
       return 0;
      }
      
      编译它:
      
      $ cc main.c -o main

      
      运行时,我们故意加几个没有任何作用的命令行参数:
      
      $ ./main -xx 000
      ### ARGC ###
      3
      ### ARGV ###
      ./main
      -xx
      000
      ### ENVP ###
      PWD=/home/lei
      REMOTEHOST=dt.laser.com
      HOSTNAME=localhost.localdomain
      QTDIR=/usr/lib/qt-2.3.1
      LESSOPEN=|/usr/bin/lesspipe.sh %s
      KDEDIR=/usr
      USER=lei
      LS_COLORS=
      MACHTYPE=i386-redhat-linux-gnu
      MAIL=/var/spool/mail/lei
      INPUTRC=/etc/inputrc
      LANG=en_US
      LOGNAME=lei
      SHLVL=1
      SHELL=/bin/bash
      HOSTTYPE=i386
      OSTYPE=linux-gnu
      HISTSIZE=1000
      TERM=ansi
      HOME=/home/lei
      PATH=/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:/home/lei/bin
      _=./main
      
      
      
      我们看到,程序将“./main”作为第1个命令行参数,所以我们一共有3个命令行参数。这可能与大家平时习惯的说法有些不同,小心不要搞错了。
      
      现在回过头来看一下exec函数族,先把注意力集中在execve上:
      
      int execve(const char *path, char *const argv[], char *const envp[]);
      
      
      
      对比一下main函数的完整形式,看出问题了吗?是的,这两个函数里的argv和envp是完全一一对应的关系。execve第1个参数path是被执行应用程序的完整路径,第2个参数argv就是传给被执行应用程序的命令行参数,第3个参数envp是传给被执行应用程序的环境变量。
      
      留心看一下这6个函数还可以发现,前3个函数都是以execl开头的,后3个都是以execv开头的,它们的区别在于,execv开头的函数是以 “char *argv[]”这样的形式传递命令行参数,而execl开头的函数采用了我们更容易习惯的方式,把参数一个一个列出来,然后以一个NULL表示结束。这里的NULL的作用和argv数组里的NULL作用是一样的。
      
      在全部6个函数中,只有execle和execve使用了 char *envp[]传递环境变量,其它的4个函数都没有这个参数,这并不意味着它们不传递环境变量,这4个函数将把默认的环境变量不做任何修改地传给被执行的应用程序。而execle和execve会用指定的环境变量去替代默认的那些。
      
      还有2个以p结尾的函数execlp和 execvp,咋看起来,它们和execl与execv的差别很小,事实也确是如此,除execlp和execvp之外的4个函数都要求,它们的第1个参数path必须是一个完整的路径,如"/bin/ls";而execlp和execvp的第1个参数file可以简单到仅仅是一个文件名,如"ls",这两个函数可以自动到环境变量PATH制定的目录里去寻找。
      
      实战
      
      知识介绍得差不多了,接下来我们看看实际的应用:
      
      /* exec.c */
      #include
      main()
      {
       char *envp[]={"PATH=/tmp",
       "USER=lei",
       "STATUS=testing",
       NULL};
       char *argv_execv[]={"echo", "excuted by execv", NULL};
       char *argv_execvp[]={"echo", "executed by execvp", NULL};
       char *argv_execve[]={"env", NULL};
       if(fork()==0)
       if(execl("/bin/echo", "echo", "executed by execl", NULL)<0)
       perror("Err on execl");
       if(fork()==0)
       if(execlp("echo", "echo", "executed by execlp", NULL)<0)
       perror("Err on execlp");
       if(fork()==0)
       if(execle("/usr/bin/env", "env", NULL, envp)<0)
       perror("Err on execle");
       if(fork()==0)
       if(execv("/bin/echo", argv_execv)<0)
       perror("Err on execv");
       if(fork()==0)
       if(execvp("echo", argv_execvp)<0)
       perror("Err on execvp");
       if(fork()==0)
       if(execve("/usr/bin/env", argv_execve, envp)<0)
       perror("Err on execve");
      }
      
      
      
      程序里调用了2个Linux常用的系统命令,echo和env。echo会把后面跟的命令行参数原封不动的打印出来,env用来列出所有环境变量。
      
      由于各个子进程执行的顺序无法控制,所以有可能出现一个比较混乱的输出--各子进程打印的结果交杂在一起,而不是严格按照程序中列出的次序。
      
      编译并运行:
      
      $ cc exec.c -o exec
      $ ./exec
      executed by execl
      PATH=/tmp
      USER=lei
      STATUS=testing
      executed by execlp
      excuted by execv
      executed by execvp
      PATH=/tmp
      USER=lei
      STATUS=testing
      
      
      果然不出所料,execle输出的结果跑到了execlp前面。
      
      大家在平时的编程中,如果用到了exec函数族,一定记得要加错误判断语句。因为与其他系统调用比起来,exec很容易受伤,被执行文件的位置,权限等很多因素都能导致该调用的失败。最常见的错误是:
      
      找不到文件或路径,此时errno被设置为ENOENT;
      
      数组argv和envp忘记用NULL结束,此时errno被设置为EFAULT;
      
      没有对要执行文件的运行权限,此时errno被设置为EACCES。
      
      
      进程的一生 
      
      下面就让我用一些形象的比喻,来对进程短暂的一生作一个小小的总结:
      
      随着一句fork,一个新进程呱呱落地,但它这时只是老进程的一个克隆。
      
      然后随着exec,新进程脱胎换骨,离家独立,开始了为人民服务的职业生涯。
      
      人有生老病死,进程也一样,它可以是自然死亡,即运行到main函数的最后一个"}",从容地离我们而去;也可以是自杀,自杀有2种方式,一种是调用 exit函数,一种是在main函数内使用return,无论哪一种方式,它都可以留下遗书,放在返回值里保留下来;它还甚至能可被谋杀,被其它进程通过另外一些方式结束他的生命。
      
      进程死掉以后,会留下一具僵尸,wait和waitpid充当了殓尸工,把僵尸推去火化,使其最终归于无形。
      
      这就是进程完整的一生。 
      
      小结 
      
      本文重点介绍了系统调用wait、waitpid和exec函数族,对与进程管理相关的系统调用的介绍就在这里告一段落,在下一篇文章,也是与进程管理相关的系统调用的最后一篇文章中,我们会通过两个很酷的实际例子,来重温一下最近学过的知识。  
    展开全文
  • 调用用fork()函数创建子进程,子进程以父进程为模板,把父进程的PCB、地址空间、代码等拷贝了一份,那子进程再去执行和父进程一模一样的程序又什么意义呢?所以进行程序替换,将硬盘上的代码和数据替换到进程...

    在介绍进程的程序替换时,我们首先得明白为什么要进行程序替换??程序替换是创建一个新的进程吗??替换了哪些东西??搞清楚这三点是至关重要的!!!

    一、为什么要进行程序替换以及替换了哪些东西

    调用用fork()函数创建子进程,子进程以父进程为模板,把父进程的PCB、地址空间、代码等都拷贝了一份,那子进程再去执行和父进程一模一样的程序又有什么意义呢?所以进行程序替换,将硬盘上的代码和数据替换到进程物理内存中的相应位置,让子进程去执行另外一个程序,以此来提高效率!

    二、程序替换是不是创建一个新的进程

    并不是的,程序替换只是子进程调用exec函数去执行另外一个程序,并未创建新的进程。

    三、替换函数(exec家族)

    int execl(const char* path, const char* arg,…);
    int execlp(const char* file, const char* arg, …);
    int execle(const char* path, const char* arg, …, char* const envp[]);
    int execv(const char* path, char* const argv[]);
    int execvp(const char* file, char* const argv[]);

    系统调用:int execve(const char* path, char* const argv[], char* const envp[]);
    函数区分:

    • l(list):表示参数采用列表
    • v(vector):表示参数采用数组
    • p(path):有p自动搜索环境变量path
    • e(env):自己维护环境变量

    四、函数理解

    • 这些函数如果调用成功直接加载新的程序,从启动代码开始执行,不再返回
    • 如果调用出错则返回-1
    • exec家族的函数,只有出错的返回值,没有成功的返回值

    五、函数测试

    • execl
      文件名:test.c
    int main()
    {
    		int ret = execl("./hello", "./hello", NULL);
    		printf("%d \n", ret);
    		return 0;
    }
    

    输出结果:
    在这里插入图片描述
    可以看出,test可执行程序执行成功之后,并没有输出返回值ret,可见,exec类函数,调用之后,直接执行,成功后,不再返回。
    当把代码改成如下:

    int main()
    {
    		int ret = execl("../hello", "./hello", NULL);
    		printf("%d \n", ret);
    		return 0;
    }
    

    输出结果:
    在这里插入图片描述
    可见执行失败,返回-1.

    • execlp
      无需给出路径,只需指明想执行谁(ls),你想怎么执行(“ls”, “-a”)
    int main()
    {
    	execlp("ls", "ls", "-a", NULL);
    	return 0;
    }
    

    输出结果:
    在这里插入图片描述

    • execle
    int main()
    {
    	char* const envp[] = {"PATH=/bin:/user/bin", NULL};
    	execle("/bin/ls", "ls", "-a", envp);
    	return 0;
    }
    

    输出结果:
    在这里插入图片描述

    • execv
    int main()
    {
    	char* const argv[] = {"ls", "-a", NULL};
    	execv("/bin/ls", argv);
    	return 0;
    }
    

    输出结果:
    在这里插入图片描述

    • execvp
    int main()
    {
    	char* const argv[] = {"ls", "-a", NULL};
    	execvp("ls", argv);
    	return 0;
    }
    

    输出结果:
    在这里插入图片描述

    • execve
    int main()
    {
    	char* const envp[] = {"PATH=/bin:/user/bin", NULL};
    	char* const argv[] = {"ls", "-a", NULL};
    	execve("/bin/ls", argv, envp);
    	return 0;
    }
    

    输出结果:
    在这里插入图片描述

    六、总结

    上面的exec类函数,只有execve是真正的系统调用,其他5个函数最终都会调用execve,所以execve在man手册第2节,其他的在第3节。
    下面是这六个函数的关系
    在这里插入图片描述

    展开全文
  • 可以看出:在父进程中的全局变量,如果在子进程中去改变这个全局变量,则子进程中被改变的这个值不会去影响父进程,因为子进程中的所有数据是通过写时拷贝拷自父进程的,这两个进程的地址空间就是不同的了。...
  • 这样一段代码: ...我认为由于父进程执行waitpid函数,且第三个参数为0,因此在子进程终止后才能继续运行,也就是说,父进程的c不可能在a之前出现。 请问我是错的还是书中答案是错的?
  • mm指向同一个地址),且都有自己的用户态堆栈(共享父进程的地址空间,再在里面分配自己的独立栈,默认2M)。这是毫无疑问的,但还有一点我没搞明白,内核栈是共享还是独立的? 回答:独立的。理由:要不然内核栈...
  • 11. 711表示的文件权限是:( C) ... 除了初始进程,每个进程都有父进程 D.一个程序只能产生一个进程 13.关于前台任务和后台任务,错误的是:( B) A. 可以在命令后加上&,让命令运行在后台 B. ctrl+z将会将当.
  • 大家好! 我目前正在写一个多进程程序,但是遇到一... 可写了会导致代码不必要的冗杂(我写的是多进程程序,意味着所有子进程都得释放,父进程也得释放)。 大家平时的习惯呢? 你们觉得没有手动写释放代码的必要?
  • 难道每当想要同时做几件事时得创建进程吗? 不见得,以下几个原因: 1.创建进程要花时间 的机器新建进程只要花一丁点时间。虽然时间很短,但还是需要时间。如果你想要执行的任务才用几十毫秒,每次...
  • fork()创建的子进程拷贝了父进程的代码段,数据段,堆栈;而vfork()创建出来的子进程和父进程共享数据段; fork()创建的子进程执行次数不确定,操作系统对他们进行交替执行,而vfork()创建出来的必须要保证子进...
  • RabbitMQ基本示例,轮询机制,no_ack作用 一、RabbitMQ简介: ... 之前不是学了Queue了吗,是队列还学RabbitMQ干嘛?... 一个线程Queue生产者消费者模型,一个进程Queue用于父进程与子进程...
  • exec函数族

    2017-03-21 20:19:49
    也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
  • exec函数

    2011-09-20 13:18:38
    也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和 进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
  • exec函数簇

    2016-05-21 12:39:00
    也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
  •  进程,线程有内核对象,文件和设备都有内核对象,内核对象都是用于操作系统来管理的,每一个内核对象里面都会有一个使用计数,HANDLE里面有使用计数。 2:文件也是属于设备,电脑中任何IO设备,IO设备包含了文件...
  • 也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
  • exec函数族的详解

    千次阅读 2012-07-18 16:15:49
    也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
  • linux exec 函数族

    2014-09-08 20:53:51
    也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
  • exec函数族 详解

    千次阅读 2010-04-16 11:44:00
    exec函数族也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该...
  • linux C编程中exec调用

    2012-02-17 20:32:08
    也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该一模一样了吗?...
  • exec函数族

    2009-10-31 20:35:00
    exec函数族 也许不少读者从本系列文章一推出就开始读,一直到这里还有一个很大的疑惑:既然所有新进程都是由fork产生的,而且由fork产生的子进程和父进程几乎完全一样,那岂不是意味着系统中所有的进程都应该...
  • 这就是多进程的神奇之处,相比于专业的术语,父进程和子进程,我更愿意把它称为平行进程。我感觉它真是就是构造出了平行时空的概念,我们知道一段代码是按照顺序往下走的,就像时间长河顺着往下流。但是在某一个特殊...
  • 如果在子类中定义某方法与其相同的名称和参数,我们说该方法被重写 (Overriding)。子类的对象使用这个方法时,将调用子类中的定义,对它而言,父类中的定义如同被"屏蔽"了。如果在一个类中定义了多个同名的...

空空如也

空空如也

1 2 3
收藏数 51
精华内容 20
关键字:

进程都有父进程吗