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。

原文地址:http://blog.51cto.com/967243153/2071310

时间: 2024-11-07 13:03:18

Linux系统编程之进程的相关文章

Linux系统编程——Daemon进程

目录 Daemon进程介绍 前提知识 Daemon进程的编程规则 Daemon进程介绍 Daemon运行在后台也称作"后台服务进程". 它是没有控制终端与之相连的进程.它独立与控制终端.会话周期的执行某种任务. 那么为什么守护进程要脱离终端后台运行呢? 守护进程脱离终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的任何终端信息所打断. 那么为什么要引入守护进程呢? 由于在linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程

Linux系统编程——特殊进程之僵尸进程

僵尸进程(Zombie Process) 进程已执行结束,但进程的占用的资源未被回收.这种进程称为僵尸进程. 在每一个进程退出的时候,内核释放该进程全部的资源.包含打开的文件.占用的内存等. 可是仍然为其保留一定的信息,这些信息主要主要指进程控制块的信息(包含进程号.退出状态.执行时间等).直到父进程通过 wait() 或 waitpid() 来获取其状态并释放(详细使用方法,请看<等待进程结束>). 这样就会导致一个问题,假设进程不调用wait() 或 waitpid() 的话, 那么保留的

Linux系统编程——特殊进程之守护进程

什么是守护进程? 守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件. 守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示.由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都

linux系统编程之进程(七):system()函数使用【转】

本文转载自:http://www.cnblogs.com/mickole/p/3187974.html 一,system()理解 功能:system()函数调用“/bin/sh -c command”执行特定的命令,阻塞当前进程直到command命令执行完毕 原型: int system(const char *command); 返回值: 如果无法启动shell运行命令,system将返回127:出现不能执行system调用的其他错误时返回-1.如果system能够顺利执行,返回那个命令的退出

Linux系统编程_6_进程环境(C程序典型的存储空间)

1.八种结束Linux进程的方法: 五种正常终止方式: main函数返回: 调用exit: 调用_exit或_Exit 最后一个线程从其启动例程返回 最后一个线程调用pthread_exit 三种异常终止方式: 调用abort. 接收到一个信号终止: 最后一个线程对取消请求做出响应. exit函数与_exit  _Exit函数的差别是exit函数在结束进程之前会调用各种终止处理程序,关闭全部IO流,这会造成全部缓冲中的数据被冲洗(写到磁盘文件): atexit(void (*fun)void)函

Linux系统编程_8_进程控制之fork_wait_waitpid函数

fork函数: #include <unistd.h> pid_t fork(void); fork用来创建一个子进程: 特点: fork调用后会返回两次,子进程返回0,父进程返回子进程的进程ID:fork返回后,子进程和父进程都从fork函数的下一条语句開始运行: 注意: fork之后,操作系统会复制一个与父进程全然同样的子进程,虽说是父子关系.可是在操作系统看来,他们更像兄弟关系,这两个进程共享代码空间,可是数据空间是互相独立的,子进程数据空间中的内容是父进程的完整拷贝.指令指针也全然同样

Linux系统编程_9_进程控制之exec 函数

exec函数 当进程调用exec函数时,该进程的执行程序完全的替换为新程序.新程序从它的main函数开始执行: 使用fork函数创建一个子进程后,子进程往往会使用exec函数去执行另一个程序. 注意:调用exec函数并不会创建新进程,所以创建前后的进程ID不会改变,exec只是用一个全新的程序替换了当前正在运行的程序的代码段.数据段.堆.栈. #include <unistd.h> extern char **environ; int execl(const char *path, const

Linux系统编程_7_进程环境之setjmp和longjmp函数

大家都知道C语言中goto关键字可以用来跳转,但你知道它的跳转范围是什么吗? goto语句只能在当前函数内不跳转,不能实现跨函数跳转: 为实现这一目的,Linux中引入了setjmp和longjmp,这两个函数对于处理发生深层嵌套函数调用中的出错情况非常有用. 函数声明: #include <setjmp.h> int setjmp(jmp_buf env); //env是jmp_buf类型,一般定义为全局变量 void longjmp(jmp_buf env, int val);   //v

linux系统编程视频 百度网盘下载

本帖最后由 雇佣兵333 于 2015-5-19 16:15 编辑 c教程目录: Linux开发快速入门培训gcc快速入门Makefile快速入门GDB快速入门 Linux系统编程之文件篇01认识计算机系统(上)计算机系统组成计算机系统硬件组成总线I/O设备内存处理器(运算器+控制器) 02认识计算机系统(下)什么是操作系统Linux内核模块操作系统管理硬件操作系统三个基本抽象进程上下文切换进程虚地址空间虚拟文件系统(VFS) 03系统编程介绍系统编程与应用编程系统资源系统调用系统调用与C库关系