前一段时间对文件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