Linux学习(十八):进程概念及创建

来源:互联网 发布:注册淘宝号 编辑:程序博客网 时间:2024/05/22 01:51

1 进程概念

进程是一个独立的可调度的任务,进程是一个抽象实体。当系统在执行某个程序时,分配和释放的各种资源。进程是一个程序的一次执行的过程。

        程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念进程是一个动态的概念,它是程序执行的过程,包括创建、调度和消亡。

进程不仅包括程序的指令和数据,而且包括程序计数器值、CPU的所有寄存器值以及存储临时数据的进程堆栈。每一个进程都会有一个进程ID,也就是PID.

2、进程类型

交互进程:该类进程是由shell控制和运行的。交互进程既可以在前台运行,也可以在后台运行。
批处理进程:该类进程不属于某个终端,它被提交到一个队列中以便顺序执行。
守护进程:该类进程在后台运行。它一般在Linux启动时开始执行,系统关闭时才结束。

3、进程状态

运行态:此时进程或者正在运行,或者准备运行。
等待态:此时进程在等待一个事件的发生或某种系统资源。
停止态:此时进程被中止。
死亡态:这是一个已终止的进程,但还在进程向量数组中占有一个task_struct结构。
僵尸态:进程已中止,但是资源没有被回收

        进程状态可以通过ps命令进程查看,可参考Linux学习(五)shell命令用户相关命令

4 进程的内存结构

Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的地址空间。该地址空间是大小为4GB的线性虚拟空间。4GB的地址空间被分为两个部分,用户空间和内核空间,0-3GB是用户空间,3-4GB为内核空间。用户进程在通常情况下只能访问用户空间的虚拟地址,不能访问内核空间的虚拟地址,只有用户进行系统调用时才会访问到内核空间。

每当进程切换时,用户空间就会跟着变化,但内核空间由内核负责映射,它并不会跟着进程改变,是固定的。


5、进程系统调用

5.1 进程创建fork

fork()函数用于从已存在的进程中创建一个新的进程。新的进程称为子进程,原进程为父进程。子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等等,而子进程所独有的只有它的进程号、资源使用和计时器等。
由于子进程几乎是父进程的完全复制,所以父子进程会运行同一个程序,那两者如何区别呢?就是根据fork()函数的返回值。父进程返回的是子进程的进程号,而子进程返回的是0。
另外要注意的是,子进程是从fork()函数的下一句开始执行的。

#include <stdio.h>#include <unistd.h>#if 0pid_t fork(void);功能:以复制父进程的方式创建一个子进程返回:成功:父进程返回子进程PID,子进程返回0  失败:父进程返回-1,子进程不存在#endifint main(int argc, const char *argv[]){printf("hello\n"); //是否有"/n"程序执行效果不一样pid_t pid = fork();if(pid == -1){perror("fork error");return -1;}else if(pid == 0){//子进程 printf("child\n");}else{//父进程 printf("parent\n");}while(1);return 0;}
第13行是否带“\n”会有不同的执行结果
带"\n"的执行结果

不带"\n"的执行结果:
产生这种结果的原因是,标准输出stdout是行缓冲,只有出现换行符时才会真正显示。子进程所独有的只有它的进程号、资源使用和计时器等,缓存也是公用的,所以在打印child时就会也打印出hello。

5.2 僵尸进程

        当前进程运行结束,但其资源没有被回收,这样的进程就是僵尸进程,我们在编程时应避免出现这种情况。什么情况下会出现僵尸态呢,也就是在子进程已经结束,父进程也没有退出,也没有使用wait函数(5.5 wait函数)对资源进行回收。
僵尸态例程
#include <stdio.h>#include <unistd.h>#include <sys/types.h>  //getpid#if 0pid_t fork(void);pid_t getpid(void); pid_t getppid(void);孤儿进程:当前进程的父进程提前结束,此进程被称为孤儿进程。孤儿进程会被1号进程收养,并最终由1号进程回收资源  不论孤儿进程处于R T S Z都可被1号进程收养且回收。僵尸进程:当前进程运行结束,但其资源没有被回收产生僵尸:子退出 父进程不使用wait  父进程不能退出#endifint main(int argc, const char *argv[]){pid_t pid = fork();if(pid == -1){perror("fork error");return -1;}else if(pid == 0){//子进程 sleep(4);printf("childPID  = %d\n", getpid());}else{//父进程printf("parentPID = %d\n", getpid());while(1);}return 0;}

