进程的相关基本概念:
进程是一个独立的可调度的任务
进程是一个抽象实体。当系统在执行某个程序时,分配和释放各种资源。
操作系统特点:
多任务、多用户、分时性
多任务的操作系统分为:抢占式、非抢占式
抢占式:多任务多用户的操作系统具有绝对的控制权来控制每个任务可以使用CPU的时间。
进程和程序的区别:
程序是静态的,它是一些保存在磁盘上的指令的有序集合,没有任何执行的概念
进程是一个动态的概念,它是程序执行的过程,包括创建(通过函数接口创建)、调度(用户不参与)、执行(CPU正在读程序的内存空间)、消亡(释放内存)。
进程是程序执行和资源管理的最小单位
进程包含的资源:代码段(.text)、数据段(.data .bss .radata)、堆、栈、寄存器(pc)
注:代码段存放的内容告诉cpu一系列的动作
分类:
IO消耗型:某一类任务需要从磁盘或者是通过鼠标来进行输入输出操作
需要时常分配时间片,每次分配的时间片都比较短
处理器消耗型:通常情况下指的是维持系统正常运行的后台任务
不需要时常分配时间片,每次分配的时间片都比较长
linux系统中的进程的类型:
交互进程:和终端相关,可以前台可以后台。最大的生命周期是从执行开始到终端关闭
前台进程可以输入,如果想要杀后台进程不能使用kill -2 进程号(kill -2 相当于ctrl c),可以使用信号9(kill -9)。
守护进程:和终端不相关,一定是后台进程,最大生命周期是从执行开始到系统关闭。
pts/1:虚拟终端(跟终端相关,printf打印在终端上,若为?则不相关)
注:第一行中左边的13428是进程本身的PID,1是进程的父进程PID
查看进程状态、;ps -aux或者是ps -axj
进程三态
进程因创建而就绪,因调度而执行;因时间片用完而重新就绪;
执行中因I/O请求而阻塞;
I/O完成而就绪
注意:阻塞以后不能直接执行,必须进入就绪状态。
linux进程的运行状态:
运行状态(TASK_RUNNING)(R)
指正在被CPU运行或者就绪的状态。这样的进程被成为runnning进程。运行态的进程可以分为3种情况:内核运行态、用户运行态、就绪态。
可中断睡眠状态(TASK_INTERRUPTIBLE)(S)
处于等待状态中的进程,一旦被该进程等待的资源被释放,那么该进程就会进入运行状态。
不可中断睡眠状态(TASK_UNINTERRUPTIBLE)(D)(应用层基本不用,在底层用于保护机制)
该状态的进程只能用wake_up()函数唤醒。
暂停状态(TASK_STOPPED)(T)
当进程收到信号SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU时就会进入暂停状态。可向其发送SIGCONT信号让进程转换到可运行状态。
注:暂停状态一定是后台。
僵死状态(TASK_ZOMBIE)(Z)(子进程退出,父进程没有回收资源,资源是使用内核中的task_struct描述)(尽量避免)
当进程已经终止运行,但是父进程还没有询问其状态的情况。
孤儿进程:父进程退出,子进程没有退出但是会被init收养,会由交互进程变成守护进程
注:
状态号:
<:代表高优先级
N:低优先级
s:会话组长
注:会话指一个或多个进程组,进程组,包含一个或多个进程
+:前台进程
l:多线程
NI:优先级(-20 ~ 19)-20最高 19最低
暂停进程 kill -19 进程号
使暂停进程继续 kill -18 进程号
fg:将刮起的进程在后台执行
bg:把后台运行的进程放到前台运行
进程的编程相关:
进程创建
pid_t fork(void);
返回值:
创建子进程1的fork将0返回给子进程,将创建子进程的PID返回给父进程。
出错返回-1.
实践1——了解fork函数的处理机制
程序:
#include
int main(int argc, const char *argv[])
{
fork();
fork();
printf("hello!\n");
return 0;
}
输出结果:
leo_walker@leo-walker:~/linux_app/process_1 ./a.outhello!leowalker@leo−walker: /linuxapp/process1 hello!
hello!
hello!
总结:子进程从它被创建的fork后一句代码开始运行,执行顺序是随机的。
实践2——证明数据段和栈区的继承
程序:
#include
#include
int x=100;
int main(int argc, const char *argv[])
{
pid_t pid;
int c=100;
if((pid = fork())==-1)
{
perror("fail of fork");
return -1;
}
else if(pid == 0)//子进程
{
c++;
x++;
printf("child c:%d\n",c);
printf("child x:%d\n",x);
printf("child c:%p\n",&c);
}
else//父进程
{
c++;
x++;
printf("parents c:%d\n",c);
printf("parents x:%d\n",x);
printf("parents c:%p\n",&c);
}
return 0;
}
结果:
parents c:101
parents x:101
parents c:0x7ffcc113ca08
leo_walker@leo-walker:~/linux_app/process_1$ child c:101
child x:101
child c:0x7ffcc113ca08
注:父子进程共享代码段
数据区、堆、栈可能是继承的,也可能是独立的。具体要看代码所处于的位置,若处于父子进程的判断方式内部,则是独立的,若是在其外部则是继承的。
继承(复制)出来的变量地址为什么一样?
打印出来的地址是虚拟电子,物理地址实际上是不一样的,虚拟地址一共32位,前10位是作为下标来用的,中间10位下标找到页,剩余的12位做偏移,得到的就是物理地址空间.
实践3——显示父进程和子进程的fork的返回值、本身的进程号和其父进
程序:
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
pid=fork();
if(pid==-1)
{
}
else if(pid==0)
{
printf("child pid:%d PID:%d PPID:%d\n",pid,getpid(),getppid());
}
else
{
printf("father pid:%d PID:%d PPID:%d\n",pid,getpid(),getppid());
}
while(1);
return 0;
}
结果:
father pid:12175 PID:12174 PPID:2941
child pid:0 PID:12175 PPID:12174
注:getpid()获得当前进程进程号(不要描述为获得子进程进程号)
getppid()获得当前进程的父进程号
exec函数族:
exec函数族可以节省内存空间。
exec函数族要以空结尾。
主要搞懂下面这两个函数就可以,殊途同归;
execl:可调用其他可执行空间,调用的可执行程序会替换掉之前进程的进空间
execv:与execl其实用法是一样的
因为使用exec函数族会使调用函数之后的空间被清空,所以若是想继续运行之后的代码,可以使用一下函数来代替exec函数族来实现。
system(“ls -l 1.c”);
执行ls -l命令,再接上1.c
exit函数和_exit函数都可以提前退出进程,exit()退出时刷新缓存区,_exit()退出时不刷新缓存区。
return与exit的区别在return只能结束一个函数,而exit是结束一个进程。
僵尸进程的产生
如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕捉到子进程的退出状态才能真正结束,否则这个子进程就会变成僵尸进程。
实践4——僵尸进程的产生
程序:
#include
#include
#include
int main(int argc, const char *argv[])
{
pid_t pid;
pid = fork();
if(pid<0)
{
perror("fork");
exit(1);
}
else if(pid==0)
{
exit(1);
}
else
{
while(1);
}
return 0;
}
结果:
leo_wal+ 13347 62.0 0.0 4188 356 pts/0 R+ 19:28 0:01 ./a.out
leo_wal+ 13348 0.0 0.0 0 0 pts/0 Z+ 19:28 0:00 [a.out]
#include
#include
#include
#include
#include
int main(int argc, const char *argv[])
{
int status;
pid_t pid,ret_pid;
pid = fork();
if(pid<0)
{
perror("fork");
exit(1);
}
else if(pid==0)
{
sleep(3);
exit(1);
}
else
{
sleep(5);
ret_pid=wait(&status);
printf("ret_pid:%d status:%d\n",ret_pid,status);
while(1);
}
return 0;
}
结果:
子进程结束前
2941 13569 13569 2941 pts/0 13569 S+ 1000 0:00 ./a.out
13569 13570 13569 2941 pts/0 13569 S+ 1000 0:00 ./a.out
返回的ret_pid为13570,也就是子进程的PID
子进程接收后,并且wait接收到status
2941 13569 13569 2941 pts/0 13569 R+ 1000 0:07 ./a.out