Linux进程学习

一、进程概念

进程是指正在运行的程序,一个程序中可以包含多个进程;一个进程可能包含一个或者多
个线程。

1.1 进程ID

每个进程都有一个唯一的标识符,叫做进程ID,简称pid。

内核运行的第一个进程是init程序,pid为1,是唯一的。

除了init进程,其他进程都有由别的进程进行创建的。创建新进程的进程叫父进程,创建的新进程叫做子进程

1.2 获取进程

在系统调用函数中,getpidgetppid函数均可以用来获取进程的ID号。

需要包含的头文件为:#include<sys/types.h>#include<unistd.h>

函数原型分别为:

pid_t getpid(void);
pid_t getppid(void);

根据描述,getpid返回进程的子进程,getppid返回进程的父进程。

在这里插入图片描述

简单获取进程:

#include <stdio.h>

#include <sys/types.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
	pid_t ppid, spid;
	spid = getpid();//获取子进程
	if(spid)
		printf("spid = %d\n", spid);
	ppid = getppid();//获取父进程
	if(ppid)
		printf("ppid = %d\n", ppid);
}

进程如下:

在这里插入图片描述

1.3 查看进程、杀死进程

在终端输入top可以实时查看当前的进程运行情况,ps可以查看当前时间的进程情况。

在这里插入图片描述

使用kill+ID可以杀死某个进程。

二、创建进程

当需要在程序中进行创建新的程序时,可以使用exec族函数和fork进程调用函数。使用exec方式会比较直接,fork方式则比较复杂。

2.1 exec方式

使用exec函数族需要注意,当某个进程调用了exec函数族去执行一个新的程序或命令时,这个进程会在执行exec完后结束自己,不会往下执行与父进程相同的内容。

在这里插入图片描述

exec族函数属于C库函数,查看man手册可知,包含六个函数:execl, execlp, execle, execv, execvp, execvpe ,需要包含的头文件为#include <unistd.h>

在这里插入图片描述

这几个函数可以简单进行区分:

  • “l”“v”表示参数是以列表还是以数组的方式提供的。它们的区别在于,execv 开头的函数是以“char *argv[]”(vector)形式传递命令行参数的,而execl 开头的函数采用罗列(list)的方式,把参数一个一个列出来,然后以一个NULL表示结束
  • “p”表示这个函数的第一个参数是path,就是以绝对路径来提供程序的路径,也可以以当前目录作为目标。
  • “e”表示为程序提供新的环境变量。
2.1.1 execl函数:

main.c:

#include <stdio.h>

#include <unistd.h>

int main(int argc, char *argv[])
{
    if(execl("test/hello", "hello", "First!", NULL) == -1)
    {
        printf("execl hello failed!\n");
        return -1;
    }

    printf("can not excel hello!\n");
    return 0;
}

hello.c:

#include <stdio.h>

int main(int argc, char *argv[])
{
    printf("arg[1] = %s\n", argv[1]);
    printf("hehehe\n");
}

输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBKmDGou-1583759934993)(C:\Users\ACER\AppData\Roaming\Typora\typora-user-images\image-20200308174623403.png)]

2.1.2 excev函数:

main.c修改为:

#include <stdio.h>

#include <unistd.h>

int main(int argc, char *argv[])
{
    char *Eargv[] = {"hello", "First!", NULL};
    if(execv("test/hello", Eargv) == -1)
    {
        printf("execl hello failed!\n");
        return -1;
    }

    printf("can not excel hello!\n");
    return 0;
}

hello.c内容不变,编译执行:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IdbYQMrF-1583759935001)(C:\Users\ACER\AppData\Roaming\Typora\typora-user-images\image-20200308175453652.png)]

输出结果一致。

区别

从上面的结果可以看出,execlexecv的区别在于调用程序的参数传递方式不同。带l的函数通过列表的方式直接在本函数参数中传递,遇到NULL即参数结束;带v的函数则是通过一个字符指针数组封装参数后直接传递给函数。

2.2 fork方式

在 linux 中可以使用 fork 创建和当前进程一样的进程,新的进程叫子进程,原来的进程叫父进程。

函数的原型只有一个:

在这里插入图片描述

pid_t fork(void);

返回值:执行成功,子进程 pid 返回给父进程,0 返回给子进程;出现错误-1,返回给父
进程。

内存不够或者 id 号用尽时,会出现错误。

参考一些经典的例子,首先理解一下的fork的流程:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
    pid_t fpid;
    int count = 0;

    fpid = fork();
    if(fpid < 0)
        printf("fork err!\n");
    else if (fpid == 0)
    {
        printf("I am child  process, ppid pid fpid: %4d %4d %4d\n", getppid(), getpid(), fpid);
        count++;
    }else{
        printf("I am parent process, ppid pid fpid: %4d %4d %4d\n", getppid(), getpid(), fpid);
        count++;
    }

    printf("final count = %d\n", count);
    return 0;
}

执行结果:

在这里插入图片描述

分析

