目录
一 ,进程与程序(process & program)
程序:通常为二进制程序,放置在存储媒介中(硬盘,光盘,软盘,磁带等),以物理文件的形式存在。
进程:程序被触发后,执行者的权限与属性,程序的代码与所需数据等都会被加载到内存中,操作系统给予这个内存中的单元一个标识符(PID),可以说进程就是一个正在运行中的程序。
二 ,进程标识符(PID)
含义:每一个进程都有一个非负整数表示的唯一ID,叫做pid,类似一个人一个身份证号;
pid = 0:交换进程,进程调度
pid = 1:init进程,系统初始化(比如:pc,pos机,点歌机,提款机等设备的开机显示界面,就是初始进程)
三 ,getpid()函数:获取进程标识符(PID)
头文件: #include <sys/types.h>
#include <unistd.h>
函数原型: pid_t getpid(void);
pid_t getppid(void); // 获取父进程pid
getpid()函数说明:在此函数参数列表中只有void,说明函数参数是为空。
代码示例:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t pid;
pid = getpid();
printf("pid:%d\n",pid);
return 0;
}
运行结果:成功获取新进程pid
dhw@dhw-virtual-machine:~$ ./a.out
pid:51018
四 ,查询进程pid的相关指令: ps top
① 使用ps指令查看。实际工作中,可以配合grep来查找程序中是否存在某一个进程。
dhw@dhw-virtual-machine:~$ ps -aux|grep init编写格式说明:输入ps -aux指令,会调用系统已经存在的所有进程,不方便查找具体某一个进程信息,于是需要调用grep来过滤系统冗杂的进程,直接指向我们需要查看的进程信息。
② 使用top指令查看,类似windows系统任务管理器。
dhw@dhw-virtual-machine:~$ top说明:直接在命令行输入top即可。top指令可用于查看程序进程的cpu占用率,内存消耗情况等,从而来评估程序的优化情况。
五 ,fork()函数的使用:创建子进程
父进程和子进程:
含义:如果进程A创建了进程B,那么进程A就是父进程,进程B就是子进程,属于相对关系。也可以通俗理解为人类中的父子关系。
fork()函数代码示意:
函数说明:fork()函数也是属于无参函数,主要作用于通过原进程创建新的进程来接受客户端指令。
头文件: #include <sys/types.h>
#include <unistd.h>原型: pid_t fork(void);
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid = getpid();
printf("pidA:%d\n",pid);
fork();
printf("pidB:%d\n",getpid());
return 0;
}
编译结果:
dhw@dhw-virtual-machine:~$ ./a.out
pidA:51712
pidB:51712
pidB:51713
代码说明:上图代码中可知,在使用fork()函数之前,通过gitpid()获取了原进程pidA:51712,使用fork()函数之后,生成两个进程pidB:51712,pidB:51713。其中一个pidB和原进程pidA的pid值相等,也就是父进程,另外一个pidB就是子进程。
▲由此得出结论(fork()返回值):fork函数被原/父进程调用一次,但是却返回两次;一次是返回到父/原进程,一次是返回到新创建的子进程。
| fork()返回值 | 结果 |
| <0 | 获取进程失败 |
| >0 | 返回父进程或调用者。该值包含新创建的子进程的进程ID |
| ==0 | 返回到新创建的子进程。 |
fork()函数写实拷贝:
说明:在调用fork()函数时,系统内核并不复制整个进程的地址空间,而是让父进程和子进程共享同一个数据内容。只有在需要写入的时候,共享的数据内容才会被拷贝和调用。
附:vfork()函数:
说明:fork()与vfork()都可以创建一个进程,但vfork是由fork()函数封装得来的。fork是父子进程同时顺序进行,不会中断影响,而vfork是先执行子进程,当子进程结束之后才会执行父进程
六 ,孤儿进程和僵尸进程
孤儿进程:父进程结束后,子进程还在运行,此时的子进程就叫做孤儿进程。但是系统不允许进程中有太多孤儿进程,就会安排pid=1的初始进程init来领养孤儿进程(系统版本差异,也可能是其它进程来收养),此时的初始进程为此进程的父进程,孤儿进程重新开始为子进程。
僵尸进程:子进程结束后,父进程还在运行,且没有收集子进程的退出状态,此时的子进程就是僵尸进程。
孤儿进程------代码示例:
#include <sys/types.h> #include <unistd.h> #include <stdio.h> int main() { pid_t pid; pid = fork(); if(pid > 0){ printf("father pid:%d\n",getpid()); } else if(pid == 0){ printf("child pid:%d,2father pid:%d\n",getpid(),getppid()); //子进程pid //新父进程pid } return 0; } 编译结果: dhw@dhw-virtual-machine:~$ ./a.out father pid:3593 dhw@dhw-virtual-machine:~$ child pid:3594,2father pid:1587 使用ps指令查询pid=1587的进程: dhw@dhw-virtual-machine:~$ ps -aux|grep 1587 dhw 1587 0.0 0.6 20436 12740 ? Ss 13:00 0:00 /lib/systemd/systemd --user dhw 3548 0.0 0.1 17880 2316 pts/1 S+ 13:44 0:00 grep --color=auto 1587代码说明:父进程获取pid=3593后停止运行,子进程继续运行,同时获取子进程的父进程pid,会发现pid并不是原父进程的pid,而是生成了新的pid=1587,输入指令ps -aux|grep 1587查询可知,1587是一个名为system的系统进程,也就有是说,之前的孤儿进程被系统进程所领养,生成了新的父子进程关系。
僵尸进程------代码示例:
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if(pid > 0){ while(1){ printf("father pid:%d\n",getpid());//父进程继续运行 sleep(2); } } else if(pid == 0){ printf("child pid:%d\n",getpid()); //先让子进程结束运行 exit(0); } return 0; } 编译结果: dhw@dhw-virtual-machine:~$ ./a.out father pid:3754 //父进程pid child pid:3755 //子进程获取pid后,停止运行 father pid:3754 father pid:3754 father pid:3754 //父进程一直运行。。。。。。 查看子进程pid:显示z+(僵尸进程) dhw 3755 0.0 0.0 0 0 pts/4 Z+ 14:29 0:00 [a.out] <defunct>代码说明:子进程获取pid=3755后停止运行,父进获取pid=3754后一直运行,通过ps指令查看子进程状态显示为“z+”(单词zombie缩写,意为僵尸)。
七 ,父进程等待子进程退出
原因:父进程等待子进程退出,并收集子进程的退出状态,如果子进程退出状态不被收集,那么子进程就会变成僵尸进程。
实现方法:父进程需要调用wait()函数来收集子进程退出的状态码。
wait()函数使用:
头文件: #include <sys/types.h>
#include <sys/wait.h>原型: pid_t wait(int *wstatus);
| WIFEXITED(status) | 如果子进程正常退出,则该宏为真。 |
| WEXITSTATUS(status) | 如果子进程正常退出,则该宏将获取子进程退出值。 |
| WCOREDUMP(status) | 如果子进程被信号杀死且生成核心转储文件(core,dump),则该宏为真。 |
| WTERMSIG (status) | 如果子进程被信号杀死,该宏获取导致他死亡信号值 |
| WIFSTOPPED(status) | 如果子进程被信号暂停,且option中 WUNTRACED已被设置,为真 |
| WSTOPSIG(status) | /如果WIFSYOPPEN(status)为真,则该宏将获取导致子进程暂停的信号值。 |
| WIFCONTINUED(status) | 如果子进程被信号SIGONT重新置为就绪态,该宏为真。 |
父进程收集子进程退出状态的代码示例:
#include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <sys/wait.h> int main() { pid_t pid; pid = fork(); int status;//定义状态值 if(pid > 0){ wait(&status); //调用wait()函数 printf("status:%d\n",WEXITSTATUS(status));//打印子进程退出的状态值 while(1){ printf("father pid:%d\n",getpid()); sleep(2); } } else if(pid == 0){ printf("child pid:%d\n",getpid()); exit(2);//子进程退出的状态码为2,父进程收集的状态码也为2 } return 0; } 编译结果: dhw@dhw-virtual-machine:~$ ./a.out child pid:4971 status:2 //父进程收集的状态码和子进程正常退出的值都为2 father pid:4970 father pid:4970说明:子进程正常退出,退出值为2,父进程调用wait()函数收集子进程的退出值后,在使用ps指令查询子进程状态,发现并没有子进程的的进程信息,因为此时的子进程已经正常退出,因此也不属于僵尸进程。
八 ,exec族函数配合fork()函数使用
exec族函数:
作用:用fork函数创建新进程后,经常会在新进程中调用exec函数去执行另外一个程序。当进程调用exec函数时,该进程被完全替换为新程序。因为调用exec函数并不创建新进程,所以前后进程的ID并没有改变。
功能:在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
函数族:exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe
函数原型:
#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 execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[],char *const envp[]);参数说明:
path:可执行文件的路径名字
arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束
file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。
☺☺☺接下来代码示例最常用的execl()函数。
详细了解exec函数族,参考链接内容:https://blog.csdn.net/u014530704/article/details/73848573
execl()函数配合fork()函数使用:
代码示例:
//hello.c -o haha #include <stdio.h> int main() { int arr[][2]={{3,4},{7,8}}; int i,j; int len = sizeof(arr)/sizeof(arr[0]); for(i=0;i<len;i++){ for(j=0;j<2;j++){ printf("%d ",arr[i][j]); } } return 0; } 运行结果: dhw@dhw-virtual-machine:~$ ./a.out 3 4 7 8 dhw@dhw-virtual-machine:~$ #include <sys/types.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> int main() { pid_t pid; pid = fork(); if(pid > 0){ printf("father pid:%d\n",getpid()); } else if(pid == 0){ printf("child pid:%d\n",getpid()); execl("/home/dhw/hello","haha",NULL); //"路径/文件名",“执行程序名”,NULL结尾 } return 0; }编译结果: dhw@dhw-virtual-machine:~$ ./a.out father pid:2758 child pid:2759 dhw@dhw-virtual-machine:~$ 3 4 7 8 //hello.c文件编译的结果代码说明:先写好脚本文件hello.c,内容为遍历二维数组。在代码fork后中的子进程中调用execl()函数后,编译结果为父子进程的pid,和hello.c文件中遍历二维数组的内容。
九 ,system()函数和popen()函数
system()函数:
功能:执行 dos(windows系统) 或 shell(Linux/Unix系统) 命令,参数字符串command为命令名。
说明:在windows系统中,system函数直接在控制台调用一个command命令。
在Linux/Unix系统中,system函数会调用fork函数产生子进程,由子进程来执行command命令,命令执行完后随即返回原调用的进程。system - execute a shell command //执行shell命令 头文件: #include <stdlib.h> 原型 : int system(const char *command);代码示例:
#include <sys/types.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> int main() { pid_t pid; pid = fork(); if(pid > 0){ printf("father pid:%d\n",getpid()); } else if(pid == 0){ printf("child pid:%d\n",getpid()); system("ls"); //在子进程中调用system,输入ls指令 } return 0; } 编译结果: dhw@dhw-virtual-machine:~$ ./a.out father pid:3206 child pid:3207 dhw@dhw-virtual-machine:~$ ag.c demo10.c demo1.c demo2.c demo4.c demo6.c demo8.c file hello kk strstr.c a.out demo11.c demo20.c demo3.c demo5.c demo7.c demo9.c haha hello.c snap
popen()函数:
说明:① system函数的执行需要通过调用fork()函数创建一个子进程,子进程通过execl函数调用shell对传参的可执行文件进行实现。意味着system函数实现需要依赖execl函数实现自身功能。因此system函数的结果将直接显示在终端上,这样原本运行的结果就无法保存在文件中用于实现信息交互等功能。
② popen函数对比system函数的优势:可以获得运行的输出结果。③ 通过popen函数读入信息,可以通过网络的方式发送从而实现多机通信。
原型:
#include <stdio.h> FILE *popen(const char *command, const char *type); int pclose(FILE *stream);参数说明:
① commmand:是一个指向以 NULL 结束的 shell 命令字符串的指针。这行命令将被传到 bin/sh 并使用 -c 标志,shell 将执行这个命令。
② type:只能是读和写的一种,如果是 “r” 则文件指针连接到command的标准输出,返回的文件指针是可读;如果是 “w” 则文件指针连接到command的标准输入,返回的文件指针是可写。
③ stream:popen返回的文件地址。
pclose函数:用于关闭标准I/O流。popen()函数的返回值是一个普通的标准I/O流, 它只能用 pclose() 函数来关闭。函数实现:
#include <stdio.h> int main() { FILE *fp; char ret[1024] = {0}; fp = popen("ps","r");//ps执行的结果会流向popen开辟的管道中/流(fp)。 int n_read = fread(ret,1,1024,fp); printf("read ret %d Byte,ret=%s\n",n_read,ret); pclose(fp); return 0; } 编译结果:输出ret的值 dhw@dhw-virtual-machine:~$ ./a.out read ret 363 Byte,ret= PID TTY TIME CMD 2363 pts/0 00:00:00 bash 2954 pts/0 00:00:00 top 2968 pts/0 00:00:00 a.out 2969 pts/0 00:00:00 sh 2970 pts/0 00:00:00 top 2989 pts/0 00:00:00 a.out 2990 pts/0 00:00:00 sh 2991 pts/0 00:00:00 top 3953 pts/0 00:00:00 a.out 3954 pts/0 00:00:00 sh 3955 pts/0 00:00:00 ps代码说明:popen得到“ps”指令的结果,并存放在文件中
本文详细介绍了进程的基本概念,包括进程与程序的区别、PID的含义以及如何通过`getpid()`获取PID。接着,讨论了`fork()`函数在创建子进程中的应用,解释了父进程与子进程的关系,以及`vfork()`的异同。此外,文章还讲解了孤儿进程和僵尸进程的产生及处理方式,强调了父进程等待子进程退出的重要性。最后,提到了`exec`族函数和`system()`、`popen()`函数在进程间通信和执行命令的作用。
1304

被折叠的 条评论
为什么被折叠?



