进程
Linux是一个多用户多任务的操作系统。
- 1概念:进程,是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。它的执行需要系统分配资源创建实体之后,才能进行。
举个例子: 我们所写的程序,在运行的时候它也是一个进程。 - 2进程和程序的区别: 程序相当于是一个文件,是有序代码的集合,是静态的存储在硬盘中。而进程是运行中的程序,系统会为变量分配相应的内存和初始化它们的值,还有一些资源如:堆,栈等…当程序运行完进程也就结束了,但程序还是保存在硬盘中没有消失
进程的特性
(1)并发性:可以与其它进程在宏观上同时向前推进(一会在下面讲解和并行性的区别)。
(2)动态性:进程是执行中程序,动态产生、动态消亡、动态变化。
(3)独立性:进程是系统资源分配的基本单位。
(4)交往性:与其它进程可以进行交互。
(5)异步性:不统一推进。
(6)结构性:每个进程有一个控制块PCB。
(7)并行性:只有在多cpu多处理器的计算机上,进程才能并行执行。
- 我们谈谈并发性和并行性的区别:
并发性是在宏观上的同时向前推进,cpu是时间轮转机制进行对进程的调度,可能一个进程占用cpu短短10ms就切换去执行另一个进程了,而我们的肉眼是不可能看到这种细微的变化,所以在我们宏观概念上认为他是同时推进的,这就是并发性。(一定是在宏观上)
并行性就是真正意义上的一起推进,就是多个进程在同一时间被一起执行。但是上面也提到过,想要支持进程并行,必须是多cpu的计算机。
打个比方理解一下这它们的区别:
现在有一个人吃三个馒头,但是这个人可能比较有意思,他咬一口第一个馒头,迅速的又咬了一口第二个馒头,然后吃了一口第二个馒头可能又去咬了第三个馒头,就这样子他把三个馒头吃完了。这就是并发性。
但是这一边是三个人再吃三个馒头,三个人同时开始吃,然后吃完了三个馒头,这就是并行性。
讲完这些之后,我们再来谈谈在一个进程中如何创建多个进程,并且它们之间又有什么关系呢?
创建进程有两种方式:一种是操作系统创建,另一种是父进程创建。
父进程创建的进程(通常称为子进程),它们和父进程存在隶属关系。子进程又可以创建进程,这样形成一个进程家族。子进程可以继承其父进程几乎所有的资源。
父进程创建进程的函数fork: pid_t = fork(void);
pid_t getpid();//进程id
pid_t getppid();//父进程id
pid_t 当调用成功时有两个返回值,一个是父进程调用fork返回值是子进程的id, 第二个是子进程调用fork返回值0。如果创建失败则返回-1。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
//父进程和子进程执行顺序由内核算法调度。
int main(void)
{
pid_t pid;
printf("creat fork\n");
pid = fork();
switch(pid)
{
case 0:
printf("child process is running, Curpid is %d, Parentpid is %d\n", pid, getppid());
break;
case -1:
perror("Process creat fault");
break;
default:
printf("Parent process is running, Childpid is %d, Parentpid is %d\n", pid, getppid());
break;
}
}
孤儿进程
孤儿字面意思就是没有父母的孩子,那孤儿进程的意思就是没有父进程的进程。
这种情况是怎么发生的呢。就是父进程先于子进程结束,这时子进程被init进程收养,init的进程号为1。
僵尸进程
当一个子进程终止时, 如果它的父进程还在运行, 内核会为这个终止的子进程保留一定量的信息.
父进程可以根据这些信息知道子进程的情况. 直到父进程对其进行了善后处理, 子进程才会完全终止.
在这期间, 这个子进程会成为 僵死进程, 它仍然占用一定资源.
######防止僵尸进程但产生
父进程可以用 wait 或 waitpid 函数来获得子进程的终止状态.
#include <sys/wait.h>
pid_t wait(int *status); //参数status存放子进程的退出码
pid_t waitpid(pid_t pid, int *status, int options);
wait函数作用就是父进程调用,将父进程挂起,直到所等待的进程结束,然后在执行父进程。而如果有多个子进程的话,只要第一个子进程结束,wait就会接受到信号,并结束等待。
而waitpid就会指定一个进程号,父进程将会等待pid这个子进程结束才会执行。
进程终止
有八种方式使进程终止, 它们是:
- 从main函数返回 return .
- 调用 exit. (会清空缓冲区,关闭所有打开的流)
- 调用 _exit 或 _Exit (不清空缓冲区)
- 最后一个线程从其启动例程返回.
- 最后一个线程调用 pthread_exit.
- 调用 abort. 它产生 SIGABRT 信号.(异常终止 )
- 收到一个信号并终止.
- 最后一个线程对取消请求作出相应.
- return 和exit的区别:exit十一个函数,有参数;而return是函数执行完后的返回。exit把控制权交给系统,而return将控制交给函数调用。
- exit和abort的区别:exit是正常终止进程,而abort是异常退出
- exit和_exit的区别:exit在头文文件stdlib.h中声明,而 _exit在unistd.h中声明, _exit()函数的作用最为简单:直接使进程停止运行,清除其使用的内存空间,并销毁其在内核中的各种数据结构;exit() 函数则在这些基础上作了一些包装,在执行退出之前加了若干道工序,也是因为这个原因,有些人认为exit已经不能算是纯粹的系统调用。
exec函数族
(1)exec函数说明
fork函数是用于创建一个子进程,该子进程几乎是父进程的副本,而有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行脚本文件。
(2)在Linux中使用exec函数族主要有以下两种情况
a. 当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用任何exec 函数族让自己重生。
b. 如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用任何一个exec函数使子进程重生。
(3)exec函数族语法
实际上,在Linux中并没有exec函数,而是有6个以exec开头的函数族,下表列举了exec函数族的6个成员函数的语法。
int execl(const char *path, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, ..., char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[])
/*
函数中带l的意思是,命令行参数argv要一个一个详细的传进去,
带p就是第一个参数不用传路径而是传一个文件名,并且函数会自动在当前目录和$PATH指定的目录下进行查找
带v的就是要传一个命令行中的argv参数
带e的就是传环境变量参数。
*/
进程组
每个进程都属于某个进程组.
进程组是一个或多个进程的集合. 通常它们与同一作业相关联, 可以接收来自同一终端的各种信号.
每个进程组有一个唯一的进程组ID.
#include <unistd.h>
pid_t getpgrp();//当前进程的进程组id
pid_t getpgid(pid_t pid);//根据pid返回对应的进程组id.
每个进程组都有一个组长进程. 进程id等于进程组id的进程就是组长进程.
会话
会话(session) 是一个或多个进程组的集合.
竞态条件
当多个进程都企图对共享数据进行某种处理时,而最后的结果又取决于进程运行的顺序时,我们认为发生了竞态条件(race condition)。如果fork之后的某种逻辑显式或隐式地依赖于在fork之后是父进程先运行还是子进程先运行,那么fork函数就会是竞争条件活跃的滋生地。通常,我们都是不可以预估子进程和父进程哪个先运行。因为进程的运行顺序完全取决于内核的调度算法和系统的负载。
我们可以调用wait或者waitpid函数来让父进程等待子进程运行完之后在运行,这样父进程就会被阻塞住,直到子进程运行完毕之后才能运行,其实这样做的效率就比较低,我们fork就是希望多进程并发的执行,如果采用此方法,则fork也就失去了很大的意义。
为了避免竞争条件和不采用wait或waitpid方法,我们可以在多进程之间采用信号发送和接收的处理方法来避免竞态条件。或者各种进程间的通信方式(IPC)也可以使用。