翼度科技»论坛 云主机 LINUX 查看内容

Linux进程

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
程序与进程

程序:是可执行文件,其本质是是一个文件,程序是静态的,同一个程序可以运行多次,产生多个进程
进程:它是程序的一次运行过程,当应用程序被加载到内存中运行之后它就称为了一个进程,进程是动态的,进程的生命周期是从程序运行到程序退出
父子进程:当一个进程A通过frok()函数创建出进程B,A为B的父进程,B为A的子进程
进程号(PID):也称进程标识符,是一个非负整数,用于唯一标识系统下某一个进程。

  • pid=0:交换进程,pid=1:init进程,
  • linux中可以使用ps -aux查看系统中的所有进程,可配合管道进行使用 ps -aux | grep xxx。
  • pid_t getpid(void):该系统调用用于获取当前进程的pid。
  • pid_t getppid(void):用于获取父进程的pid。
进程的创建

一个现有的进程可以调用 fork()函数创建一个新的进程, 调用 fork()函数的进程称为父进程,由 fork()函
数创建出来的进程被称为子进程(child process) , fork()函数原型如下所示(fork()为系统调用)。
应用场景:


  • 在诸多的应用中,创建多个进程是任务分解时行之有效的方法,譬如,某一网络服务器进程可在监听客户端请求的同时,为处理每一个请求事件而创建一个新的子进程,与此同时,服务器进程会继续监听更多的客户端连接请求。
  • 一个进程要执行不同的程序。 譬如在程序 app1 中调用 fork()函数创建了子进程,此时子进程是要
    去执行另一个程序 app2,也就是子进程需要执行的代码是 app2 程序对应的代码,子进程将从 app2
    程序的 main 函数开始运行。这种情况,通常在子进程从 fork()函数返回之后立即调用 exec 族函数
    来实现,关于 exec 函数将在后面内容向大家介绍。
fork():

作用:用于创建一个子进程,原型如下:
  1. #include <unistd.h>
  2. pid_t fork(void);
复制代码
理解 fork()系统调用的关键在于,完成对其调用后将存在两个进程,一个是原进程(父进程)、另一个
则是创建出来的子进程,并且每个进程都会从 fork()函数的返回处继续执行,会导致调用 fork()返回两次值,
子进程返回一个值、父进程返回一个值  。
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main()
  4. {
  5.     pid_t pid;
  6.     pid = getpid();
  7.     fork();     //创建一个子进程,子进程从这开始执行
  8.     printf("pid = %d\r\n",pid);     //父子进程都会执行该语句
  9.     return 0;
  10. }
复制代码

fork()调用成功后,会在父进程中返回子进程的 PID,而在子进程中返回值是 0;如果调用失败,父进
程返回值-1,不创建子进程,并设置 errno。
fork返回值总结:父子进程都会从fork()函数的返回处继续执行。

  • 父进程:返回子进程PID,调用失败返回-1
  • 子进程:返回0
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6.     pid_t pid;
  7.     pid = fork();
  8.     switch(pid)
  9.     {
  10.         case -1:
  11.             perror("fork error");
  12.             exit(-1);
  13.         case 0:
  14.             printf("这是子进程打印的信息:父进程pid = %d,子进程pid = %d\r\n",getppid(),getpid());
  15.             _exit(0);   //子进程使用_exit退出
  16.         default:
  17.             printf("这是父进程打印的信息:父进程pid = %d,子进程pid = %d\r\n",getpid(),pid);
  18.             exit(0);
  19.     }
  20. }
复制代码

vfork()

也是用于创建子进程,返回值与fork()一样。原型如下:
  1. #include <sys/types.h>
  2. #include <unistd.h>
  3. pid_t vfork(void)
