1.定义;对于用户而言,执行的运用程序(任务);于操作系统来说,系统控制对应的进程运行中的程序,是一个动态执行的概 念。程序:存储在磁盘上的一个可执行的二进制文件。
2.组成:PCB+进程实体 操作系统为了管理进程设置了一个结构体管理进程的信息(进程描述符、优先级、状态、程序计数器、信号等等),每一个进程都有一个结构体变量,我们称之为PCB(进程控制块)。在32位系统上,每个PCB大小为1.7K,通过双向循环链表管理PCB。
启动进程时,先申请PCB,在加载进程实体;
结束进程时,释放进程实体,在释放PCB结构。
僵死进程: 进程实体已被释放, PCB结构依旧存在。exit_code
从父子进程角度讲,子进程结束,父进程未结束,且父进程没有获取子进程的退出状态。
孤儿进程:父进程结束,子进程未结束,这是子进程成为孤儿进程。但系统会将所有的孤儿进程托管到系统进程init下,init统一管理。
3.有关进程的操作
- 启动进程: 先申请PCB,在加载进程实体
路径/可执行文件name ./main
路径/可执行文件name & ./main & 表示在后台执行
- 查看正在运行进程:
ps 仅显示与当前终端有关的进程
-c 显示系统上所有进程
-f 显示进程的详细信息
| 管道命令 将一个进程的输入传给另一个进程,做他的输出
grep 过滤命令
- 强制关闭一个进程:
kill pid 结束进程 kill -9 pid 不管进程状态强制结束 pkill cmd 结束一组进程
- 挂起一个进程:
暂停一个进程,不管进程条件怎样,CPU都不会调度执行
kill -stop pid bg任务号,将挂起的进程唤醒到后台执行; fg 任务号 将挂起/后台的进程调度到前台进行
4.进程的状态
进程在生命周期的状态有多个状态,主要有:执行、就绪、阻塞状态。
就绪:所有资源准备完成,等待CPU空闲状态;
运行:在CPU上的执行状态;
阻塞:等待某些条件发生,在未发生前,不调用的状态;
除这三个状态外,还有创建、退出、就绪/挂起、阻塞/挂起等状态。
5.创建一个进程
当系统调用函数时,调用在用户态,执行在内核态。
当调用自己写的函数(库函数)时,调用和执行都在用户态。
创建一个进程的系统调用函数:
pid_t fork(void) ; //fork调用一次,返回两次。在父进程下返回子进程的pid(非0值),在子进程返回0。fork之后,父子进 程运行顺序不定。出错返回-1。
父子进程不共享全局、局部、堆区数据。
父子进程打印出的地址为虚拟地址,并不是真实的物理地址。
写时拷贝技术: fork之后,父子进程共享的所有空间(数据、堆区),但内核会将这些共享空间全局设置为只读的。如果父子进程任意一个进程修改空间上的数据,则会将修改的数据所在的页拷贝出来。将原来的页 和拷贝页的权限设置为读写。
6.进程如何打开文件资源(用户态切换到内核态过程)
什么时候切换?? 调用系统调用函数 中断
- 将函数对应的系统调用号保存到eax寄存器上,
- 触发0x80中断 ——>中断处理程序
- 保存程序上下文,为将来系统调用函数返回后恢复现场使用;
- 传递系统调用函数的参数
- 进入内核空间
7.如何让父子进程并发执行,且在子进程结束后及时处理僵死进程???
——>:父进程在子进程结束后调用wait函数,子进程结束之时就调用wait,等待子进程结束;
父进程在如何知道子进程结束??
子进程结束时,将这件事通知给父进程,父进程接收到信号时在调用wait函数。
这时候就需要信号机制了。
8.如何处理僵死进程
1)父进程调用wait或waitpid获取子进程的退出状态,这种方式可能会导致父进程在wait或waitpid调出阻塞运行,直到子进程 退出;
2)父调用进程signal(SIGNALD,SIG_SIG)来忽略sigchld信号,这样子进程结束后由内核释放资源。
3)对子进程的退出捕获退出信号,在信号处理函数中调用wait或waitpid操作释放资源。
9. 守护进程(精灵进程)
在系统启动时自启,在系统关闭时停止。生存周期长,后台运行。
可通过ps -axj查看守护进程,最常见的为init进程,init负责各运行层次的系统服务。
编程规则:
- 调用unmask(mode_t unmask())函数将文件模式创建屏蔽字设置为0;
- 调用fork(),使父进程退出(exit);
- 调用setsid()创建一个会话;
- chdir("/")将当前目录改为根目录;
- 关闭不再需要的文件描述符;
- 某些守护进程打开/dev/null使其具有文件描述符0,1,2,这样任何一个进程不会产生其他不好的效果。
10.进程替换——>exec()函数
exec()功能:调用它时并没有产生新的进程。一个进程调用exec()函数,那他本身就会死亡。
系统将代码替换为新的程序代码,唯一保留的是进程ID。对于系统而言,还是同一个进程,但执行的是另一个程序。
- fork()和vfork()才能创建一个新进程;
- 在使用exec()之前,首先要用fork创建一个子进程,子进程调用exec函数
int exec(const char *path,const char *arg,....) int execv((const char *path,const char *arg[])
与execv函数用法类似。只是在传递arg参数时每个命令行参数都声明为一个单独参数,这些参数以一个空指针结尾。
附加:
linux系统上使用的malloc函数仅仅是在进程的虚拟地址空间上分配堆区空间,并没有将分配的堆区空间映射到物理内存上,只有在真正使用此区域时,才做映射。
阻塞运行:调用函数时,如果条件未发生,函数不会立即返回。而是会等待条件发生;
非阻塞运行:调用函数时,不管条件是否发生,函数都会立即返回