Linux 多进程
进程
进程理论相关内容直接看教材就好现代操作系统等
shell运行程序的过程:
用户键入命令->shell建立一个新进程来运行此程序->shell将程序从磁盘载入->程序在它的进程中运行直到结束
进程=程序+数据集合
进程是操作系统动态执行的基本单元,基本的分配单元
进程的状态和状态转换
进程的三个基本状态:就绪态,运行态,阻塞态
五种状态的模型:创建态,就绪态,运行态,阻塞态,终止态
Unix/Linux进程命令
ps命令:
ps aux / ajx
a:显示终端上的所有进程,包括其他用户的进程
u:显示进程的详细信息
x:显示没有控制终端的进程
j:列出与作业控制相关的信息
当前运行的命令也是一个进程
ps -ajx
PPID:父进程ID
PID:进程IDPGID:进程组的ID(一个组中可以有多个进程)
SID:会话ID:一个会话包含一个或多个组
会话是由会话中的第一个进程创建的,一般是打开终端时创建的shell进程(领头进程),会话中领头进程的PID即为会话的SID
STAT参数意义:
D 不可中断Uninterruptible (usually Io)
R 正在运行,或在队列中的进程
s(大写) I处于休眠状态
T 停止或被追踪
Z 僵尸进程
w 进入内存交换(从内核2.6开始无效)
x 死掉的进程
< 高优先级
N 低优先级
s 包含子进程
+ 位于前台的进程组
top命令
实时显示进程的动态
-d参数指定信息更新的时间间隔
top
可以在使用top命令时加上 -d 来指定显示信息更新的时间间隔,在top命令执行后.可以按以下按键对显示的结果进行排序:
M 根据内存使用量排序
P 根据cPU占有率排序
T 根据进程运行时间长短排序
u 根据用户名来筛选进程
K 输入指定的 PID杀死进程
kill命令
杀死进程
kill -l,显示所有的kill信号
kill [-signal] pid
kill -l 列出所有信号
kill -SIGKILL 进程ID
kill -9 进程ID
killall name 根据进程名字杀死进程
让程序在后台运行
例如:
#./可执行程序名+&
./hello &
PID,PPID,PGID和相关的函数
每个进程都由进程号来标识:
范围:0-32767(可以修改)
进程号对于一个进程来说是唯一的,但是可以在一个进程终止后,重用其进程号
类型:pid_t
(整型)
fork
#include
pid_t fork(void);
每次调用返回两次,父进程中返回的是子进程的PID,子进程中返回0
新进程拥有和父进程相同的代码和数据(运行到相同的地方)(从fork返回的地方开始)
(父子进程的代码完全相同)
如果返回-1,失败:
失败的普遍原因:
(1)当前系统进程数达到系统规定上限:errno被设置为EAGAIN
(2)系统内存不足:errno设置为ENOMEM
此返回值是条件判断父子进程的依据:
#include
#include
#include
int main()
{
int res_form_fork,mypid;
mypid = getpid();
printf("before id:%d\n",mypid);
res_form_fork = fork();
sleep(1);
//打印创建子进程前后的pid和fork返回值
print("after pid is:%d fork():%d\n",getpid(),res_form_fork);
return 0;
}
/* 结果:
root@ziggy-virtual-machine:~/unix_linux/chapter8# gcc -o forkdemo forkdemo.c
root@ziggy-virtual-machine:~/unix_linux/chapter8# ./forkdemo
before id:1291
after pid is:1291 fork():1292
after pid is:1292 fork():0
*/
以下代码会有八行输出,也就是总共7个进程
//分别是父进程,子1,子2,子3,子1.1,子1.1.1,子2.2
#include
#include
#include
int main()
{
printf("pid is:%d\n",getpid());
fork();
fork();
fork();
//打印创建子进程前后的pid和fork返回值
printf("pid is:%d\n",getpid());
return 0;
}
区分父子进程:根据返回值判断
#include
#include
#include
int main()
{
printf("before pid is:%d\n",getpid());
int res = fork();
if(res==-1)
{
printf("error\n");
}
else if(res==0)
{
printf("child pid is:%d\n",getpid());
}
else
{
printf("parent pid is:%d\n",getpid());
}
return 0;
}
/*
root@ziggy-virtual-machine:~/unix_linux/chapter8# ./forkdemo.3 before pid is:1680
child pid is:1680
parent pid is:1681
*/
子进程的PPID被设置为父进程的PID
原进程设置的信号处理函数对新进程不再起作用
数据的复制是写时复制,只有任一进程对数执行了写操作,复制才会发生
父进程中打开的文件描述符在子进程中默认打开,且引用计数+1,且父进程的用户根目录,当前工作目录,的引用计数都+1
可以看Linux高性能服务器编程第13章第一部分
#include
#include
// #include0){//parent
printf("pid:%d\n",pid);//子进程进程号
printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
}else if(pid==0){
printf("i am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
}
for(int i = 0;i<5;i++)
{
printf("i:%d\n",i);
}
return 0;
}
再看一看:ps aux进程的信息
上面表示,终端是一个进程,而程序是终端的子进程
父子进程是交替运行的(CPU分配时间片)
在没有添加for循环的时候,子进程getppid()得到的返回值为1。
原因是此时父进程被杀死,子进程被init进程领养,可以在for循环中将子进程的存活时间设置得比父进程长,分别在父进程被杀死前后查看ppid,可以观察到这一变化。
fork的返回值pid为局部变量在栈空间
父进程存储在栈空间的返回值pid为子进程id,子进程返回的pid为0
父子进程相同的地方:
内核区复制一份,但是pid不同,用户区数据相同
测试局部变量:测试父子进程都拥有number后,互相之间是否影响(子进程拷贝一份局部变量)
#include
#include
int main()
{
int num = 10;
pid_t pid = fork();
if(pid>0)
{
printf("parent num:%d\n",num);
printf("parent num address:%p\n",&num);
}else if(pid==0){
num+=10;
printf("child num:%d\n",num);
printf("child num address:%p\n",&num);
}
return 0;
}
两个num互不影响,虚拟地址空间相同,但是映射在物理空间地址就不一样了
linux系统下每个进程都拥有自己的页表,父进程fork出新的子进程时,子进程拷贝一份父进程的页表,且父子进程将页表状态修改为写保护。当父进程或子进程发生写操作时将会发生缺页异常,缺页异常处理函数将会为子进程分配新的物理地址。
栈空间数据读时父子进程共享一块物理内存,写时复制,开辟一块新的内存