Linux进程
程序与进程程序:是可执行文件,其本质是是一个文件,程序是静态的,同一个程序可以运行多次,产生多个进程
进程:它是程序的一次运行过程,当应用程序被加载到内存中运行之后它就称为了一个进程,进程是动态的,进程的生命周期是从程序运行到程序退出
父子进程:当一个进程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;
char *env_arr = {"NAME=app", "AGE=25",
"SEX=man", NULL}; //设置newAPP程序中的环境变量
if (2 > argc)
exit(-1);
arg_arr = argv;
arg_arr = "Hello";
arg_arr = "World";
arg_arr = NULL; //必须以NULL结束
execve(argv, 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;
char *env_arr = {"NAME=app", "AGE=25",
"SEX=man", NULL}; //设置newAPP程序中的环境变量
if (2 > argc)
exit(-1);
arg_arr = argv;
arg_arr = "Hello";
arg_arr = "World";
arg_arr = NULL; //必须以NULL结束
execve(argv, arg_arr, env_arr);
perror("execve error"); //execve成功退出后,该程序也结束,并不会执行这些代码
exit(-1);
}参数介绍:
[*]path:指向新可执行程序的路径,可以是相对和绝对路径。
[*]argv:指定了传递给新程序的命令行参数。是一个字符串数组, 该数组对应于 main(int argc, char*argv[])函数的第二个参数 argv,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。argv对应的便是新程序自身路径名。
/*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); //打印传递过来的参数
}
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;
arg_arr = "./newApp";
arg_arr = "Hello";
arg_arr = "World";
arg_arr = 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 = {"NAME=app", "AGE=25","SEX=man", NULL};
char *arg_arr;
arg_arr = "./newApp";
arg_arr = "Hello";
arg_arr = "World";
arg_arr = 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;
arg_arr = "ls";
arg_arr = "-a";
arg_arr = "-l";
arg_arr = 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;
arg_arr = "ls";
arg_arr = "-a";
arg_arr = "-l";
arg_arr = 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;
arg_arr = "ls";
arg_arr = "-a";
arg_arr = "-l";
arg_arr = NULL;
execvpe("ls", arg_arr, environ);
perror("execvpe error");
exit(-1);
}
来源:https://www.cnblogs.com/tenzzz/p/18170604
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!
页:
[1]