Linux系统编程之进程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux系统编程之进程相关的知识,希望对你有一定的参考价值。

   前一段时间对文件I/O的基本操作基本操作做了总结,今天这里继续按照我的理解对linux系统编程的进程操作进行总结。


首先我们先理解几个概念:程序、进程、线程。

      所谓程序,就是计算机指令的集合,它以文件的形式存储在磁盘上,进程是一个程序在其自身的地址空间中的一次执行活动。而线程进程内的一个执行单元,也是进程内的可调度实体。说完这个不知道大家理解了吗?反正我第一次听到这个概念以后看到的时候可以明白过后就忘记了,现在我给大家举一个例子帮助大家理解,大家都看电视剧吧,所谓程序,就是一个剧本,像什么《西游记》、《神雕侠侣》……好多,这里不是介绍电视剧,回归我们的主题,程序呢就是一个剧本,那么进程就是一个具体拍好的电视剧,比如86版《西游记》,每播放一次就是编译好的程序的执行,也就是进程。有人会说了,那我不喜欢86版《西游记》,就喜欢看张纪中版《西游记》那这个有对应什么呢?这可难不倒博主,博主对这个问题,也做了思考,这里我使用C语言开发程序,那么有人学的Java、python……对吧,那么用不同语言开发的程序这就对应着不同版本的《西游记》,我们继续,那么线程是什么呢?我们的《西游记》都有很多集,同样的我们的进程也是由多个线程组成的也就是线程。这样说,不知道大家记住了吗?大家记住这个以后我还是给出他们的概念和相互之间的区别。

技术分享图片技术分享图片


    1、概念:

            程序:程序是计算机指令的集合,它以文件的形式存储在磁盘上。

            进程:进程是一个程序在其自身的地址空间中的一次执行活动。

            线程:进程内的一个执行单元,也是进程内的可调度实体。

    2、联系:

            1、一个进程是程序的一次动态执行,程序是放在硬盘中的静态的,进程是占用系统资源是动态的加载到内存中的。

            2、进程是系统进行资源分配和调度的基本单位,是操作系统结构的基础。而线程线程,有时被称为轻量级进程,是程序执行流的最小单元。

            3、一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.

            4、相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。

    3、区别

    地址空间:线程是进程内的一个执行单元;进程至少有一个线程;它们共享进程的地址空间;而进程有自己独立的地址空间;

    资源拥有:进程是资源分配和拥有的单位,同一个进程内的线程共享进程的资源
    线程是处理器调度的基本单位,但进程不是.
    进程和线程二者均可并发执行.
    简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
    线程的划分尺度小于进程,使得多线程程序的并发性高。
    另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
    线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程 序中,由应用程序提供多个线程执行控制。
    从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。

区分了这三个重要概念以后,我们重点来看与进程有关的Linux/Unix系统API。


    1、创建子进程fork

       #include <unistd.h>
       pid_t fork(void);

    fork系统调用用于创建子进程,一个现存进程调用fork函数是UNIX内核创建一个新进程的唯一方法(这并不适用于交换进程、init进程和精灵进程。这些进程是由内核作为自举过程的一部分以特殊方式创建的)。由fork创建的新进程被称为子进程( child process)。该函数被调用一次,但返回两次。两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。将子进程I D返回给父进程的理由是:因为一个进程的子进程可以多于一个,所以没有一个函数使一个进程可以获得其所有子进程的进程ID。fork使子进程得到返回值0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用getppid以获得其父进程的进程ID (进程ID 0总是由交换进程使用,所以一个子进程的进程ID不可能为0)。

    下来我写个简单的程序测试一下这个API的用法:

#include<stdlib.h>
#include<unistd.h>
int main()
{
    pid_t pid;
    //fork调用一次,返回两次,子进程返回0,父进程返回子进程ID,
    pid = fork();

    if(pid < 0)
    {
        printf("NO  child \n");
        exit(1);
    }
    else if( 0 == pid)
    {
        printf("child \n");
    }
    else
    {
        printf("I am arent \n");
    }


    return 0;
}

            2、vfork()

       vfork同样是用来创建进程的,但是fork由细微的不同,  

          1.vfork保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

          2.fork要拷贝父进程的进程环境;而vfork则不需要完全拷贝父进程的进程环境,在子进程没有调用exec和exit之前,子进程与父进程共享进程环境,相当于线程的概念,此时父进程阻塞等待。

    同样的我给出简单的测试用例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>

