Linux进程控制:初识篇

本文详细介绍了进程的基本概念,包括进程与程序的区别、PID的含义以及如何通过`getpid()`获取PID。接着,讨论了`fork()`函数在创建子进程中的应用,解释了父进程与子进程的关系,以及`vfork()`的异同。此外,文章还讲解了孤儿进程和僵尸进程的产生及处理方式,强调了父进程等待子进程退出的重要性。最后,提到了`exec`族函数和`system()`、`popen()`函数在进程间通信和执行命令的作用。
摘要由CSDN通过智能技术生成

目录

一 ,进程与程序(process & program)

二 ,进程标识符(PID)

三 ,getpid()函数:获取进程标识符(PID)

四 ,查询进程pid的相关指令: ps  top

五 ,fork()函数的使用:创建子进程

父进程和子进程:

fork()函数代码示意:

fork()函数写实拷贝:

附:vfork()函数:

六 ,孤儿进程和僵尸进程

孤儿进程------代码示例:

僵尸进程------代码示例:

七 ,父进程等待子进程退出

八 ,exec族函数配合fork()函数使用

exec族函数:

execl()函数配合fork()函数使用:

九 ,system()函数和popen()函数

system()函数:

popen()函数:


一 ,进程与程序(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”指令的结果,并存放在文件中

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

D.•

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值