进程入门篇
首先,先问一个问题,程序是什么?
- 假如是非计算机专业的,可能会回答我,我们平时使用的软件,比如QQ,微信,微博啊,都是程序啊。
- 假如是学过刚学C语言,可能会回答我,“hello world!”就是一个程序啊。
其实程序是一些保存在硬盘上的可执行代码,是静态的。你每次下载软件不是都要下载吗。其实下载就是将这个程序的可执行代码刻录到本地的磁盘上去,每个程序的大小就是可执行文件的大小。那他是怎么运行的?
- 当你执行这个程序的时候,操作系统就将你程序的可执行程序由硬盘复制到内存当中。然后腻?进程就出场了。
- 进程是一个动态的概念,它是静态的可执行文件执行过程的描述,其包含了一个静态程序运行时的状态和其所占据的系统资源的总和。在这样的描述中,也很容易理解在某一个时刻,某个CPU的核心上只运行着某一个进程的既定事实。 这样就OK了吗?怎么可能,还有线程没说呢,那都可以执行了,为什么还要有线程呢,开始的时候确实是没有线程的,但随着计算机硬件技术的快速发展,进程就有点大,占内存有点多,在多进程情况下,每个进程都有自己独立的地址空间。在通信机制方面,进程间的数据空间相互独立,彼此通信要以专门的通信方式,通信时必须经过操作系统。主要的是,CPU单核的频率做到了极致,最后想要提升运行速度,人们想出来的办法是多核CPU,然后,对计算机程序就有了更新的要求,那就是并行的运算和数据处理。 说这么多,这和线程有什么关系?
- 线程就是一个用来解决一个程序并行处理数据的解决方案。不过必须说明的是,线程有时候也是用来简化程序逻辑等目的。
进程是操作系统资源分配的基本单位,线程是CPU调度的基本单位。 说了这么多,这句话的意思你应该基本明白了吧,同一个进程内的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,而不必经过操作系统。因此,线程间的通信也更加省时方便。而且在系统调度方面,线程间的切换速度要远快过进程间的切换速度。线程的创建花费的时间也要少得多。
有了基本的理解,接下来我们仔细聊聊。
进程
在Linux进程控制中,用于对进程进行控制的主要系统调用:
- 获取进程ID
- Linux操作系统。每个进程都是通过唯一的进程ID标识的。
pid_t getpid(void);
这是头文件中的定义,getpid()
用来返回当前进程的ID。
-
创建一个新线程
pid_t fork(void);
函数是pid_t
类型,说明返回值是pid_t 类型,值得一说的是这个函数的返回值有两个 ,即调用一次返回两次。成功调用fork()
,当前进程实际上已经分裂为两个进程,一个是原来的父进程,另一个是刚刚的创建的父进程。父子进程在刚刚fork()
函数的地方分开,fork()
函数有两个返回值,一个是父进程调用fork()函数后的返回值,该返回值是刚刚创建的子进程的ID;另一个是子进程中fork()的返回值,该返回值是0;一般来说,fork之后是父进程先执行还是子进程先执行是不确定的,这取决于内核的调度算法。pid_t vfork(void);
vfork和fork都是调用一次返回两次。使用fork创建一个子进程,会完全复制父进程的资源,这样的子进程与父进程就是两个完全独立的进程,相互不会影响。而vfork的子进程与父进程共享地址空间,所以子进程对数据的任何update都对父进程可见。使用vfork创建进程时,保证子进程先运行。使用fork()
是一个很大的系统开销。比如exec
一个子进程,那么复制父进程的地址空间将是一个多余的操作。
-
终止线程
进程退出表示进程即将结束运行,在Linux系统中进程退出的方法分为正常退出和异常退出。- 正常退出
在main()函数中执行return
调用exit函数
调用_exit函数 - 异常退出
调用about函数
进场收到某个信号,而该信号是程序终止 - 不管是那种退出方式,最终都会执行内核中的一段代码。该段代码用来关闭进程所有已打开的文件描述符,释放它所占用的内存和其他资源。以下是各种退出方式的比较。
- exit和retrun的区别:exit是一个函数,有参数;而return是函数执行完的返回。exit把控制权交给系统。而return把控制权交给调用函数。
- exit() 和_exit():exit()在文件stdlib.h中声明,而_exit()在头文件unistd.h中。两个函数均能正常终止进程,但是_exit()会执行后立即返回给内核,而exit()要先执行一些清楚操作,然后返回给内核。
- 正常退出
-
执行一个应用程序
系统调用exec用于执行一个可执行文件以代替当前进程的执行映像。执行新程序的进程保持了原有的进程ID,父进程ID,实际用户ID。 -
将父进程挂起,等待子进程终止
当子进程先于父进程退出时,如果父进程没有调用wait()和waitpid()函数,子进程就会进入僵死状态。如果调用了wait()和waitpid()函数,就不会变成僵尸进程。
pid_t wait(int *statloc);
pid_t wairpid(pid_t pid,int *statloc,int options);
既然说到僵尸进程了,咋们就说说孤儿进程吧。
如果一个子进程的父进程先于子进程结束,子进程就成为一个孤儿进程,它由init进程收养,成为init进程的子进程。//进程的创建方式
进程的内存执行映像
代码段:即二进制机器代码,代码段是可读的,可被多个进程共享。如一个进程创建了一个子进程,父子进程共享代码段,此外子进程还获得父进程数据段,堆,栈的复制。
数据段:存储已被初始化的变量,包括全局变量和已被初始化的静态变量。
未初始化数据段:存储未被初始化的静态变量,bss段。
堆:用于存放程序运行中动态分配的变量。
栈:用于函数调用,保存函数的返回地址,函数的参数,函数内部定义的局部变量。
好,今天就写到这吧。下一篇我们来聊聊线程。