fork执行成功后,创建了一个子进程,子进程的ID为2246,返回给当前进程2245(父进程),644则是2245即当前进程的父进程。

创建的子进程2246跟2245一样,会执行fork下面程序。也就是说在语句fpid=fork()之前,只有一个进程在执行这段代码,但在这条语句之后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)

执行子进程2246时,父进程自然是2245,根据fork的描述,fork执行后返回给子进程的是0,因此fpid = 0;

可以用下面这幅图描述这个过程:

在这里插入图片描述

再通过一个复杂一点的例子进行理解:

/*file:  test.c*/
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char **argv)
{
    int i = 0;
    printf("i son/par ppid pid fpid\n");
    for(i = 0; i < 2; i++){
        pid_t fpid = fork();
        if(fpid == 0)
            printf("%d  child %4d %4d %4d\n", i, getppid(), getpid(), fpid);
        else
            printf("%d parent %4d %4d %4d\n", i, getppid(), getpid(), fpid);
    }
    return 0;
}

执行结果:

在这里插入图片描述

分析

i=0第一次循环,当前父进程是2262,fork创建了一个子进程2263, 664为当前进程的父进程,用链表表示:

644->2262->2263(第二行)

创建的子进程2263中,fork返回给2263的是0,所以子进程的子进程ID=0,链表表示:

2262->2263->0(第三行)

接着,在之前的进程2262中,i=1,fork执行,再次创建一个子进程2264,父进程仍为644,链表表示:

664->2262->2264(第四行)

然后创建的子进程2264也会执行后面的判断,fork给这个子进程返回的ID也是0,所以:

2262->2264->0(第五行)

最后由2262创建的第一个子进程2263也会做相同的操作,这个进程的i++,i=1,执行fork创建子进程2265,所以:

2262->2263->2265(第六行)

接着2263创建的子进程2265也会做相同的事情,fork给它返回0,所以:

2263->2265->0(最后一行)

此时这些进程的i均为1,再次执行会完成当前进程退出,如果前面的某些进程已经退出被杀死了,则由这些被杀死的进程创建的子进程的父进程id就会变为1,如下:

在这里插入图片描述

进程执行的先后和什么时间杀死进程并不一定都一样。大致过程描述如下:

在这里插入图片描述

每次fork都会创建新的子进程,直到条件不满足。这样就进行了三次fork,并且创建了三个子进程。

三、终止进程

前面讲了如何获取、创建进程,只剩下终止或者叫杀死进程了。终止一个进程使用exit函数。需要包含标准库头文件stdlib.h

原型:

void exit(int status);

参数status是传递给父进程的参数。

例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(int argc, char *argv[]) 
{
    char *Myargv[] = {"ls", "-l", NULL};
    printf("start ppid pid = %d, %d\n", getppid(), getpid());
    if(fork() == 0)
    {
        printf("fork 1 success!\n");
        printf("ppid pid = %d, %d\n", getppid(), getpid());
        if(execv("/bin/ls", Myargv) < 0)
        {
            printf("execv failed!\n");
            exit(1);//退出进程
        }
        printf("end ppid pid = %d, %d\n", getppid(), getpid());//无法执行,exec族函数会结束子进程
    }
    sleep(2);//延时两秒
    if(fork() == 0)//如果当前为子进程
    {
        printf("fork 2 success!\n");
        printf("ppid pid = %d, %d\n", getppid(), getpid());
        if(execl("/bin/ls", "ls", "-lh", NULL) < 0)
        {
            printf("execl failed!\n");
            exit(1);
        }
        printf("end ppid pid = %d, %d\n", getppid(), getpid());//无法执行,exec族函数会结束子进程
    }
    sleep(2);//延时两秒
    if(fork() == 0)//如果当前为子进程
    {
        printf("fork 3 success!\n");
        printf("ppid pid = %d, %d\n", getppid(), getpid());
        if(execlp("ls", "ls", "-la", NULL) < 0)
        {
            printf("execlp failed!\n");
            exit(1);
        }
        printf("end ppid pid = %d, %d\n", getppid(), getpid());//无法执行,exec族函数会结束子进程
    }

    sleep(1);
    printf("final ppid pid = %d, %d\n", getppid(), getpid());
    printf("excute excelp ls -la----------------\n");
    execlp("ls", "ls", "-la", NULL);
    sleep(1);
    printf("finish!\n");
    return 0;
}

结果:

在这里插入图片描述

当前进程916,第一次fork创建一个子进程917,fork返回给子进程917的值为0,所以执行第一个if(fork() == 0),917执行到excev函数之后,就会结束当前的917进程(exce族函数执行后会结束当前进程),所以不会917不会有后续fork行为。

916进程这边会继续fork,创建子进程924,924进程执行execl函数后也会结束自己。

最后916进程执行最终的fork,创建子进程925,925进程同样执行execlp函数后结束自己。

这样最后查看PID,只有原来的916进程在执行,916在rerurn之前调用了exec族函数,也会结束自己。因此看不到finish。

过程示意图:

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值