|
程序与进程
程序:是可执行文件,其本质是是一个文件,程序是静态的,同一个程序可以运行多次,产生多个进程
进程:它是程序的一次运行过程,当应用程序被加载到内存中运行之后它就称为了一个进程,进程是动态的,进程的生命周期是从程序运行到程序退出
父子进程:当一个进程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():
作用:用于创建一个子进程,原型如下:- #include <unistd.h>
- pid_t fork(void);
复制代码 理解 fork()系统调用的关键在于,完成对其调用后将存在两个进程,一个是原进程(父进程)、另一个
则是创建出来的子进程,并且每个进程都会从 fork()函数的返回处继续执行,会导致调用 fork()返回两次值,
子进程返回一个值、父进程返回一个值 。- #include <stdio.h>
- #include <unistd.h>
- int main()
- {
- pid_t pid;
- pid = getpid();
- fork(); //创建一个子进程,子进程从这开始执行
- printf("pid = %d\r\n",pid); //父子进程都会执行该语句
- return 0;
- }
复制代码
fork()调用成功后,会在父进程中返回子进程的 PID,而在子进程中返回值是 0;如果调用失败,父进
程返回值-1,不创建子进程,并设置 errno。
fork返回值总结:父子进程都会从fork()函数的返回处继续执行。
- 父进程:返回子进程PID,调用失败返回-1
- 子进程:返回0
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- pid_t pid;
- pid = fork();
- switch(pid)
- {
- case -1:
- perror("fork error");
- exit(-1);
- case 0:
- printf("这是子进程打印的信息:父进程pid = %d,子进程pid = %d\r\n",getppid(),getpid());
- _exit(0); //子进程使用_exit退出
- default:
- printf("这是父进程打印的信息:父进程pid = %d,子进程pid = %d\r\n",getpid(),pid);
- exit(0);
- }
- }
复制代码
vfork()
也是用于创建子进程,返回值与fork()一样。原型如下:- #include <sys/types.h>
- #include <unistd.h>
- 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。- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- pid_t pid;
- int a = 10;
- pid = fork(); //fork创建子进程
- switch(pid)
- {
- case -1:
- perror("fork error");
- exit(-1);
- case 0:
- a+=10; //子进程中改变数据
- printf("我是子进程: a = %d\r\n",a);
- exit(0);
- default:
- printf("我是父进程: a = %d\r\n",a);
- exit(0);
- }
- }
复制代码
使用vfork()创建子进程,子进程不会拷贝父进程中的数据,而是直接使用父进程中的数据。子进程中更改数据也影响父进程中的数据。- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- pid_t pid;
- int a = 10;
- pid = vfork(); //vfork创建子进程
- switch(pid)
- {
- case -1:
- perror("fork error");
- exit(-1);
- case 0:
- a+=10; //子进程中改变数据
- printf("我是子进程: a = %d\r\n",a);
- exit(0);
- default:
- printf("我是父进程: a = %d\r\n",a);
- exit(0);
- }
- }
复制代码
父子竞争
调用 fork()之后,子进程成为了一个独立的进程,可被系统调度运行,而父进程也继续被系统调度运行,
那么谁先访问cpu呢?答案是不确定的,父子进程运行顺序不确定。
从测试结果可知,虽然绝大部分情况下,父进程会先于子进程被执行,但是并不排除子进程先于父进程
被执行的可能性。- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main()
- {
- pid_t pid;
- pid = fork();
- switch(pid)
- {
- case -1:
- perror("fork error");
- exit(-1);
- case 0:
- printf("我是子进程\r\n");
- exit(0);
- default:
- printf("我是父进程\r\n");
- exit(0);
- }
- }
复制代码
进程退出
进程退出包括正常退出和异常退出:
正常退出
- main()函数中通过 return 语句返回来终止进程;
- 应用程序中调用 exit()函数终止进程;
- 应用程序中调用exit()或_Exit()终止进程;
- 补充:进程中最后一个线程返回,进程也会退出。最后一个线程调用pthrea_exit();
异常退出
- 应用程序中调用 abort()函数终止进程;
- 进程接收到一个信号,譬如 SIGKILL 信号。
一般使用 exit()库函数而非exit()系统调用 ,原因在于 exit()最终也会通过exit()终止进程,但在此之前,它将会完成一些其它的工作, exit()函数会执行的动作如下 :
- 如果程序中注册了进程终止处理函数,那么会调用终止处理函数。
- 刷新 stdio 流缓冲区 。
- 执行_exit()系统调用。
- #include <stdlib.h>
- void exit(int status); //传入状态码,用于标识为啥退出,0表示正常退出,-1表示异常退出
复制代码 监视子进程
就是等待子进程退出。对于许多需要创建子进程的进程来说,有时设计需要监视子进程的终止时间以及终止时的一些状态信息,在某些设计需求下这是很有必要的。
wait()
系统调用 wait()可以等待进程的任一子进程终止,同时获取子进程的终止状态信息,wait()函数的作用除了获取子进程的终止状态信息之外,更重要的一点,就是回收子进程的一些资源,俗称为子进程“收尸” , 其函数原型如下所示:- #include <sys/types.h>
- #include <sys/wait.h>
- pid_t wait(int *status);
复制代码 参数介绍:
- status: 参数 status 用于存放子进程终止时的状态信息,参数 status 可以为 NULL,表示不接收子进程
终止时的状态信息。
- 返回值: 若成功则返回终止的子进程对应的进程号;失败则返回-1。
系统调用 wait()将执行如下动作: 一次 wait()调用只能处理一次。
<ul>调用 wait()函数,如果其所有子进程都还在运行,则 wait()会一直阻塞等待,直到某一个子进程终
止;
如果进程调用 wait(),但是该进程并没有子进程, 也就意味着该进程并没有需要等待的子进程, 那 么 wait()将返回错误,也就是返回-1、并且会将 errno 设置为 ECHILD。
如果进程调用 wait()之前, 它的子进程当中已经有一个或多个子进程已经终止了,那么调用 wait()
也不会阻塞,而是会立即替该子进程“收尸” 、处理它的“后事” 。- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- #include <sys/types.h>
- #include <sys/wait.h>
- #include <errno.h>
- int main(void)
- {
- int status;
- int ret;
- int i;
- /* 循环创建 3 个子进程 */
- for (i = 1; i <= 3; i++)
- {
- switch (fork())
- {
- case -1:
- perror("fork error");
- exit(-1);
- case 0:
- /* 子进程 */
- printf("子进程<%d>被创建\n", getpid());
- sleep(i);
- _exit(i);
- default:
- /* 父进程 */
- break; //跳出switch
- }
- }
- sleep(1);
- printf("~~~~~~~~~~~~~~\n");
- for (i = 1; i <= 3; i++)
- {
- ret = wait(&status);
- if (-1 == ret)
- {
- if (ECHILD == errno)
- {
- printf("没有需要等待回收的子进程\n");
- exit(0);
- }
- else
- {
- perror("wait error");
- exit(-1);
- }
- }
- printf("回收子进程<%d>, 终止状态<%d>\n", ret,
- WEXITSTATUS(status));
- }
- exit(0);
- }
复制代码- #include <sys/types.h>
- #include <sys/wait.h>
- pid_t waitpid(pid_t pid, int *status, int options);
复制代码 execl()
函数原型:- #include <unistd.h>
- extern char **environ;
- int execl(const char *path, const char *arg, ... /* (char *) NULL */);
- int execlp(const char *file, const char *arg, ... /* (char *) NULL */);
- int execle(const char *path, const char *arg, ... /*, (char *) NULL, char * const envp[] */);
- /*testAPP.c*/
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(int argc, char *argv[])
- {
- char *arg_arr[5];
- char *env_arr[5] = {"NAME=app", "AGE=25",
- "SEX=man", NULL}; //设置newAPP程序中的环境变量
- if (2 > argc)
- exit(-1);
- arg_arr[0] = argv[1];
- arg_arr[1] = "Hello";
- arg_arr[2] = "World";
- arg_arr[3] = NULL; //必须以NULL结束
- execve(argv[1], arg_arr, env_arr);
-
- perror("execve error"); //execve成功退出后,该程序也结束,并不会执行这些代码
- exit(-1);
- }
- int execvp(const char *file, char *const argv[]);
- int execvpe(const char *file, char *const argv[], char *const envp[]);
复制代码 参数介绍:
- path:指向新可执行程序的路径,可以是相对和绝对路径
- 参数列表:把参数列表依次排列,使用可变参数形式传递,本质上也是多个字符串,以 NULL 结尾。
- #include <unistd.h>
- int execve(const char *filename, char *const argv[], char *const envp[]);
复制代码 execv
函数原型:- /*testAPP.c*/
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(int argc, char *argv[])
- {
- char *arg_arr[5];
- char *env_arr[5] = {"NAME=app", "AGE=25",
- "SEX=man", NULL}; //设置newAPP程序中的环境变量
- if (2 > argc)
- exit(-1);
- arg_arr[0] = argv[1];
- arg_arr[1] = "Hello";
- arg_arr[2] = "World";
- arg_arr[3] = NULL; //必须以NULL结束
- execve(argv[1], arg_arr, env_arr);
-
- perror("execve error"); //execve成功退出后,该程序也结束,并不会执行这些代码
- exit(-1);
- }
复制代码 参数介绍:
- path:指向新可执行程序的路径,可以是相对和绝对路径。
- argv:指定了传递给新程序的命令行参数。是一个字符串数组, 该数组对应于 main(int argc, char*argv[])函数的第二个参数 argv,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。argv[0]对应的便是新程序自身路径名。
- /*newAPP.c*/
- #include <stdio.h>
- #include <stdlib.h>
- extern char **environ; //对应testAPP中的env_arr
- int main(int argc, char *argv[])
- {
- char **ep = NULL;
- int j;
- for (j = 0; j < argc; j++)
- {
- printf("argv[%d]: %s\n", j, argv[j]); //打印传递过来的参数
- }
- puts("env:");
- for (ep = environ; *ep != NULL; ep++)
- {
- printf(" %s\n", *ep); //打印环境变量
- }
- exit(0);
- }
复制代码 execlp()execvp()
- 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()
- execl("./newApp", "./newApp", "Hello", "World", NULL);
复制代码 execle()和 execvpe()这两个函数在命名上加了一个 e,这个 e 其实表示的是 environment 环境变量,
意味着这两个函数可以指定自定义的环境变量列表给新程序, 参数envp与系统调用execve()的envp
参数相同,也是字符串指针数组 。- int execv(const char *path, char *const argv[]);
复制代码- char *arg_arr[5];
- arg_arr[0] = "./newApp";
- arg_arr[1] = "Hello";
- arg_arr[2] = "World";
- arg_arr[3] = NULL;
- execv("./newApp", arg_arr);
复制代码 使用exec簇函数执行ls命令
execl
- int execlp(const char *file, const char *arg, ... );
- int execvp(const char *file, char *const argv[]);
复制代码 execv
- int execle(const char *path, const char *arg, ... );
- int execvpe(const char *file, char *const argv[], char *const envp[]);
复制代码 execlp
- //execvpe 传参
- char *env_arr[5] = {"NAME=app", "AGE=25","SEX=man", NULL};
- char *arg_arr[5];
- arg_arr[0] = "./newApp";
- arg_arr[1] = "Hello";
- arg_arr[2] = "World";
- arg_arr[3] = NULL;
- execvpe("./newApp", arg_arr, env_arr);
复制代码 execvp
- // execle 传参
- execle("./newApp", "./newApp", "Hello", "World", NULL, env_arr);
复制代码 execle
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- execl("/bin/ls", "ls", "-a", "-l", NULL);
- perror("execl error");
- exit(-1);
- }
复制代码 execvpe
- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- char *arg_arr[5];
- arg_arr[0] = "ls";
- arg_arr[1] = "-a";
- arg_arr[2] = "-l";
- arg_arr[3] = NULL;
- execv("/bin/ls", arg_arr);
- perror("execv error");
- exit(-1);
- }
复制代码
system函数
使用 system()函数可以很方便地在我们的程序当中执行任意 shell 命令 。system()函数其内部的是通过调用 fork()、execl()以及 waitpid()这三个函数来实现它的功能。
函数原型:- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- execlp("ls", "ls", "-a", "-l", NULL);
- perror("execlp error");
- exit(-1);
- }
复制代码 返回值:
- 当参数 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来执行该命令。- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- int main(void)
- {
- char *arg_arr[5];
- arg_arr[0] = "ls";
- arg_arr[1] = "-a";
- arg_arr[2] = "-l";
- arg_arr[3] = NULL;
- execvp("ls", arg_arr);
- perror("execvp error");
- exit(-1);
- }
复制代码
popen函数
也是用于执行shell命令的函数,与system相比,popen能够将执行命令后得到的数据通过管道进行读出或写入。
popen() 函数通过创建一个管道,调用 fork 产生一个子进程,执行一个 shell 以运行命令来开启一个进程。
这个进程必须由 pclose() 函数关闭,而不是 fclose() 函数。pclose() 函数关闭标准 I/O 流,等待命令执行结束,然后返回 shell 的终止状态。如果 shell 不能被执行,则 pclose() 返回的终止状态与 shell 已执行 exit 一样。也就是,popen创建管道,执行shell命令将文件流中的某些数据读出。
函数原型:- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- extern char **environ;
- int main(void)
- {
- execle("/bin/ls", "ls", "-a", "-l", NULL, environ);
- perror("execle error");
- exit(-1);
- }
复制代码 补充:FILE指针,相当于文件描述符fd,作为文件句柄。
参数介绍:
- command:是一个指向以 NULL 结束的 shell 命令字符串的指针。命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令,比如sh -c ls。
- type:“r” 则文件指针连接到 command 的标准输出;如果 type 是 “w” 则文件指针连接到 command 的标准输入。
使用示例:通过调用popen去执行ls -l命令,把结果给fp,通过fread读取- #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
- extern char **environ;
- int main(void)
- {
- char *arg_arr[5];
- arg_arr[0] = "ls";
- arg_arr[1] = "-a";
- arg_arr[2] = "-l";
- arg_arr[3] = NULL;
- execvpe("ls", arg_arr, environ);
- perror("execvpe error");
- exit(-1);
- }
复制代码
来源:https://www.cnblogs.com/tenzzz/p/18170604
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
x
|