复制代码
vfork与fork区别:

  • vfork直接使用父进程存储空间,不拷贝。

    • vfork()与 fork()一样都创建了子进程,但 vfork()函数并不会将父进程的地址空间完全复制到子进程中,因为子进程会立即调用 exec(_exit) ,于是也就不会引用该地址空间的数据。不过在子进程调用 exec 或_exit 之前,它在父进程的空间中运行、 子进程共享父进程的内存。这种优化工作方式的实现提高的效率; 但如果子进程修改了父进程的数据(除了 vfork 返回值的变量)、进行了函数调用、或者没有调用 exec 或_exit 就返回将可能带来未知的结果

  • vfork保证子进程先运行,当子进程调用exit退出后,父进程才执行。

    • vfork()保证子进程先运行, 子进程调用 exec 之后父进程才可能被调度运行。
      虽然 vfork()系统调用在效率上要优于 fork(),但是 vfork()可能会导致一些难以察觉的程序 bug,所以尽量避免使用 vfork()来创建子进程,虽然 fork()在效率上并没有 vfork()高,但是现代的 Linux 系统内核已经采用了写时复制技术来实现 fork(),其效率较之于早期的 fork()实现要高出许多,除非速度绝对重要的场合,

父子数据共享

使用fork()创建子进程后,子进程会把父进程中的数据拷贝一份;该实验中,子进程对a进行了+10操作,但是不影响父进程中的a。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6.     pid_t pid;
  7.     int a = 10;
  8.     pid = fork();                                                                                //fork创建子进程
  9.     switch(pid)
  10.     {
  11.         case -1:
  12.             perror("fork error");
  13.             exit(-1);
  14.         case 0:
  15.             a+=10;                                                                                //子进程中改变数据
  16.             printf("我是子进程: a = %d\r\n",a);
  17.             exit(0);
  18.         default:
  19.             printf("我是父进程: a = %d\r\n",a);
  20.             exit(0);
  21.     }
  22. }
复制代码

使用vfork()创建子进程,子进程不会拷贝父进程中的数据,而是直接使用父进程中的数据。子进程中更改数据也影响父进程中的数据。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6.     pid_t pid;
  7.     int a = 10;
  8.     pid = vfork();                        //vfork创建子进程
  9.     switch(pid)
  10.     {
  11.         case -1:
  12.             perror("fork error");
  13.             exit(-1);
  14.         case 0:
  15.             a+=10;                        //子进程中改变数据
  16.             printf("我是子进程: a = %d\r\n",a);
  17.             exit(0);
  18.         default:
  19.             printf("我是父进程: a = %d\r\n",a);
  20.             exit(0);
  21.     }
  22. }
复制代码

父子竞争

调用 fork()之后,子进程成为了一个独立的进程,可被系统调度运行,而父进程也继续被系统调度运行,
那么谁先访问cpu呢?答案是不确定的,父子进程运行顺序不确定。
从测试结果可知,虽然绝大部分情况下,父进程会先于子进程被执行,但是并不排除子进程先于父进程
被执行的可能性。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6.     pid_t pid;
  7.     pid = fork();
  8.     switch(pid)
  9.     {
  10.         case -1:
  11.             perror("fork error");
  12.             exit(-1);
  13.         case 0:
  14.             printf("我是子进程\r\n");
  15.             exit(0);
  16.         default:
  17.             printf("我是父进程\r\n");
  18.             exit(0);
  19.     }
  20. }
复制代码

进程退出

进程退出包括正常退出和异常退出:
正常退出

  • main()函数中通过 return 语句返回来终止进程;
  • 应用程序中调用 exit()函数终止进程;
  • 应用程序中调用exit()或_Exit()终止进程;
  • 补充:进程中最后一个线程返回,进程也会退出。最后一个线程调用pthrea_exit();
异常退出

  • 应用程序中调用 abort()函数终止进程;
  • 进程接收到一个信号,譬如 SIGKILL 信号。
般使用 exit()库函数而非exit()系统调用 ,原因在于 exit()最终也会通过exit()终止进程,但在此之前,它将会完成一些其它的工作, exit()函数会执行的动作如下  :

  • 如果程序中注册了进程终止处理函数,那么会调用终止处理函数。
  • 刷新 stdio 流缓冲区 。
  • 执行_exit()系统调用。
  1. #include <stdlib.h>
  2. void exit(int status);                //传入状态码,用于标识为啥退出,0表示正常退出,-1表示异常退出
