进程:就是进行中的程序
程序:存放指令的程序文件,存放在磁盘上,固定不变的,保存着指令的有序集合。
程序执行过程:将程序从硬盘导入到内存,内存上分为代码区、静态变量区、堆栈区等等
文本区:储存处理器执行的代码
数据区:存储变量和动态分配的内存:全局变量,局部变量,静态变量,
堆栈区:存储着活动进程调用的指令和本地变量,指针变量->
栈区,动态分配的内存->
堆区
进程为程序执行提供各种各样的条件,程序需要各种各样操作,磁盘操作、网络操作等等。需要各种管理,这种动态的管理行为就可以通俗的称之为进程。所谓程序开始活动起来了。进程是linux
系统调度的最小单位
进程是程序的执行过程,根据他的生命周期可以分为3
种状态。
执行态:该进程正在运行,即进程正在使用CPU
就绪态:进程已经具备执行的一切条件,正在等待分配CPU
的处理
等待态:进程不能使用CPU
,若等待事件发生(等待的资源分配到),将其唤醒
process:是os
的最小单元,地址空间为4g
,其中1g
给os
,另外3g
给进程
ps:查看活动的进程
ps -aux:查看各个进程的状态,包括运行、就绪、等待等状态(S
睡眠 T
中断 R
运行 Z
僵尸)
ps -aux | grep `aa`:查看指定(aa
)进程
ps -ef:查看所有进程的pid
,ppid
等信息
ps -aux :查看cpu
内存的使用状态
进程不能用程序区分
因为不同进程可能执行的是同一个程序
所以,使用ID
号来标识区分不同的进程
OS
会为每个进程分配一个唯一的整型ID
,作为进程的标识号(pid
)
进程0
时调度进程,常被称为交换进程,他不执行任何程序,是内核的一部分,因此被称为系统进程。
进程除了自身ID
外,还有父进程ID
,每个进程都会有一个父进程,操作系统不会无缘无故产生一个新进程,所有的进程的祖先进程是同一个进程,叫做init
进程,进程号是1.init
进程是内核自举后的第一个启动进程。
init
进程负责引导系统、启动守护(后台)进程并且运行必要的程序。他不是系统进程,但它以系统的超级用户特权运行。
父进程是负责子进程空间的清理
并发:宏观上看起来,同时发生,cpu
轮转非常快,给人一种错觉,所有的事情在同时发生
并行:同时执行,微观上真的同时执行,多个cpu
同时执行不同的进程,多个进程真的在同时执行。
同步:相同的步伐,进程间相互联系,或者共同完成某件事,需要相互协调
异步:不同的步伐,进程间毫无关联
例子:c
语言只能让一个任务接一个任务的执行,单片机普通情况也下一样,也是一个接一个的执行任务,除非中断发生,单片机会先中断当前任务的执行,去执行中断需要做的事情,中断执行完毕再回来执行刚才中断的任务。可以拿人来类比,当一个人炒菜的时候,快递打电话来了,让去取快递,不能让送快递的人一直等,只能先不炒菜,去取快递,取完快递再回来继续炒菜。这样实际把炒菜的任务就给耽误了。
但linux
中,一个人在炒菜,快递打电话来了,让去取快递,他可以叫他的儿子去取快递,自己继续炒菜。从cpu
角度想,由于cpu
执行速度较快,看起来任务在同时进行(并发执行),这样所有的事情都不耽误。这就是进程的意义,在某个层面上也能说明父进程子进程的意义。
再举一个例子:
ATM
机取款时,一方面播放提示音乐,一方面提示密码输入,不带系统的单片机处理起来就很恶心,不能先播音乐,在此期间让用户等待输入,或者播放音乐同时用中断监测用户输入,用户输入会造成音乐的停止。用户体验很差,反过来程序用进程来写效果就很好,子进程播放提示音乐,父进程检测用户输入,父子进程并发执行,可以产生同时运行的效果。提高了用户体验。
说白了进程的意义就是:让任务并发执行
特殊说明:现在说的进程仅仅是linux
下的进程
ID
号 PID
PPID
ID
ID
#include
#include
pid_t getpid(void);
pid_t getpid(void);
功能:获取自己的进程ID
号
参数:无
返回值:本进程的ID
号
获取自己的父进程的ID
#include
#include
pid_t getppid(void);
pid_t getpid(void);
功能:获取自己的父进程ID
号
参数:无
返回值:本进程的父进程的ID
号
创建一个进程
#include
pid_t fork(void);
功能:创建一个子进程
参数:无
返回值:有两次,子进程返回0
,父进程返回子进程的进程号。
注意:子进程从fork
函数之后执行,fork
完全拷贝了父进程的地址空间给子进程,父子进程运行顺序不确定。
虚拟创建进程
#include
#include
pid_t vfork(void);
功能:创建一个子进程
参数:无
返回值:有两次,子进程返回0
,父进程返回子进程的进程号。
注意:子进程从fork
函数之后执行,子进程有自己的ID
与父进程共享资源,子进程先执行,父进程后执行。价值并不大,不能实现任务并发执行
错误打印函数
#include
void perror(const char *s);
功能:打印错误信息(某些函数返回负值,表明发生错误,但是不知道具体类型,使用这个函数可以获得具体错误类型)
进程的常见的终止方式有5
种:
主动:
main
函数的自然返回,注意:return
不是结束,只是函数结束,当它刚好结束的是main
函数,此时导致进程结束。造成return
结束进程的错觉。exit
函数 ,标准函数_exit
函数 ,系统调用函数abort
函数,产生SIGABRT
信号被动:
ctrl+c
,SIGINT
,ctrl+\ SIGOUT
kill
向进程发信号 atexit
函数的执行。exit
和__exit
函数最大的区别在于exit
函数退出之前会检查文件的打开情况,把文件缓冲区的内容写回文件,而__exit
直接退出,什么意思?比如打开文件向文件写入内容,如果在文件没有关闭,也没有调用同步到磁盘的函数,文件并没有同步到磁盘,只存在缓冲区内,这时调用exit
,那么进程结束时,缓冲区的内容可以同步到文件中,内容已经存在在文件之中了,调用__exit
进程直接结束,文件不会有写入的内容。#include
int system(const char *command);
功能:打开命令或者程序
参数:带路径的程序启动文件,或者在启动变量里声明的程序直接写程序名
返回值:-1
失败
打开的程序是另一个进程,也可以成为此程序的子进程,因此子进程不一定和父进程视同一个程序,在成功打开所要执行的文件之后,父进程才能继续执行。
父进程程序
#include
#include
int main(void)
{
printf("调用子进程\n");
system("./son");
printf("调用子进程结束\n");
return 0;
}
子进程程序
#include
#include
int main(void)
{
printf("这是子进程在执行\n");
sleep(20);
}
程序运行效果
[root@CentOS workdir]# gcc son.c -o son
[root@CentOS workdir]# gcc sys.c -o app
[root@CentOS workdir]# ./app
调用子进程
这是子进程在执行
调用子进程结束
进程调度结果,son父进程指向app
root 39055 35664 0 19:28 pts/0 00:00:00 ./app
root 39056 39055 0 19:28 pts/0 00:00:00 ./son
#include
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
重点学习四种:
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
这四个函数第一个参数都是可执行程序或者脚本的程序名,
execl
、execv
需要带有完整的路径,第二参数为任意字符,起到占位作用,第三个或者后面的字符为调用者的参数,参数列表最后以NULL
结尾,而execlp
、execvp
不需要只需要带有可执行程序的名称即可,系统自动去环境变量去寻找同名文件,execl
、execlp
需要NULL
结尾.
函数后缀说明:
l
v
:参数呈现形式
l:list
参数一个个的列出来
v:vector
参数用数组存储起来
p:目标程序,可以省略路径
e:环境变量,不考虑
将system
程序稍作修改
父进程程序
#include
#include
#include
int main(void)
{
printf("当前进程id:%d\n",getpid());
printf("调用son进程\n");
execl("./son",NULL);
printf("调用子进程结束\n");
return 0;
}
子进程程序
#include
#include
int main(void)
{
printf("这是子进程在执行\n");
printf("son进程id:%d\n",getpid());
sleep(5);
}
程序运行效果,不会打印调用子进程结束,父子进程ID号相同,表明当前进程被son进程替换
[root@CentOS workdir]# gcc sys.c
[root@CentOS workdir]# gcc son.c -o son
[root@CentOS workdir]# ./a.out
当前进程id:39606
调用son进程
这是子进程在执行
son进程id:39606
[root@CentOS workdir]#
注意:当时用参数list
的函数时,最后一个参数赋值NULL
,表明没有参数,否则运行会出错。
#include
#include
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
pid_t wait(int *status);
函数返回值为结束子进程的进程号,当前进程中没有子进程则返回-1
,参数为子进程结束状态指针,如果单纯想等待子进程的结束而不关心进程结束状态,参数写入NULL
即可;若想获得子进程结束状态,将参数地址写入即可,例如定义int statue
存储子进程的结束状态,函数调用wait(&statue)
即可;
一旦函数调用wait()
,就会立即阻塞自己并自动判断当前进程中是否有某个子进程退出,wait()
返回该子进程的状态,终止子进程的ID
,所以它总能了解是哪一个子进程终止了。
pid_t waitpid(pid_t pid, int *status, int options);
pid:从参数的名字pid
和类型pid_t
中就可以看出,这里需要的是一个进程ID
。但当pid
取不同的值时,在这里有不同的意义。
pid>0时,只等待进程ID
等于pid
的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid
就会一直等下去。
pid==-1时,等待任何一个子进程退出,没有任何限制,此时waitpid
和wait
的作用一模一样。
pid==0时,等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid
不会对它做任何理睬。
pid<-1时,等待一个指定进程组中的任何子进程,这个进程组的ID
等于pid
的绝对值。
第二个参数与wait
相同,存储制定子进程终止的状态信息。为整形指针类型。
options:options
提供了一些额外的选项来控制waitpid
,目前在Linux
中只支持WNOHANG
和WUNTRACED
两个选项,这是两个常数,可以用”|”运算符把它们连接起来使用。
WNOHANG:若pid
指定进程并不是立即可用(终止状态),waitpid
不阻塞,返回值为0.
WUNTRACED:若实现支持作业控制··· ···涉及到一些跟踪调试方面的知识,加之极少用到,这里就不多说了。
稍微琢磨一下就能看出,wait
与waitpid
有很大的关联性甚至wait
就是封装了wairpid
函数,没错!实际就是这样
察看/include/unistd.h文件349-352
行就会发现以下程序段:
static inline pid_t wait(int * wait_stat)
{
return waitpid(-1,wait_stat,0);
}
返回值和错误
waitpid
的返回值比wait
稍微复杂一些,一共有3种情况:
waitpid
返回收集到的子进程的进程ID
;WNOHANG
,而调用中waitpid
发现没有已退出的子进程可收集,则返回0
;-1
,这时errno
会被设置成相应的值以指示错误所在;当pid
所指示的子进程不存在,或此进程存在,但不是调用进程的子进程,waitpid
就会出错返回,这时errno
被设置为ECHILD
;