int main()
{
    pid_t pid = -1;

    pid = vfork();

    if(pid < 0)
    {
        printf("vfork error\n");
        exit(1);
    }
    else if(pid > 0)
    {
        printf("I am parent\n");
    }
    else
    {
        sleep(10);
        printf("I am child\n");
        exit(1);
    }

    return 0;
}

我们可以从运行结果得到,每次都是子进程运行完成后,才是父进程运行。


        3、getpid()/getppid()

            获取当前进程id和父进程id,进程id是标识进程的唯一标识。

            我这里同样给出一个简单测试用例:

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>


int main()
{
    pid_t pid;
    //fork调用一次,返回两次,子进程返回0,父进程返回子进程ID,
    pid = fork();
    if(pid < 0)
    {
        printf("NO  child \n");
        exit(1);
    }
    else if( 0 == pid)
    {
        printf("I am Child \n");
        printf("pid = %d\n",getpid());
        printf("ppid = %d\n",getppid());
    }
    else if (pid > 0)
    {
        sleep(1);
        printf("I am parent \n");
        printf("pid = %d\n",getpid());
        printf("ppid = %d\n",getppid());
    }
    return 0;
}
运行结果:                       
[[email protected] Fork]# ./fork-2 
I am Child 
pid = 6353
ppid = 6352
I am parent 
pid = 6352
ppid = 3674

       


     进入下一个系统API之前我们先学习几个概念:僵尸进程、孤儿进程,如同名字一样,僵尸进程就是子进程


          孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

     僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。

    之前我们用fork简单创建一个进程后,执行程序后发现,子进程和父进程运行是没有先后顺序的,而且是分开显示的,我们之前可以用sleep函数,实现防止产生孤儿进程,但是这种方法比较耗费系统资源,解决孤儿进程和僵尸进程,我们这里就要引入其他几个API: wait()/waitpid()

           #include <sys/types.h>
       #include <sys/wait.h>
       pid_t wait(int *status);
       pid_t waitpid(pid_t pid, int *status, int options);


    wait()函数,当调用后,阻塞等待任意一个子进程退出后,就会立即返回。调用成功返回这个子进程的ID,调用失败返回-1。wait()与waitpid函数里面的的status返回一个值,用几个宏测试其子进程退出状态。

wait获取staus后检测处理
宏定义  描述
    WIFEXITED(status) 如果进程子进程正常结束,返回一个非零值
    WEXITSTATUS(status) 如果WIFEXITED非零,返回子进程退出码
    WIFSIGNALED(status) 子进程因为捕获信号而终止,返回非零值
    WTERMSIG(status) 如果WIFSIGNALED非零,返回信号代码
    WIFSTOPPED(status) 如果进程被暂停,返回一个非零值
    WSTOPSIG(status) 如果WIFSTOPPED非零,返回信号代码


#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<errno.h>

int main()
{
    pid_t pid = -1,child_pid = -1;
    int status;
    pid = fork();

    if(pid < 0)
    {
        perror("fork ");
        exit(1);
    }
    else if( pid == 0)
    {
          printf("I am child\n");

    }
    else if(pid > 0)
    {
        printf("I am parent\n");
        printf("child pid = %d \n",pid);
        child_pid = wait(&status);
        printf("wait pid = %d\n",child_pid);
        //测试子进程返回状态
        printf("Exit Status = %d\n",WIFEXITED(status));
    }
    return 0;
}

上面代码我给出了,wait()函数的用法,以及status的基本用法,其他几个宏的用法类似,这里没有给出其他几个用法。


对于waitpid()系统调用,

与wait函数的区别就是waitpid用来等待某个特定进程的结束
函数原型:
    pid_t waitpid(pid_t pid, int *status, int options);
参数:
    status如果不为空,会把状态信息写到它指向的位置
    options允许改变waitpid的行为,最有用的一个选项是WNOHANG,它的作用是防止waitpid把调用者的执行挂起
返回值:成功返回等待子进程的pid,失败返回-1


    以上就是关于进程操作的基本函数,后边将继续总结,通过综合的案例来综合使用这几个API。


以上是关于Linux系统编程之进程的主要内容,如果未能解决你的问题,请参考以下文章

Linux 编程之进程篇:task_struct进程创建和退出

Linux 编程之进程篇:task_struct进程创建和退出

LINUX系统编程之线程

Linux编程之《守护进程》

Linux系统编程之进程

Linux系统编程之进程概念