复制代码
监视子进程

就是等待子进程退出。对于许多需要创建子进程的进程来说,有时设计需要监视子进程的终止时间以及终止时的一些状态信息,在某些设计需求下这是很有必要的。
wait()

系统调用 wait()可以等待进程的任一子进程终止,同时获取子进程的终止状态信息,wait()函数的作用除了获取子进程的终止状态信息之外,更重要的一点,就是回收子进程的一些资源,俗称为子进程“收尸” ,  其函数原型如下所示:
  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t wait(int *status);
复制代码
参数介绍:

  • status: 参数 status 用于存放子进程终止时的状态信息,参数 status 可以为 NULL,表示不接收子进程
    终止时的状态信息。
  • 返回值: 若成功则返回终止的子进程对应的进程号;失败则返回-1。
系统调用 wait()将执行如下动作:  一次 wait()调用只能处理一次。
<ul>调用 wait()函数,如果其所有子进程都还在运行,则 wait()会一直阻塞等待,直到某一个子进程终
止;
如果进程调用 wait(),但是该进程并没有子进程, 也就意味着该进程并没有需要等待的子进程, 那 么 wait()将返回错误,也就是返回-1、并且会将 errno 设置为 ECHILD。
如果进程调用 wait()之前, 它的子进程当中已经有一个或多个子进程已经终止了,那么调用 wait()
也不会阻塞,而是会立即替该子进程“收尸” 、处理它的“后事”   。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <sys/wait.h>
  6. #include <errno.h>
  7. int main(void)
  8. {
  9.     int status;
  10.     int ret;
  11.     int i;
  12.     /* 循环创建 3 个子进程 */
  13.     for (i = 1; i <= 3; i++)
  14.     {
  15.         switch (fork())
  16.         {
  17.             case -1:
  18.                 perror("fork error");
  19.                 exit(-1);
  20.             case 0:
  21.                 /* 子进程 */
  22.                 printf("子进程<%d>被创建\n", getpid());
  23.                 sleep(i);
  24.                 _exit(i);
  25.             default:
  26.                 /* 父进程 */
  27.                 break;      //跳出switch
  28.         }
  29.     }
  30.     sleep(1);
  31.     printf("~~~~~~~~~~~~~~\n");
  32.     for (i = 1; i <= 3; i++)
  33.     {
  34.         ret = wait(&status);
  35.         if (-1 == ret)
  36.         {
  37.             if (ECHILD == errno)
  38.             {
  39.                 printf("没有需要等待回收的子进程\n");
  40.                 exit(0);
  41.             }
  42.             else
  43.             {
  44.                 perror("wait error");
  45.                 exit(-1);
  46.             }
  47.         }
  48.         printf("回收子进程<%d>, 终止状态<%d>\n", ret,
  49.         WEXITSTATUS(status));
  50.     }
  51.     exit(0);
  52. }
复制代码
  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. pid_t waitpid(pid_t pid, int *status, int options);
复制代码
execl()

函数原型:
  1. #include <unistd.h>
  2. extern char **environ;
  3. int execl(const char *path, const char *arg, ... /* (char *) NULL */);
  4. int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
  5. int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
  6.                                                         /*testAPP.c*/
  7. #include <stdio.h>
  8. #include <stdlib.h>
  9. #include <unistd.h>
  10. int main(int argc, char *argv[])
  11. {
  12.     char *arg_arr[5];
  13.     char *env_arr[5] = {"NAME=app", "AGE=25",
  14.     "SEX=man", NULL};                //设置newAPP程序中的环境变量
  15.     if (2 > argc)
  16.             exit(-1);
  17.     arg_arr[0] = argv[1];
  18.     arg_arr[1] = "Hello";
  19.     arg_arr[2] = "World";
  20.     arg_arr[3] = NULL;                //必须以NULL结束
  21.     execve(argv[1], arg_arr, env_arr);
  22.    
  23.     perror("execve error");                //execve成功退出后,该程序也结束,并不会执行这些代码
  24.     exit(-1);
  25. }
  26. int execvp(const char *file, char *const argv[]);
  27. int execvpe(const char *file, char *const argv[], char *const envp[]);