程序执行结果

利用ps -ajx查看进程,此时的子进程就处于僵尸态。

5.3 exec族

exec函数族提供了一种在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段。在执行完之后,原调用进程的内容除了进程号外,其他全部都被替换了。
可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。
可执行文件查找方式
表中的前四个函数的查找方式都是指定完整的文件目录路径,而最后两个函数(以p结尾的函数)可以只给出文件名,系统会自动从环境变量“$PATH”所包含的路径中进行查找。

参数表传递方式
两种方式:逐个列举或是将所有参数通过指针数组传递
以函数名的第五位字母来区分,字母为“l”(list)的表示逐个列举的方式;其类型为const char *arg;字母为“v”(vertor)的表示将所有参数构造成指针数组传递,其语法为char *const argv[];
这里的参数实际上就是用户在使用这个可执行文件时所需要的全部命令选项字符串,包括命令本身。特别需要注意的是,这些参数必须以NULL结尾。

环境变量的使用
exec函数族可以默认使用系统的环境变量,也可以传入指定的环境变量。这里,以“e”(Enviromen)结尾的两个函数execle、execve就可以在envp[]中传递当前进程所使用的环境变量。
实际上,这六个函数中只有execve()是真正的系统调用,其余5个都是库函数,他们最终都会调用execve()这个系统调用(还记得系统调用和库函数的区别吗,可以看这里Linux学习(十六):文件IO)
#include<unistd.h>#include<stdio.h>#include<stdlib.h>#include<sys/types.h>int main(int argc, const char *argv[]) {     if(fork()==0)      {       if(execlp("ps","ps","-ef",NULL)<0)         {              printf("execlp err\n");          }      }     return 0;  }                                                    

首先使用fork()创建一个子进程,再在子进程中使用execlp()函数。得到的结果和ps -ef是一个效果。

5.4 进程退出

进程退出有两个函数,exit()和_exit(),两个函数都可以终止进程的执行,但是两者还是有区别的。
_exit()函数的作用最为简单:直接使进程终止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;
exit()函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序。
exit()函数在调用exit系统调用之前要检查文件的打开情况,把文件缓冲区中的内容写回文件,就是图中的"清理I/O缓冲"一项。 
在Linux标准函数库中,有一种被称为“缓冲IO”操作,其特征就是对应每一个打开的文件,在内存中都有一片缓
冲区,在Linux学习(十五):标准IO我们也接触过这种情况。使用标准函数库时,都是先写入到缓冲当中,满足一定
条件后再写入文件当中。exit()和_exit()区别就在于此,exit()会在进程终止前将缓冲区中的数据清理,也就是把缓冲区
中的数据写入到相应文件中,然后再调用_exit()结束进程。看下面的例子能刚好理解


运行结果:

标准输出流stdout是行缓冲,只有遇到换行符\n时才会实际写入到终端中去。所以在父进程中,直接使用_exit()退出进程就无法将“parent printf@@@@@@@@@@@”显示到终端上来。

5.5 wait函数

wait函数

  调用该函数使进程阻塞,直到任一个子进程结束或者是该进程接收到了一个信号为止。如果该进程没有子进程或者其子进程已经结束,wait函数会立即返回。

waitpid函数
功能和wait函数类似。可以指定等待某个子进程结束以及等待的方式(阻塞或非阻塞)



例程:父进程中创建一个子进程,子进程5s后退出,父进程每隔1s判断一下子进程是否退出。

#include<unistd.h>#include<stdlib.h>#include<stdio.h>#include<sys/types.h>#include<sys/wait.h>int main(int argc, const char *argv[]){int ret,pid;if((pid=fork())<0){printf("fork err\n");}else if(pid ==0){sleep(5);exit(0);}else{do{ret = waitpid(pid,NULL,WNOHANG);if(ret==0){printf("child process is running\n");sleep(1);}}while(ret==0);if(ret==pid){printf("child process is exit\n");}}return 0;}

运行结果