版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_33232390/article/details/50244279
关于linux 高级编程中进程的控制总结
1,时间和空间是计算机里的两个基本概念,操作系统将这两个概念实现为进程和文件。
首先理解进程的概念:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是动态的,是系统进行资源分配和调度的一个独立单位。
2,进程ID是进程的一个基本属性,下面介绍进程中六个重要的ID,及获取的函数原型
进程ID pid_t getpid();
父进程ID pid_t getppid();
进程的用户ID uid_t getuid();
有效用户ID uid_t geteuid();
进程的组ID gid_t getgid();
有效组ID gid_t getegid();
实现代码如下:
#include <stdio.h>
#include <unistd.h>
void main()
{
pid_t pid,ppid;
uid_t uid,euid;
gid_t gid,egid;
pid = getpid();
ppid = getppid();
uid = getuid();
euid = geteuid();
gid = getgid();
egid = getegid();
printf("pid: %u\n",pid);
printf("ppid: %u\n",ppid);
printf("uid: %u\n",uid);
printf("euid: %u\n",euid);
printf("gid: %u\n",gid);
printf("egid: %u\n",egid);
}
3,说到进程,我们该怎么创建进程呢?这里使用fork()函数创建进程。
#include <unistd.h> fork函数包含库
pid_t fork(void); fork函数原型
利用fork函数创建进程:
pid_t pid;
pid = fork();
fork函数不需要参数,他的返回值有三种情况:
1,父进程返回值大于0 即pid > 0
2,子进程返回值等于0 即pid == 0
3,创建失败 返回值为-1 小于0
既然有父子进程,那么父子进程之间又有啥关系呢?(也有三点)
1,父进程返回值为子进程ID,子进程返回的是0
2,父子进程是两个独立的进程,调度机会均等
3,父子进程,fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但更像是兄弟关系,这两个进程共享代码空间,但数据空间是相互独立的(重点记住)。
接下来,代码实现一个进程的创建,如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork(); //创建进程
if(pid < 0) //创建失败
{
printf("fork create error...\n");
return -1;
}
else if(pid > 0) //父进程
{
printf("parents pid:%d child pid :%d\n",getpid(),pid);
}
else // 子进程
{
printf("child pid:%d\n",getpid());
}
return 0;
}
4,上面所说fork函数创建父子进程,他们是相互独立的,那怎么创建一个共享空间的子进程呢?下面用类似fork函数的vfork()函数实现:
vfork 函数原型如下:
#include <unistd.h>
pid_t vfork(void);
vfork()函数与fork()函数有啥区别呢?
1,vfork函数创建的父子进程其子进程和父进程完全共享地址空间,包括代码段,数据段和堆栈段,子进程对这些共享资源的修改,可以影响到父进程,而fork函数不会。
2,vfork函数产生的子进程一定比父进程先运行,而fork函数产生的父子进程是由系统决定先后,调用机会均等。
5,进程的退出
一般用exit函数进行进程的退出操作,如果进程正常退出,参数为0 即exit(0),如果异常退出则为非0.
6.Linux环境下使用exec函数执行一个新程序
在进程中使用exec函数会将原进程内容取代,即完全执行exec函数程序内容,原代码将不会执行。
exec函数原型有六个,但其作用都相同,只是使用方法及参数不同而已。
#include <unistd.h>
extern char **environ;
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 execve(const char *path, char *const argv[],...,char * const envp[]);
int execvp(const char *file, char *const argv[]);
接下来,通过一些简单方法来区别他们的使用。
1,含有‘l’字母 的,其参数以列表方式提供
2,含有‘v’字母的,参数以二维数组形式提供
3,末尾带有‘e’字母的,表示传给新程序环境变量列表,二维数组形式提供
4,带有‘p’字母的,表示第一个参数不是路径名和程序名,而是只是一个程序名
通过不同的实例对比来进行exec函数的解析使用。
1,带p字母与不带p字母的相比较
例:使用ls -l 命令作为例子
execl("/bin/ls","ls","-l",NULL);
其中第一个参数包括路径名称及程序名称。
execlp("ls","ls","-l",NULL);
其中第一个参数只是程序名,没有包含路径
2,带l字母的与带v字母的比较。
execl("/bin/ls","ls","-l",NULL);
其中参数是以列表方式提供
char *const argv[] = {"ls","ls","-l",NULL};
execv("/bin/ls",argv);
其中参数是以数组形式提供
3,带e字母与不带e字母的相比较
execl("/bin/ls","ls","-l",NULL);
其中没有设置新进程环境变量
char *const envp[] = {"PATH:/bin:/usr/bin",NULL};
execle("/bin/ls","ls","-l",NULL,envp);
其中加上了环境变量列表 以二维数组形式提供
7,wait函数的使用
wait 函数的原型
#include <sys/wait.h>
pid_t wait(int *staloc);
调用wait函数的进程会堵塞,直到该进程的任意一个子进程结束,wait函数会取得结束的子进程的信息并且返回该子进程的ID,结束信息保存在参数staloc所指向的内存空间中。
我们通过判断宏和取值宏来判断哪些状态有效,并且取得相应的状态值。
状态 判断宏 取值宏
进程正常结束 WIFEXITED(status) WEXITSTATUS(status)
进程异常终止 WIFSIGNALED(status) WTERMSIG(status)
接下来通过一个实例来运用这两个宏进行比较:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t pid;
int status;
int a;
pid = fork(); //创建第一个子进程
if(pid < 0)
{
perror("create fork error...");
exit(1);
}
else if(pid == 0)
{
printf("the first,exit normally...\n"); //第一个子进程正常退出 返回0值
exit(0);
}
else
{
printf("the parent run...\n");
wait(&status); //wait函数
if(WIFEXITED(status) == 1) //判断宏 若为真 即1 值 子进程正常退出
{
printf("the first child is normally exit...\n");
printf("the fist status is :%d\n",WEXITSTATUS(status));//取值宏,打印返回值
}
}
pid = fork(); //创建第二个子进程
if(pid < 0)
{
perror("the second process fork create error..");
exit(1);
}
else if(pid == 0)
{
printf("the second process ,exit abnormally...\n");
a = 1 / 0; //0做除数会产生SIGFPE异常信号 信号值为8
}
else
{
wait(&status); //wait函数
if(WIFSIGNALED(status) == 1) //判断宏 子进程是否异常结束
{
printf("the second abnormally exit..\n");
printf("the second signal is :%d\n",WTERMSIG(status)); //打印返回的信号值 应为8
}
}
return 0;
}
下面是运行结果:
8,等待指定的进程waitpid()函数的使用
waitpid()函数原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *staloc, int options);
options选项常用的为WNOHANG
在使用WNOHANG参数时,父进程不会等待子进程结束,是非阻塞状态。
通过程序来进行理解:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子进程
{
printf("child run...\n");
sleep(10); //睡眠十秒
printf("child run over...\n");
exit(0);
}
else //父进程
{
printf("the parent run..\n");
do
{
ret = waitpid(pid,NULL,WNOHANG);
//waitpid函数使用 WNOHANG参数 非阻塞,父进程不会等待子进程结束
if(ret == 0)
{
printf("the child is not exit\n");
sleep(2); //休眠2秒
}
}while(ret == 0);
if(ret == pid) //当子进程结束 waitpid返回值为子进程id
{
printf("child process success exit..and ret is :%d\n",ret);
}
}
return 0;
}
运行结果如下所示:
通过程序可以看出,加上参数,父进程不会阻塞,不会等待子进程结束。当子进程结束时会返回 子进程的ID值
9,僵尸进程的概念
在LINUX系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程……
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被
销毁, 而是留下一个称为僵尸进程(Zombie)的数据结构
通过一个程序我们制造一个僵尸进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子进程
{
printf("child run...\n");
sleep(5); //睡眠五秒
printf("child run over...\n");
exit(0);
}
else //父进程
{
printf("the parent run..\n");
sleep(30);
wait(NULL); //调用wait函数回收子进程状态消息 防止产生僵尸进程
printf("parent run over...\n");
}
return 0;
}
程序中子进程睡眠5秒钟结束,而父进程睡眠三十秒没有调用wait函数回收,在这25秒内就产生了一个僵尸进程。
运行结果为:
使用ps -aux 命令查看进程状态
我们可以看到一个Z+ 标识符,就代表一个僵尸进程。当父进程睡眠三十秒后 调用wait函数,再查看系统进程就发现僵尸进程消失了。
10,僵尸进程的危害及如何避免僵尸进程的产生呢?
危害:linux系统进程数是有限制的,若有大量僵尸进程,使得系统 无法产生新进程,这就是僵尸进程最大的危害。
避免僵尸进程产生的方法
1,父进程通过wait和waitpid等函数等待子进程结束(代码参考上面)
3,fork两次,建立一个子进程,子进程再建立一个子进程,孙子进程做实际工作,子进程退出,让Init来接管孙子进程。
实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //创建一个子进程
{
printf("the child run....\n");
pid = fork(); //再创建子进程
if(pid < 0)
{
perror("the child child error...");
exit(1);
}
else if(pid == 0) //创建一个孙子进程
{
printf("do something you want...\n");
sleep(5);
printf("child child run over..\n");
exit(0);
}
else
{
exit(0); //子进程退出 将孙子进程托付Init进程
}
}
else
{
printf("the parent ...\n"); //父进程
}
return 0;
}
3,父进程中调用signal函数,signal(SIGCHLD,SIG_IGN);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
pid_t pid;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //创建一个子进程
{
printf("the child run....\n");
sleep(5);
printf("the child run over..\n");
exit(0);
}
else
{
signal(SIGCHLD,SIG_IGN); //调用signal函数 避免僵尸进程的产生
sleep(30);
printf("the parent ...\n"); //父进程
}
return 0;
}
首先理解进程的概念:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是动态的,是系统进行资源分配和调度的一个独立单位。
2,进程ID是进程的一个基本属性,下面介绍进程中六个重要的ID,及获取的函数原型
进程ID pid_t getpid();
父进程ID pid_t getppid();
进程的用户ID uid_t getuid();
有效用户ID uid_t geteuid();
进程的组ID gid_t getgid();
有效组ID gid_t getegid();
实现代码如下:
#include <stdio.h>
#include <unistd.h>
void main()
{
pid_t pid,ppid;
uid_t uid,euid;
gid_t gid,egid;
pid = getpid();
ppid = getppid();
uid = getuid();
euid = geteuid();
gid = getgid();
egid = getegid();
printf("pid: %u\n",pid);
printf("ppid: %u\n",ppid);
printf("uid: %u\n",uid);
printf("euid: %u\n",euid);
printf("gid: %u\n",gid);
printf("egid: %u\n",egid);
}
3,说到进程,我们该怎么创建进程呢?这里使用fork()函数创建进程。
#include <unistd.h> fork函数包含库
pid_t fork(void); fork函数原型
利用fork函数创建进程:
pid_t pid;
pid = fork();
fork函数不需要参数,他的返回值有三种情况:
1,父进程返回值大于0 即pid > 0
2,子进程返回值等于0 即pid == 0
3,创建失败 返回值为-1 小于0
既然有父子进程,那么父子进程之间又有啥关系呢?(也有三点)
1,父进程返回值为子进程ID,子进程返回的是0
2,父子进程是两个独立的进程,调度机会均等
3,父子进程,fork之后,操作系统会复制一个与父进程完全相同的子进程,虽说是父子关系,但更像是兄弟关系,这两个进程共享代码空间,但数据空间是相互独立的(重点记住)。
接下来,代码实现一个进程的创建,如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = fork(); //创建进程
if(pid < 0) //创建失败
{
printf("fork create error...\n");
return -1;
}
else if(pid > 0) //父进程
{
printf("parents pid:%d child pid :%d\n",getpid(),pid);
}
else // 子进程
{
printf("child pid:%d\n",getpid());
}
return 0;
}
4,上面所说fork函数创建父子进程,他们是相互独立的,那怎么创建一个共享空间的子进程呢?下面用类似fork函数的vfork()函数实现:
vfork 函数原型如下:
#include <unistd.h>
pid_t vfork(void);
vfork()函数与fork()函数有啥区别呢?
1,vfork函数创建的父子进程其子进程和父进程完全共享地址空间,包括代码段,数据段和堆栈段,子进程对这些共享资源的修改,可以影响到父进程,而fork函数不会。
2,vfork函数产生的子进程一定比父进程先运行,而fork函数产生的父子进程是由系统决定先后,调用机会均等。
5,进程的退出
一般用exit函数进行进程的退出操作,如果进程正常退出,参数为0 即exit(0),如果异常退出则为非0.
6.Linux环境下使用exec函数执行一个新程序
在进程中使用exec函数会将原进程内容取代,即完全执行exec函数程序内容,原代码将不会执行。
exec函数原型有六个,但其作用都相同,只是使用方法及参数不同而已。
#include <unistd.h>
extern char **environ;
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 execve(const char *path, char *const argv[],...,char * const envp[]);
int execvp(const char *file, char *const argv[]);
接下来,通过一些简单方法来区别他们的使用。
1,含有‘l’字母 的,其参数以列表方式提供
2,含有‘v’字母的,参数以二维数组形式提供
3,末尾带有‘e’字母的,表示传给新程序环境变量列表,二维数组形式提供
4,带有‘p’字母的,表示第一个参数不是路径名和程序名,而是只是一个程序名
通过不同的实例对比来进行exec函数的解析使用。
1,带p字母与不带p字母的相比较
例:使用ls -l 命令作为例子
execl("/bin/ls","ls","-l",NULL);
其中第一个参数包括路径名称及程序名称。
execlp("ls","ls","-l",NULL);
其中第一个参数只是程序名,没有包含路径
2,带l字母的与带v字母的比较。
execl("/bin/ls","ls","-l",NULL);
其中参数是以列表方式提供
char *const argv[] = {"ls","ls","-l",NULL};
execv("/bin/ls",argv);
其中参数是以数组形式提供
3,带e字母与不带e字母的相比较
execl("/bin/ls","ls","-l",NULL);
其中没有设置新进程环境变量
char *const envp[] = {"PATH:/bin:/usr/bin",NULL};
execle("/bin/ls","ls","-l",NULL,envp);
其中加上了环境变量列表 以二维数组形式提供
7,wait函数的使用
wait 函数的原型
#include <sys/wait.h>
pid_t wait(int *staloc);
调用wait函数的进程会堵塞,直到该进程的任意一个子进程结束,wait函数会取得结束的子进程的信息并且返回该子进程的ID,结束信息保存在参数staloc所指向的内存空间中。
我们通过判断宏和取值宏来判断哪些状态有效,并且取得相应的状态值。
状态 判断宏 取值宏
进程正常结束 WIFEXITED(status) WEXITSTATUS(status)
进程异常终止 WIFSIGNALED(status) WTERMSIG(status)
接下来通过一个实例来运用这两个宏进行比较:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main()
{
pid_t pid;
int status;
int a;
pid = fork(); //创建第一个子进程
if(pid < 0)
{
perror("create fork error...");
exit(1);
}
else if(pid == 0)
{
printf("the first,exit normally...\n"); //第一个子进程正常退出 返回0值
exit(0);
}
else
{
printf("the parent run...\n");
wait(&status); //wait函数
if(WIFEXITED(status) == 1) //判断宏 若为真 即1 值 子进程正常退出
{
printf("the first child is normally exit...\n");
printf("the fist status is :%d\n",WEXITSTATUS(status));//取值宏,打印返回值
}
}
pid = fork(); //创建第二个子进程
if(pid < 0)
{
perror("the second process fork create error..");
exit(1);
}
else if(pid == 0)
{
printf("the second process ,exit abnormally...\n");
a = 1 / 0; //0做除数会产生SIGFPE异常信号 信号值为8
}
else
{
wait(&status); //wait函数
if(WIFSIGNALED(status) == 1) //判断宏 子进程是否异常结束
{
printf("the second abnormally exit..\n");
printf("the second signal is :%d\n",WTERMSIG(status)); //打印返回的信号值 应为8
}
}
return 0;
}
下面是运行结果:
8,等待指定的进程waitpid()函数的使用
waitpid()函数原型
#include <sys/wait.h>
pid_t waitpid(pid_t pid,int *staloc, int options);
options选项常用的为WNOHANG
在使用WNOHANG参数时,父进程不会等待子进程结束,是非阻塞状态。
通过程序来进行理解:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子进程
{
printf("child run...\n");
sleep(10); //睡眠十秒
printf("child run over...\n");
exit(0);
}
else //父进程
{
printf("the parent run..\n");
do
{
ret = waitpid(pid,NULL,WNOHANG);
//waitpid函数使用 WNOHANG参数 非阻塞,父进程不会等待子进程结束
if(ret == 0)
{
printf("the child is not exit\n");
sleep(2); //休眠2秒
}
}while(ret == 0);
if(ret == pid) //当子进程结束 waitpid返回值为子进程id
{
printf("child process success exit..and ret is :%d\n",ret);
}
}
return 0;
}
运行结果如下所示:
通过程序可以看出,加上参数,父进程不会阻塞,不会等待子进程结束。当子进程结束时会返回 子进程的ID值
9,僵尸进程的概念
在LINUX系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程……
一个进程在调用exit命令结束自己的生命的时候,其实它并没有真正的被
通过一个程序我们制造一个僵尸进程:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
pid_t pid;
int ret;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork error...");
exit(1);
}
else if(pid == 0) //子进程
{
printf("child run...\n");
sleep(5); //睡眠五秒
printf("child run over...\n");
exit(0);
}
else //父进程
{
printf("the parent run..\n");
sleep(30);
wait(NULL); //调用wait函数回收子进程状态消息 防止产生僵尸进程
printf("parent run over...\n");
}
return 0;
}
程序中子进程睡眠5秒钟结束,而父进程睡眠三十秒没有调用wait函数回收,在这25秒内就产生了一个僵尸进程。
运行结果为:
使用ps -aux 命令查看进程状态
我们可以看到一个Z+ 标识符,就代表一个僵尸进程。当父进程睡眠三十秒后 调用wait函数,再查看系统进程就发现僵尸进程消失了。
10,僵尸进程的危害及如何避免僵尸进程的产生呢?
危害:linux系统进程数是有限制的,若有大量僵尸进程,使得系统 无法产生新进程,这就是僵尸进程最大的危害。
避免僵尸进程产生的方法
1,父进程通过wait和waitpid等函数等待子进程结束(代码参考上面)
3,fork两次,建立一个子进程,子进程再建立一个子进程,孙子进程做实际工作,子进程退出,让Init来接管孙子进程。
实现代码如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
int main()
{
pid_t pid;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //创建一个子进程
{
printf("the child run....\n");
pid = fork(); //再创建子进程
if(pid < 0)
{
perror("the child child error...");
exit(1);
}
else if(pid == 0) //创建一个孙子进程
{
printf("do something you want...\n");
sleep(5);
printf("child child run over..\n");
exit(0);
}
else
{
exit(0); //子进程退出 将孙子进程托付Init进程
}
}
else
{
printf("the parent ...\n"); //父进程
}
return 0;
}
3,父进程中调用signal函数,signal(SIGCHLD,SIG_IGN);
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/types.h>
#include <signal.h>
int main()
{
pid_t pid;
pid = fork(); //创建子进程
if(pid < 0)
{
perror("fork create error ..");
exit(1);
}
else if(pid == 0) //创建一个子进程
{
printf("the child run....\n");
sleep(5);
printf("the child run over..\n");
exit(0);
}
else
{
signal(SIGCHLD,SIG_IGN); //调用signal函数 避免僵尸进程的产生
sleep(30);
printf("the parent ...\n"); //父进程
}
return 0;
}