复制代码
参数介绍:

  • path:指向新可执行程序的路径,可以是相对和绝对路径
  • 参数列表:把参数列表依次排列,使用可变参数形式传递,本质上也是多个字符串,以 NULL 结尾。
    1. #include <unistd.h>
    2. int execve(const char *filename, char *const argv[], char *const envp[]);
    复制代码
execv

函数原型:
  1.                                                         /*testAPP.c*/
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. int main(int argc, char *argv[])
  6. {
  7.     char *arg_arr[5];
  8.     char *env_arr[5] = {"NAME=app", "AGE=25",
  9.     "SEX=man", NULL};                //设置newAPP程序中的环境变量
  10.     if (2 > argc)
  11.             exit(-1);
  12.     arg_arr[0] = argv[1];
  13.     arg_arr[1] = "Hello";
  14.     arg_arr[2] = "World";
  15.     arg_arr[3] = NULL;                //必须以NULL结束
  16.     execve(argv[1], arg_arr, env_arr);
  17.    
  18.     perror("execve error");                //execve成功退出后,该程序也结束,并不会执行这些代码
  19.     exit(-1);
  20. }
复制代码
参数介绍:

  • path:指向新可执行程序的路径,可以是相对和绝对路径。
  • argv:指定了传递给新程序的命令行参数。是一个字符串数组, 该数组对应于 main(int argc, char*argv[])函数的第二个参数 argv,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。argv[0]对应的便是新程序自身路径名。
    1.                                                                 /*newAPP.c*/
    2. #include <stdio.h>
    3. #include <stdlib.h>
    4. extern char **environ;                        //对应testAPP中的env_arr
    5. int main(int argc, char *argv[])
    6. {
    7.     char **ep = NULL;
    8.     int j;
    9.     for (j = 0; j < argc; j++)
    10.     {
    11.         printf("argv[%d]: %s\n", j, argv[j]);        //打印传递过来的参数
    12.     }
    13.     puts("env:");
    14.     for (ep = environ; *ep != NULL; ep++)
    15.     {
    16.         printf(" %s\n", *ep);                //打印环境变量
    17.     }
    18.     exit(0);
    19. }
    复制代码
execlp()execvp()
  1. int execl(const char *path, const char *arg, ... );
复制代码
execlp()和 execvp()在 execl()和 execv()基础上加了一个 p,这个 p 其实表示的是 PATH。execl()和execv()要求提供新程序的路径名,而 execlp()和 execvp()则允许只提供新程序文件名,系统会在由
环境变量 PATH 所指定的目录列表中寻找相应的可执行文件,如果执行的新程序是一个 Linux 命令,这将很有用; 当然, execlp()和 execvp()函数也兼容相对路径和绝对路径的方式。
execle()与execvpe()
  1. execl("./newApp", "./newApp", "Hello", "World", NULL);
复制代码
execle()和 execvpe()这两个函数在命名上加了一个 e,这个 e 其实表示的是 environment 环境变量,
意味着这两个函数可以指定自定义的环境变量列表给新程序, 参数envp与系统调用execve()的envp
参数相同,也是字符串指针数组  。
  1. int execv(const char *path, char *const argv[]);
复制代码
  1. char *arg_arr[5];
  2. arg_arr[0] = "./newApp";
  3. arg_arr[1] = "Hello";
  4. arg_arr[2] = "World";
  5. arg_arr[3] = NULL;
  6. execv("./newApp", arg_arr);
复制代码
使用exec簇函数执行ls命令

execl
  1. int execlp(const char *file, const char *arg, ... );
  2. int execvp(const char *file, char *const argv[]);
复制代码
execv
  1. int execle(const char *path, const char *arg, ... );
  2. int execvpe(const char *file, char *const argv[], char *const envp[]);
复制代码
execlp
  1. //execvpe 传参
  2. char *env_arr[5] = {"NAME=app", "AGE=25","SEX=man", NULL};
  3. char *arg_arr[5];
  4. arg_arr[0] = "./newApp";
  5. arg_arr[1] = "Hello";
  6. arg_arr[2] = "World";
  7. arg_arr[3] = NULL;
  8. execvpe("./newApp", arg_arr, env_arr);
