Linux 进程
一、进程的基本概念
1、进程与程序
程序:完成特定任务的一系列指令集合。
进程:从用户的角度来看进程是程序的一次执行过程从操作系统的核心来看,进程是操作系统分配的内存、CPU时间片等资源的基本单位。
进程是资源分配的最小单位
每一个进程都有自己独立的地址空间与执行状态。像UNIX这样的多任务操作系统能够让许多程序同时运行,每一个运行着的程序就构成了一个进程
2、进程数据结构
进程的静态描述:由三部分组成:PCB、有关程序段和该程序段对其进行操作的数据结构集。
- (1)进程控制块:每个进程在内核都有一个进程控制块(PCB),用于描述进程情况及控制进程运行所需的全部信息,Linux内核进程控制块是task_struct结构体
- 进程Id。
- 系统中每个进程有唯一的id,在c语言中用pid_t(非负数)类型表示,取值范围2-32768。
- 当一个进程被启动时,它会顺序挑选一个未使用的编号作为自己的PID
- 数字1一般为特殊进程init保留
- 进程的状态,有运行态、挂起、停止、僵尸等状态。
- 进程切换时需要保存和恢复的一些CPU寄存器。
- 寄存器
- PC
- 程序状态字PSW
- 栈指针
- 描述虚拟地址空间的信息。
- 描述控制终端的信息。
- 当前工作目录(Current Working Directory)
- umask掩码
- 文件描述符表,包含很多指向file结构体的指针
- 和信号相关的信息
- 用户id和组id
- 控制终端、Session和进程组
- 进程可以使用的资源上限
- 进程Id。
- (2)代码段:是进程中能被进程调度程序在CPU上执行的程序代码段。
- (3)数据段:一个进程的数据段,可以是进程对应的程序加工处理的原始数据,也可以是程序执行后产生的中间或最终数据。
3、进程和程序的区别
- 进程是动态的,程序是静态的
- 进程的生命周期是相对短暂的,而程序是永久的。
- 一个进程只能对应一个程序,一个程序可以对应多个进程。
4、进程编程相关术语
进程创建
- 不同的操作系统所提供的进程创建原语的名称和格式不尽相同,但执行创建进程原语后,操作系统所做的工作却大致相同,都包括以下几点:
- 给新创建的进程分配一个内部标识,在内核中建立进程结构。
- 复制父进程的环境
- 为进程分配资源, 包括进程映像所需要的所有元素(程序、数据、用户栈等),
- 复制父进程地址空间的内容到该进程地址空间中。
- 置该进程的状态为就绪,插入就绪队列。
进程撤销
进程终止时操作系统做以下工作:
- 关闭软中断:因为进程即将终止而不再处理任何软中断信号;
- 回收资源:释放进程分配的所有资源,如关闭所有已打开文件,释放进程相应的数据结构等;
- 写记帐信息:将进程在运行过程中所产生的记帐数据(其中包括进程运行时的各种统计信息)记录到一个全局记帐文件中;
- 置该进程为僵死状态:向父进程发送子进程死的软中断信号,将终止信息status送到指定的存储单元中;
- 转进程调度:因为此时CPU已经被释放,需要由进程调度进行CPU再分配。
进程创建其他实践
1)Win PK Linux进程观察工具
0号进程(也称为)空闲进程
内存===》交换空间,支持虚拟内存。
1号进程 第一个用户进程。。。。
2)which init /sbin/
3)查看内核进程pid最大配置
[root@localhost ~]#cat /proc/sys/kernel/pid_max
32768
4)终止进程方法中
SIGABORT
4、总结
- 中断
早期是没有进程这个概念,当出现中断技术以后才出现进程这个概念 - 分式操作系统
基于时间片轮转
进程是操作系统对资源的一种抽象,一个进程:代码段、数据段、堆栈段、+进程控制块(PCB)PCB是操作系统感知进程存在的一个重要数据结构(cpu通过进程控制块来控制进程)
二、进程状态
操作系统经典三态
- 就绪
- 等待
- 运行
进程状态条件
进程因创建而就绪,因调度而执行;因时间片用完而重新就绪;执行中因I/O请求而阻塞;I/O完成而就绪
Linux内核中的进程状态
- 运行状态(TASK_RUNNING)
- 可中断睡眠状态(TASK_INTERRUPTIBLE)
- 不可中断睡眠状态(TASK_UNINTERRUPTIBLE)
- 暂停状态(TASK_STOPPED)
- 僵尸状态(TASK_ZOMBIE)
修改进程资源限制,软件限制可改,最大值不能超过硬件限制,硬限制只有root用户可以修改
#include<sys/time.h>
#include<sys/resource.h>
int getrlimit(int resource,struct rlimit *rlim);
int setrlimit(int resource,const struct rlimit *rlim);
查看进程资源限制
cat /proc/self/limits
ulimit -a
备注:就绪态深入理解:内存中就绪和交换空间中就绪,操作系统支持虚拟内存。
虚拟内存实现需要操作系统支持:内存段式管理、业式管理、段页管理。
进程状态编程事件
进程调度及调度算法
概念:按一定算法,从一组待运行的进程中选出一个来占有CPU运行
- 先来先服务调度算法
- 短进程优先调度算法
- 高优先级优先调度算法
- 时间片轮询法
三、进程编程实践
3.1 fork
#include <unistd.h>
pid_t fork(void);
说明:
子进程复制父进程的0-3G地址空间(进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等)和父进程内核中的PCB,但id号不同。fork调用一次返回两次。
+ 父进程中返回子进程ID
+ 子进程中返回0
+ 读时共享,写时复制
子进程与父进程的区别:
- 1、父进程设置的锁,子进程不继承
- 2、各自的进程ID和父进程ID不同
- 3、子进程的未决告警被清除
- 4、子进程的未决信号集设置为空集
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
pid_t pid;
char *message;
int n;
pid = fork();
if(pid < 0)
{
perror("fork failed");
exit(1);
}
if(pid == 0)
{
message = "This is the child\n";
n = 6;
}
else
{
message = "This is the parent\n";
n = 3;
}
for(; n > 0; n--)
{
printf(message);
sleep(1);
}
return 0;
}
进程相关函数
getpid/getppid
#include<sys/types.h>
#include<unistd.h>
pid getpid(void);//返回调用进程的PID号
pid getppid(void);//返回调用进程父进程的PID号
getuid
#include<unistd.h>
#include<sys/types.h>
uid_t getuid(void);//返回实际用户ID
uid_t geteuid(void);//返回有效用户ID
getgid
#include<unistd.h>
#include<sys/types.h>
gid_t getgid(void); //返回实际用户组ID
gid_t getegid(void); //返回有效用户组ID
vfork
- 用于fork后马上调用exec函数
- 父子进程,共用同一地址空间,子进程如果没有马上exec而是修改了父进程出得到的变
量值,此修改会在父进程中生效 - 设计初衷,提高系统效率,减少不必要的开销
- 现在fork已经具备读时共享写时复制机制,vfork逐渐废弃
例子
#include<sys/types.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<signal.h>
#include<errno.h>
int main(void)
{
int fd;
pid_t pid;
signal(SIGCHLD,SIG_IGN);
printf("befor fork pid:%d\n",getpid());
int num = 10;
fd = open("1.txt",O_WRONLY);
if(fd == -1)
{
perror("open failed");
exit(1);
}
pid = fork();
if(pid < 0)
{
perror("fork failed");
exit(1);
}
if(pid == 0)
{
//子进程
printf("child:%d,parent:%d\n",getpid(),getppid());
write(fd,"child",5);
close(fd);
}
else
{
//父进程
printf("parent:pid=%d",getpid());
write(fd,"parent",6);
close(fd);
}
printf("fork after...\n");
return 0;
}
3.2 孤儿进程和僵尸进程
孤儿进程和僵尸进程
- 如果父进程先退出,子进程还没退出那么子进程的父进程将变为init进程。(注:任何一个进程都必须有父进程)
- 如果子进程先退出,父进程还没退出,那么子进程必须等到父进程捕获到了子进程的退出状态才真正结束,否则这个时候子进程就成为僵进程。
- 孤儿进程如果父亲进程先结束,子进程会托孤给1号进程
避免僵尸进程
#include <signal.h>
signal(SIGCHLD, SIG_IGN);
3.3 fork之后父子进程共享文件
void main(void)
{
pid_t pid;
signal(SIGCHLD, SIG_IGN);
int fd;
fd = open("test.txt", O_WRONLY);
if (fd == -1)
{
perror("open err");
exit(0);
}
pid = fork();
if (pid == -1)
{
perror("fork err");
return -1;
}
if (pid == 0)
{
printf("this is child pid:%d ppid:%d \n", getpid(), getppid());
write(fd, "child", 5);
//sleep(20);
}
if (pid > 0)
{
printf("this is parent pid:%d ppid:%d \n", getpid(), getppid());
write(fd, "parent", 5);
//sleep(20);
}
printf("fork() after\n");
sleep(1);
close(fd);
return 0;
}
3.4 fork和vfork
1)在fork还没实现copy on write之前。Unix设计者很关心fork之后立刻执行exec所造成的地址空间浪费,所以引入了vfork系统调用。
2)vfork有个限制,子进程必须立刻执行_exit或者exec函数。
即使fork实现了copy on write,效率也没有vfork高,但是我们不推荐使用vfork,因为几乎每一个vfork的实现,都或多或少存在一定的问题。
结论:
1:fork子进程拷贝父进程的数据段
Vfork子进程与父进程共享数据段;
2:fork父、子进程的执行次序不确定
Vfork:子进程先运行,父进程后运行;
Vfork和exec函数族在一起
execve替换进程映像(加载程序)注意execve是一个系统调用;
替换意味着:代码段、数据段、堆栈段、进程控制块PCB全部替换。
int main(void)
{
int fd;
pid_t pid;
signal(SIGCHLD, SIG_IGN);
printf("befor fork pid:%d \n", getpid());
g_num = 10;
pid = vfork();
if (pid == -1)
{
printf("pid < 0 err.\n");
return -1;
}
if (pid > 0)
{
printf("parent: pid:%d \n", getpid());
}
else if (pid == 0)
{
printf("child: %d, parent: %d \n", getpid(), getppid());
// int execve(const char *filename, char *const argv[],
// char *const envp[]);
char *const argv[] = {"ls", "-lt", NULL};
execve("/bin/ls", argv, NULL);
//注意:此处有/bin/ls程序结束程序,不会出现coredump现象
printf("测试下面这句话还执行吗\n");
//exit(0);
//1 vfork只有需要用exit(0) _exit(0)
//2 测试return 0; 区别
}
printf("fork after....\n");
return 0;
}
3.5进程终止的5种方式
- 进程终止的5种方式
- 正常退出
1、 从main函数返回
2、调用exit
3、调用_exit - 异常退出
1、调用abort 产生SIGABOUT信号
2、由信号终止 ctrl+c SIGINT
- 正常退出
eg:
区别1:清空缓冲区的操作
int main(void)
{
printf("hello itcast");
//return 0;
//exit(0);
fflush(stdout);
_exit(0);
}
区别2:exit会调用终止处理程序
有关终止处理程序
- atexit可以注册终止处理程序,ANSI C规定最多可以注册32个终止处理程序。
- 终止处理程序的调用与注册次序相反
- man atexit
- int atexit(void (*function)(void));
- man 2 atexit