进程
【1】进程的概念
1.什么是进程
1. 进程是一个独立的,可以调度的任务。
2. 进程是一个程序的一次执行过程。
3. 进程在调度的时候,系统会分配和释放各种资源(cpu,内存资源......)
4. 进程是一个抽象的概念。
2.进程和程序的区别
程序是静态的,它是保存在磁盘上的可执行二进制文件.
进程是一个动态的概念,它是程序的一次执行过程。包括进程的创建,调度,消亡。是存在内存中
3.进程的内存管理
1. 每个进程都会分配4G的内存空间(虚拟空间)
2. 其中0-3G是用户空间代码使用,3-4G是内核空间使用。
3. 3-4G的内核空间是所有进程共享的。
虚拟地址和物理地址
物理地址:硬件上个真正存在的地址。
虚拟地址:程序运行后,会有4G的虚拟地址,由物理地址映射到虚拟地址
程序中的指针,只能指向到虚拟地址,不能指向到物理地址。
注意:
1.每个进程都分配4G的虚拟空间,其中0-3G是用户代码空间使用的。所以每个进程的地址都是互不相通的。
每个进程都有自己独立的段:
1)数据段(全局变量,静态变量,堆)
2)正文段(存放代码,常量)
3)栈段(局部变量,形参,返回值)
2.虚拟内存的目的:cpu能够动态分配内存
思考一下,既然每个进程都有自己的虚拟空间,那么进程之间如何通信?
使用3-4G共享内存的空间实现。
4.进程是资源分配的最小单位
1. 内存的申请和释放
2. 文件描述符,每个进程最多能打开1024个文件描述符
3. 分配cpu时间片
4. 管理自己的虚拟空间(要用的时候映射到物理地址上)
5. ... ...
5.进程号
操作系统会给每个进程分配一个id号,这个id就是进程号(PID)
1.主要的进程标识符
进程号:PID(process id)
父进程号:PPID(parent process id)
2.PID是进程的唯一标识
3.操作系统在刚启动的时候,会启动三个进程
PID:0 idle进程 (操作系统引导程序,创建1号2号进程) ,没有调用fork函数创建的进程。
PID:1 init进程 初始化内核的各个模块。当内核启动完成,用于收养孤儿进程(它是一个没有父进程进程的进程)
PID:2 kthreadd 用于进程间的调度
【2】查看进程
1.查看进程的命令
1)ps -aux 显示进程占计算机的资源
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
用户名 进程号 占用cpu资源 占用内存百分比 状态 消耗cpu时间
2)ps -ajx /ps -ef显示父子进程关系
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
父进程号 进程号 进程所属组id 会话组id
进程组:若干个进程的集合,称之为进程组。默认情况下,新创建的进程会继承父进程的组id;
会话组:若干个进程组的集合,称之为会话。默认情况下,新创建的进程会继承父进程的会话id;
STAT:
D 不可被中断的阻塞态
R 正在运行的进程
S 处于休眠状态的进程
T 被挂起/被追踪的进程
W 进入内存交换的进程(从内核2.6开始无效)
X 死掉的进程,死亡是一瞬间的事情,不可被捕捉
Z 僵尸进程
< 高优先级
N 低优先级
L 部分页被锁进内存
s 会话组组长(有子进程)
l 多线程
+ 运行在前端
3)pidof 根据程序名获取PID号
$ pidof a.out ---->显示a.out的pid号
4)pstree 显示进程关系树
5)top 实时显示进程状态
$ top -d 秒数 设置多少秒刷新一次
$ top -d 5 5秒刷新一次
$ top -p pid1 指定显示的进程id
$ top -p 5627
6)kill 杀死进程
$ kill -9 PID 根据PID杀死进程
$ kill -9 6007
$ killall -9 进程名字 根据进程名字杀死所有进程
$ killall -9 a.out 退出所有进程名字为a.out的程序
$ sudo killall -9 bash 杀死所有终端
2.进程状态
1. 创建态:创建一个进程,完成资源分配,进入就绪态
2. 就绪态:资源已经准备好,等待cpu调度
3. 运行态:程序被cpu调度,进入运行态
4. 阻塞态:正在运行的程序,由于某些事件无法运行,就会进入阻塞态。一旦事件被满足,就会进入就绪态
5. 终止态:程序结束
【3】进程相关的系统调用
man 2卷
1)fork
功能:创建一个子进程;
头文件:
#include <unistd.h>
原型:
pid_t fork(void);
返回值:
成功 0 在子进程中,返回0;
>0 在父进程中,返回创建的子进程PID
失败 -1 创建失败,更新errno
注意:
1. 父进程会拷贝一份资源给子进程(克隆),父子进程的资源是一致的。但是子进程不会执行fork以及fork以上的代码。
2. 子进程会拷贝父进程的文件描述符
3. 子进程拷贝父进程的虚拟地址
子进程会拷贝父进程的数据段,正文段,堆栈段。
父子进程的堆栈区的虚拟地址完全一致。但是物理地址不一致。
4. 父子进程是相互独立的两个进程。
5. fork的返回值不可能是1,1为init进程,收养所有孤儿进程的进程
例子
int main(int argc, const char *argv[])
{
int a = 10;
pid_t pid = fork();
// a = 11;
if(pid > 0)
{
//父进程
printf("pid=%d this is parent\n",pid);
printf("pid=%d ppid=%d\n",getpid(), getppid());
printf("parent a = %d %p\n", a, &a);
a = 9;
printf("parent a = %d %p\n", a, &a);
}
else if(0==pid)
{
//子进程
printf("pid=%d this is child\n", pid);
printf("pid=%d ppid=%d\n",getpid(), getppid());
sleep(1);
printf("child a = %d %p\n",a,&a );
}
else
{
perror("fork");
return -1;
}
// while(1);
return 0;
}
2)getpid / getppid
功能:获取当前进程/父进程的pid;
头文件:
#include <sys/types.h>
#include <unistd.h>
原型:
pid_t getpid(void);
pid_t getppid(void);
返回值:
返回当前进程/父进程的pid
3)_exit / exit
i)_exit
man 2 卷
功能:终止进程;清除进程使用的内存空间,销毁其在内核的各种数据结构;
头文件:
#include <unistd.h>
原型
void _exit(int status);
参数:
int status:是一个32整形。可以利用这个参数传递进程终止状态;
通常,0代表正常退出,其余数值代表进程异常退出。
该参数由 wait/waitpid函数接收。
注意:
_exit()函数 终止进程,不会刷新缓冲区,直接销毁缓冲区
ii)exit
man 3 卷
功能:终止进程,并刷新缓冲区;
头文件:
#include <stdlib.h>
原型:
void exit(int status);
参数:
int status:是一个32整形。可以利用这个参数传递进程终止状态;
通常,0代表正常退出,其余数值代表进程异常退出。
该参数由 wait/waitpid函数接收。
注意:
exit()函数 终止进程,会刷新缓冲区
例子
pid_t pid = fork();
if(pid > 0)
{
//父进程
printf("parent %d\n", getpid());
int s = 0;
pid_t c_pid = wait(&s);
printf("c_pid = %d\n", c_pid);
printf("s = %d\n", s);
int status = WEXITSTATUS(s);
printf("status = %d\n", status);
while(1)
sleep(1);
}
else if(0 == pid)
{
//子进程
printf("child %d\n", getpid());
sleep(2);
// _exit(1);
exit(100);
}
else
{
perror("fork");
return -1;
}
4)wait / waitpid
i)wait
功能:阻塞函数,等待子进程退出,并回收子进程资源;
头文件:
#include <sys/types.h>
#include <sys/wait.h>
原型:
pid_t wait(int *status);
参数:
int *status:获取子进程退出状态,接收exit(status)/_exit(status),中的status
如果不想接收,直接填NULL;
返回值:
成功,返回退出的子进程的pid;
失败,返回-1;
注意:
1.阻塞等待子进程退出,并回收子进程资源;
2.如果没有子进程,父进程不等待,直接退出。
从wait(&s)的s中提取到exit(status)的status —通过宏函数提取
1)WEXITSTATUS(status);提取子进程终止的状态值;exit(status)中的status的值。
int status = WEXITSTATUS(s);
2)WIFEXITED(status);判断子进程是否正常退出;
返回1,代表子进程正常退出;
返回0,代表子进程异常退出。
ps:想要子进程异常退出,可以利用kill -9 杀死子进程
pid_t wait(int *status)中的status存储着是一个32位的整形,其中[8,15]为(0开始)存放的是exit(status)返回的status;
所以,exit(status)中的status的取值范围是[0, 255];
ii)waitpid
功能:等待指定的进程退出,并回收指定进程的资源;
头文件:
#include <sys/types.h>
#include <sys/wait.h>
原型:
pid_t waitpid(pid_t pid, int *status, int options);
参数:
pid_t pid:指定要等待的进程;
< -1 回收指定进程组下的任意进程,指定进程组id = pid的绝对值
-1 等待当前进程下的任意子进程;
0 回收当前进程组下的任意子进程;
> 0 回收指定进程;
int *status:获取子进程退出状态,接收exit(status)/_exit(status),中的status
如果不想接收,直接填NULL;
int options:0,阻塞等待,如果指定进程没有退出,则当前进程阻塞等待。
WNOHANG,非阻塞等待,就算指定的进程没有退出,当前进程不等待,继续执行后面的代码。
返回值:
成功:阻塞,返回指定进程pid
非阻塞,如果指定进程退出,返回指定进程的pid;
如果指定进程没有退出,返回0;
失败,返回-1;
例子
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
//父进程
printf("parent %d\n", getpid());
int s = 0;
pid_t c_pid = wait(&s);
printf("c_pid = %d\n", c_pid);
printf("s = %d\n", s);
int status = WEXITSTATUS(s);
printf("status = %d\n", status);
int res = WIFEXITED(s);
printf("res = %d\n", res);
while(1)
sleep(1);
}
else if(0 == pid)
{
//子进程
printf("child %d\n", getpid());
/*
while(1)
{
sleep(2);
}
*/
// _exit(1);
exit(300);
}
else
{
perror("fork");
return -1;
}
return 0;
}
小练习
拷贝一张图片,实现父进程拷贝前半部分,子进程拷贝后后半部分。
提示:子进程先sleep(1)。
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <stdlib.h>
int main(int argc, const char *argv[])
{
int fd_r,fd_w;
//以读的方式打开一张图片
if((fd_r = open("./1.png", O_RDONLY))< 0)
{
perror("open");
return -1;
}
//以写的方式打开图片,只写,创建,清空
if((fd_w = open("2.png", O_WRONLY|O_CREAT|O_TRUNC, 0664))<0)
{
perror("open");
exit(1);
}
//计算文件大小
int size = lseek(fd_r, 0, SEEK_END);
printf("size=%d\n", size);
//创建进程
pid_t pid = fork();
if(pid>0)
{
//父进程拷贝前半部分
lseek(fd_r, 0, SEEK_SET);
int i = 0;
char c = 0;
int res = 0;
for(i=0; i<size/2; i++)
{
//读一个字符
res = read(fd_r, &c, 1);
if(res<0)
{
perror("read");
exit(1);
}
//写一个字符
res = write(fd_w, &c, 1);
if(res < 0)
{
perror("write");
exit(1);
}
}
printf("前半部分拷贝完毕\n");
}
else if(0 == pid)
{
//子进程拷贝后半部分
sleep(1);
lseek(fd_r, size/2, SEEK_SET);
lseek(fd_w, size/2, SEEK_SET);
int i=0, res=0;
char c=0;
for(;;)
{
//读一个字符
res = read(fd_r, &c, 1);
if(res<0)
{
perror("read");
exit(1);
}
else if(0 == res)
{
break;
}
//写一个字符
res = write(fd_w, &c, 1);
if(res < 0)
{
perror("write");
exit(1);
}
}
printf("后半部分拷贝完毕\n");
}
else
{
perror("fork");
return -1;
}
//关闭文件描述符
close(fd_r);
close(fd_w);
return 0;
}
【4】linux的特殊进程
1.孤儿进程(orphad)
概念:父进程退出,子进程未退出。子进程被init进程收养,变成init进程的子进程,脱离终端控制;
ctrl + alt + f1 切换到字符终端(f1~f6均可)
ctrl + alt + f7 切换到虚拟终端
注意:
1.孤儿进程不能使用ctrl+c退出,但是可以用kill -9杀死
2.注意:1号进程(init进程)不能被杀死的。
例子
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
}
else if(0 == pid)
{
while(1)
{
printf("child\n");
sleep(1);
}
}
else
{
perror("fork");
return -1;
}
return 0;
}
2.僵尸进程(zombie)
僵尸进程:子进程退出,父进程没有退出,且子进程的资源没有被回收。这时候子进程就是僵尸进程。
危害:
1占用进程号
2占用内存空间,占用物理空间,以及进程控制块等等。
注意:
- 僵尸进程只能回收,不能被kill;
- 父进程退出后,子进程的资源将由内核回收(收尸)。
- 如何回收僵尸进程:
01 wait/waitpid函数回收。缺点:阻塞函数,父进程不能做自己的事情;
02 用信号的方式回收;
例子
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
int i = 0;
while(i < 4)
{
sleep(1);
//i++;
}
}
return 0;
}
3.守护进程(daemon)
概念:守护进程又称之为幽灵进程;
1)守护进程常常在系统启动时候开始运行,在系统关闭的时候终止。
2)独立于控制终端,且周期性的执行某个任务或者等待处理某些事件
3)大多数服务器都是由守护进程实现的。
守护进程的创建
神话小说 ——————> 断情绝爱,修炼成神
1)创建孤儿进程 (断绝父子关系)
fork() 创建子进程,退出父进程。
2)创建新的会话 (离开家族,自立门户)
子进程创建新的会话,就不再依附于父进程的会话。
setsid
#include <unistd.h>
pid_t setsid(void);
成功,返回新的会话;
失败,返回-1,更新errno;
1.创建新的会话,成为会话组组长;
2.创建新的进程组,成为进程组组长;
3.脱离终端控制
3)改变当前孤儿进程的运行目录为’/'目录(百炼成神)
chdir
#include <unistd.h>
int chdir(const char *path);
参数:
char *path:指定修改后的运行目录里;
返回值:
成功,返回1;
失败,返回0;
注意:从这一步开始,往后的程序,就是运行在根目录下的。
4)重设文件掩码 (重塑金身)
守护进程对文件进行权限设置,一般保留文件原有的权限 umask(0);
5)关闭文件描述符(斩断红尘)
子进程拷贝了父进程的文件描述符,需要将文件描述符关闭。包括0,1,2;
例子
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
}
else if(0 == pid)
{
//创建新的会话
if(setsid()<0)
{
perror("setsid");
return -1;
}
//修改运行目录
if(chdir("/") == -1)
{
perror("chdir");
return -1;
}
//重置文件权限掩码
umask(0);
//关闭文件描述符
int fd = 0;
for(fd=0; fd<getdtablesize(); fd++)
{
close(fd);
}
while(1)
{
sleep(1);
}
}
else
{
perror("fork");
return -1;
}
return 0;
}
【5】exec函数族
功能:(夺舍,魂穿)
1)执行一个可执行程序,代替当前进程;
2)调用函数族后,没有生成新的进程,而是替换了原有的进程正文;
3)调用前后,进程的pid不变,单执行的是调用后的进程。
#include <unistd.h>
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);