复制代码
execvp
  1. // execle 传参
  2. execle("./newApp", "./newApp", "Hello", "World", NULL, env_arr);
复制代码
execle
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(void)
  5. {
  6.     execl("/bin/ls", "ls", "-a", "-l", NULL);
  7.     perror("execl error");
  8.     exit(-1);
  9. }
复制代码
execvpe
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(void)
  5. {
  6.     char *arg_arr[5];
  7.     arg_arr[0] = "ls";
  8.     arg_arr[1] = "-a";
  9.     arg_arr[2] = "-l";
  10.     arg_arr[3] = NULL;
  11.     execv("/bin/ls", arg_arr);
  12.     perror("execv error");
  13.     exit(-1);
  14. }
复制代码

system函数

使用 system()函数可以很方便地在我们的程序当中执行任意 shell 命令  。system()函数其内部的是通过调用 fork()、execl()以及 waitpid()这三个函数来实现它的功能。
函数原型:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(void)
  5. {
  6.     execlp("ls", "ls", "-a", "-l", NULL);
  7.     perror("execlp error");
  8.     exit(-1);
  9. }
复制代码
返回值:

  • 当参数 command 为 NULL, 如果 shell 可用则返回一个非 0 值,若不可用则返回 0;针对一些非
    UNIX 系统,该系统上可能是没有 shell 的,这样就会导致 shell 不可能;如果 command 参数不为
    NULL,则返回值从以下的各种情况所决定。
  • 如果无法创建子进程或无法获取子进程的终止状态,那么 system()返回-1;
  • 如果子进程不能执行 shell,则 system()的返回值就好像是子进程通过调用_exit(127)终止了;
  • 如果所有的系统调用都成功, system()函数会返回执行 command 的 shell 进程的终止状态。
参数介绍:

  • command: 指向需要执行的 shell 命令,以字符串的形式提供,譬如"ls -al"、 "echo
    HelloWorld"等  。
使用示例:将需要执行的命令通过参数传递给main函数,main函数里调用system来执行该命令。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. int main(void)
  5. {
  6.     char *arg_arr[5];
  7.     arg_arr[0] = "ls";
  8.     arg_arr[1] = "-a";
  9.     arg_arr[2] = "-l";
  10.     arg_arr[3] = NULL;
  11.     execvp("ls", arg_arr);
  12.     perror("execvp error");
  13.     exit(-1);
  14. }
复制代码

popen函数

也是用于执行shell命令的函数,与system相比,popen能够将执行命令后得到的数据通过管道进行读出或写入。
popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。
这个进程必须由 pclose() 函数关闭,而不是 fclose() 函数。pclose() 函数关闭标准 I/O 流,等待命令执行结束,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose() 返回的终止状态与 shell 已执行 exit 一样。也就是,popen创建管道,执行shell命令将文件流中的某些数据读出。
函数原型:
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. extern char **environ;
  5. int main(void)
  6. {
  7.     execle("/bin/ls", "ls", "-a", "-l", NULL, environ);
  8.     perror("execle error");
  9.     exit(-1);
  10. }
复制代码
补充:FILE指针,相当于文件描述符fd,作为文件句柄。
参数介绍:

  • command:是一个指向以 NULL 结束的 shell 命令字符串的指针。命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令,比如sh -c ls。
  • type:“r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
使用示例:通过调用popen去执行ls -l命令,把结果给fp,通过fread读取
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. extern char **environ;
  5. int main(void)
  6. {
  7.     char *arg_arr[5];
  8.     arg_arr[0] = "ls";
  9.     arg_arr[1] = "-a";
  10.     arg_arr[2] = "-l";
  11.     arg_arr[3] = NULL;
  12.     execvpe("ls", arg_arr, environ);
  13.     perror("execvpe error");
  14.     exit(-1);
  15. }
复制代码


来源:https://www.cnblogs.com/tenzzz/p/18170604
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具