引入:
今天开始学习linux应用层开发的必备知识,进程。我们之前的实时系统中,都是任务的概念,linux中也有任务概念,同时还有进程和线程,他们之间是什么关系呢?
其实在我们的windows中也有进程的概念,通过任务管理器就能查看到当前运行的进程。
开始学习之前先看下思维导图,把握这节的知识点,然后在进行详细的解读。
我们将上面的内容进行详细学习。
一.进程的基本知识
1.进程的概念
1>当我们打开windows时,说让你杀死一个进程,那什么是进程?
理解上,进行中的程序就是进程呗,程序的一次执行过程,是动态的(占用CPU和内存),包括创建,调度,执行和消亡。
2>那么和程序有啥区别?
很明显,程序是静态的,是存放在磁盘上的指令和数据的有序集合。
3>一个进程又包含哪些内容呢?
从我们的框图中也能清晰的看到,它包括进程控制块,也就是我们知道的PCB控制块,还要有cpu寄存器,堆栈,还有和程序中共有的部分,正文段,用户数据段,系统数据段。直接看框图更好理解
2.进程的特性:
1>并发性:多进程并发执行
2>动态性:进程状态不断变化
3>交互性:进程间交互,同步互斥等
4>独立性:地址空间相互独立
3.进程的类型
主要就是三大类型
1>交互式进程:典型的就是shell命令进程,可前台运行,也可以后台运行
2>批处理进程:与终端无关,作业队列中顺序执行,不需要很快响应,编译器的编译操作,数据库搜索等都属于批处理进程
3>守护进程:后台运行,系统进程都是以这种形式存在。
4.进程的状态:
运行态,等待态,停止态,死亡态。
二.进程的常见命令
我们对进程有了一点了解,那么我们还是要在终端上对进程进行一些操作,如查看一下状态啊等。
1.进程的查看,常用的几个命令
ps aux 查看所有进程信息,包括谁创建的,pid,cpu占用率,状态等
ps -elf
ps -elf|grep test 详细的查看某一个进程的信息
top 查看进程动态信息。
top |grep init 详细的查看某一个进程的动态信息
2.改变进程优先级
1> nice 按照用户指定的优先级运行(优先级范围-20--19)
例:
./test 然后通过ps -elf|grep test 查看test进程的信息,pid等。
nice -n 19 ./test 修改完后通过ps -elf|grep test 查看pid进程号,
然后top -p 10113(进程号),查看进程动态详细信息,发现进程优先级已经改成功
2>renice 运行态,改变正在运行的进程优先级
例:renice 10 10113 优先级就改变了。
3.进程的前后台操作
jobs查看后台几个进程
bg 将挂起的进程运行
fg 1 将后台进程放到前台运行
ctrl+z 前台变为后台,并停止。
三.进程的创建和结束及回收
1.进程创建
pid_t fork(void); 一个fork会有两个返回值,通过不同的返回值得到父子进程,是个岔子函数。
失败:-1 父进程:返回子进程进程号 子进程:返回0
int main(){
pid_t pid;
int i;
pid = fork();
if(pid <0){
perror("fork");
return 0;
}else if(pid==0){
printf("son\n");
}else{
printf("father\n");
}
while(1) sleep(1);
}
2.进程的结束
void exit(int status); 结束时会刷新缓冲区。
void _exit(int status); 结束当前进程并将status返回
3.父子进程关系
1> 子进程继承父进程内容
2> 父子进程有独立空间,互不影响
3> 若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变为后台进程
4> 若子进程先结束,父进程没有及时回收,子进程变为僵尸进程。
5> 子进程是从fork后开始运行,不是从main开始
6> 父子进程谁先运行依赖于操作系统调度策略
7>父子进程可以多次创建
4.进程的回收
子进程结束时由父进程回收,若不及时回收就会出现僵尸进程。
孤儿进程由init 进程回收。
1> pid_t wait(int *status)
成功:子进程进程号 失败:EOF
子进程没有结束,父进程一直阻塞
status为NULL表示直接释放进程PCB,不接收返回值(不接收遗言)
2> pid_t waitpid(pid_t pid,int *status,int option)
指定回收哪一个子进程。option 指定回收方式
3>通过以下几个宏定义,可以判断子进程是什么原因死的
WIFEXITE(status)判断子进程是否正常结束
WEXITSTATUS(status)获取子进程返回值
WIFSIGNALED(status)判断子进程是否被信号结束
WTERMSIG(status)获取结束子进程的信号类型
int main(){
pid_t pid;
int i;
int status;
pid = fork();
if(pid <0){
perror("fork");
return 0;
}else if(pid==0){
printf("bbbbbb\n");
sleep(2);
exit(2);
}else{
printf("aaaaaa\n");
//wait(&status); //回收进程
sleep(4);
waitpid(pid,&status,WNOHANG); //回收指定进程
printf("st=%d\n", WEXITSTATUS(status));
}
while(1) sleep(1);
return 0;
}
四.exec函数族
为了实现父子进程执行不同的程序,exec函数族就是为了执行第三方程序。
以下几个函数都是为了执行第三方程序,只是操作方式不一样。
int execl(const char *path, const char *arg, …);
if(execl("/bin/ls", "ls", "-a", "-l", "./", NULL) < 0) {
perror("execl");
}
int execlp(const char *file, const char *arg, …);
if(execlp("ls", "ls", "-a", "-l", "./", NULL) < 0) {
perror("execl");
} //从path路径下查找文件
int execv(const char *path, char *const argv[]);
char *arg[] = {"ls", "-a", "-l", "./", NULL};
printf("aaaaaaaaaaah\n");
if (execv("/bin/ls", arg) < 0) {
perror("execv");
}
int execvp(const char *file, char *const argv[]);
char *arg[] = {"ls", "-a", "-l", "./", NULL};
printf("aaaaaaaaaaah\n");
if (execvp("ls", arg) < 0) {
perror("execv");
}
上面几个函数执行了第三方函数,但是自己的函数也不能丢啊,就有了system函数
int system(const char *command);
system("ls -l -a ./");
相当于把exec函数进行了封装,fork了一个进程,子进程exec执行操作,父进程阻塞等待子进程结束后,继续执行
不替换程序内容。
五.守护进程
作为三大进程之一,重要性自然不必多说,那么什么是守护进程,又如何创建呢??
1.守护进程特点
始终后台运行,独立于任何终端,周期性的执行某种任务或等待处理特定事件。
一般的进程关闭会话后,所有进程就会结束。
2.守护进程的创建过程
1>先与终端脱离关系,让子进程变为孤儿进程。
2>子进程创建一个新的会话,并成为新的会话组长。
3>守护进程一直在后天运行,其工作目录不能被卸载,重新设定当前目录(假如是临时目录,容易出现问题,所以更改目录,路径不限定)
4>改变文件掩码,防止设置权限时受影响
5>关闭所有从父进程继承的打开文件
最后查看编写的程序是否为后台程序,关闭终端后是否会影响进程。
只能用kill杀死进程。
创建一个守护进程,将系统时间保存到log文件中
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
pid_t pid;
pid = fork();
if(pid<0){
perror("fork");
return -1;
}else if(pid>0){
exit(0);
}
pid = setsid();
if(pid==-1){
perror("setsid");
return -1;
}
chdir("/");
umask(0);
int i;
for(i = 0;i<2;i++){
close(i);
}
FILE *fp;
time_t ctm;
fp = fopen("1.log","w");
while(1){
ctm = time(NULL);
fputs(ctime(&ctm),fp);
fflush(fp);
sleep(1);
}
}