进程基础
个人学习随笔,仅做学习之用
【1】进程的概念
1. 什么是进程
- 进程是一个程序的一次执行过程
- 进程是一个独立的,可以调度的任务
- 进程在被调度的时候,系统会分配和释放各种资源(cpu资源,进程调度块,内存资源)
- 进程是一个抽象的概念。
2. 进程和程序的区别
程序是静态的,它存储在磁盘上的可执行二进制文件
进程是动态的概念,它是有程序的一次执行过程,包括进程的创建、调度、消亡,是存在内存中
3. 进程的内存管理
- 每个进程都会分配4G的内存空间(虚拟的空间)
- 其中0-3G是用户空间代码使用,3G-4G是内核空间使用过的。
- 3G-4G的内核空间是所有进程共享的。
虚拟地址和物理地址
物理地址:内存条(硬件上)真正存在的存储空间
虚拟地址:程序运行后,会有4G虚拟地址,由物理地址映射到虚拟地址上的。
在32位操作系统中,虚拟空间是4G
在64位操作系统中,2^48 = 256TB
虚拟内存的目的:cpu能够动态分配内存
每个进程都有自己独立的段
1)数据段 (全局变量,静态变量,堆)
2)正文段(存放代码)
3)栈(局部变量,形参,返回值)
每个进程都有自己的虚拟空间,那么进程之间的通信需要使用 3~4G的内存空间。
4. 进程是资源分配的最小单位
-
内存的申请和释放
-
进程的文件描述符分配:1024个
-
分配cpu时间片
-
管理自己的虚拟地址空间,在使用的时候会映射到物理内存上。
... ...
5. 进程号
操作系统会给每一个进程分配一个id号,这个id号就是进程号(PID)
1.主要的进程标识
进程号:PID (process id)
父进程号:PPID(parent process id)
2.PID是进程的唯一标识
3.操作系统刚启动的时候,会启动三个进程
PID:0 idle进程 (操作系统引导程序,创建1号,2号进程) 唯一一个没有调用函数创建的进程.
PID:1 init进程 初始化内核的各个模块,当内核启动完成后,用于收养孤儿进程(没有父进程的进程)
PID:2 kthreadd 用于进程间调度。
【2】查看进程
1. 查看进程的shell命令
1)$ ps -aux
功能:显示进程占计算机的资源百分比
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
2)$ ps -ajx $ ps -ef
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
父进程 进程号 进程组id 会话id 进程状态
进程组:若干个进程的集合,为进程组。默认情况下,新创建的进程会继承父进程的组id
会话组:若干个进程组的集合,称之为会话。
STAT
$ man ps /stat
D 不能被中断的阻塞态
R 正在运行的进程
S 休眠状态的进程
T 被挂起/被追踪的进程
X 死亡态,死亡是一瞬间的,不能被捕获
Z 僵尸进程
< 高优先级
N 低优先级
L 有些页被锁紧内存中
s 会话组长,有子进程的进程
l 多线程
+ 运行在前台的进程
3)$ pidof
功能:根据程序名获取PID号
$ pidof /bin/bash
4)$ pstree
5)$top
功能:实时显示进程状态。
$ top -d 秒数 设置刷新秒数
$ top -d 2
$ top -p pid 显示指定进程的状态
6)$kill
功能:杀死进程
kill -9 pid 根据pid杀死进程
killall -9 进程名字 根据指定的名字,杀死进程
$ killall -9 a.out
2. 进程的状态
- 创建态
- 就绪态
- 运行态
- 阻塞态
- 终止态
【3】进程相关的系统调用
man 2 卷
1)fork
功能:创建一个子进程;
头文件:
#include <unistd.h>
原型:
pid_t fork(void);
返回值:
成功: >0 在父进程中返回,创建的子进程的PID
0 在子进程中,返回0;
失败, -1 在父进程中,返回-1,没有子进程被创建,更新errno;
注意:
-
父进程会拷贝一份资源给子进程(克隆),父子进程的资源是一致的,但是子进程不会运行创建它的那个fork函数以及fork以上的代码。
-
子进程会拷贝父进程的文件描述符。
-
子进程会拷贝父进程的虚拟地址空间;
子进程会拷贝父进程的数据段,正文段,栈段
父子进程堆栈区的虚拟地址完全一致。
但是物理地址不一致。
2)getpid getppid
功能:获取当前进程/父进程的PID;
头文件:
#include <sys/types.h>
#include <unistd.h>
原型:
pid_t getpid(void);
pid_t getppid(void);
返回值:
获取当前进程/父进程的PID
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(int argc, const char *argv[])
{
printf("this is fork function\n");
char c = 10;
pid_t pid = fork();
if(pid > 0)
{
sleep(1);
printf("this is parent %d %d\n", getpid(), pid);
printf("pid = %d %d\n", pid, __LINE__);
printf("parent c=%d %p\n", c, &c);
}
else if(0 == pid)
{
printf("this is child %d %d\n", getppid(), getpid());
printf("pid = %d %d\n", pid, __LINE__);
printf("child c=%d %p\n", c, &c);
c = 20;
printf("child c=%d %p\n", c, &c);
}
else if(pid < 0)
{
perror("fork");
return -1;
}
while(1)
sleep(1);
return 0;
}
3)_exit / exit
i)_exit
man 2 卷
功能:终止进程,清楚进程使用的内存空间,销毁其在内核中的各种数据,不会刷新缓冲区;
头文件:
#include <unistd.h>
原型:
void _exit(int status);
参数:
int status:可以利用这个参数传递进程终止的状态,可以输入任意整形。
这个参数会被 wait/waitpid函数。
注意:_exit函数会宗旨进程,不会刷新缓冲区,直接销毁缓冲区;
ii)exit
man 3 卷
功能:终止进程,并刷新缓冲区;
头文件:
#include <stdlib.h>
原型:
void exit(int status);
参数:
int status:可以利用这个参数传递进程终止的状态,可以输入任意整形。
这个参数会被 wait/waitpid函数。
4)wait/waitpid
i)wait
功能:阻塞函数,等待子进程退出,并回收子进程的资源;
头文件:
#include <sys/types.h>
#include <sys/wait.h>
原型:
pid_t wait(int *status);
参数:
int *status:获取子进程退出的状态;接收 exit/_exit中的status产生书。
如果不想接收,填NULL;
返回值:
成功,返回退出的子进程的pid;
失败,返回-1,更新errno;
1.阻塞等待子进程退出,并回收子进程的资源;
2.如果没有子进程,父进程不等待,直接退出。
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的绝对值。例如 PGID=1976,则pid填-1976
-1 等待当前进程下的任意子进程。
0 回收当前进程组下的任意子进程
> 0 回收指定进程。
int *status:获取子进程退出的状态;接收 exit/_exit中的status产生书。
如果不想接收,填NULL;
int options:
0,阻塞等待,如果指定的进程没有退出,则当前进程阻塞等待。
WNOHANG:非阻塞,就算指定进程没有退出,当前进程不等待,继续执行后面的代码;
返回值:
成功,阻塞,返回被回收进程的pid.
非阻塞,如果指定进程退出,则返回被回收的进程的pid;
如果指定进程没有退出,则返回0;
失败,返回-1,更新errno;
从wait(&s)提取到exit(status)中status的值 ----用宏函数的方式提取
1)WEXITSTATUS(status):提取子进程终止的状态值;
int ret = WEXITSTATUS(status);
2)WIFEXITED(status):判断子进程是否正常退出。
返回1,代表子进程正常退出;
返回0,代表子进程异常退出。
判断进程是否正常退出
调用函数或return退出的都是正常退出,
由于信号退出的是异常退出,例如kill -9 pid
pid_t wait(int*s) 中s存储的是一个32位的整形,其中[8,15]总共8bit存放的是exit(status)中的status;
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
void child_func();
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
//父进程
printf("parent.....\n");
sleep(1);
int status;
pid_t c_pid = wait(&status);
printf("回收子进程资源成功 %d\n", c_pid);
printf("status=%d\n", status);
int ret = WEXITSTATUS(status);
printf("ret = %d\n", ret);
//判断进程是否正常退出
//调用函数或return退出的都是正常退出,
//由于信号退出的是异常退出,例如kill -9 pid
int res = WIFEXITED(status);
if(res)
{
printf("正常退出\n");
}
else
{
printf("异常退出\n");
}
while(1)
sleep(1);
}
else if(0 == pid)
{
//子进程
child_func();
printf("child end\n");
}
else
{
perror("fork");
return -1;
}
printf("准备退出了\n");
return 0;
}
void child_func()
{
int i=0;
while(1)
{
printf("child....\n");
sleep(1);
i++;
if(i==3)
{
// exit(10);
}
}
}
【4】linux的特殊进程
1. 孤儿进程
没有父进程的进程,父进程退出,子进程没有退出。子进程会被init进程收养,脱离终端控制。
孤儿进程不能用ctrl+c退出,但是可以被kill -9杀死
ctrl + alt + f1 切换到字符终端 (f1-f6)
ctrl + alt + f7
#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. 僵尸进程
子进程退出,父进程没有退出,且子进程的资源没有被回收。这时候子进程就是僵尸进程。
注意:
1.僵尸进程只能被回收,不能被kill;
2.父进程退出后,子进程的资源将由内核回收。
危害:
1.占用进程号
2.占用内存空间,占用物理空间,以及进程控制块等等......
如何回收僵尸进程:
1.wait/waitpid函数回收。缺点:阻塞函数,父进程不能做自己的事情。
2.用信号的方式回收。
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
pid_t pid = fork();
if(pid > 0)
{
while(1)
{
printf("parent....\n");
sleep(1);
wait(NULL);
}
}
else if(0 == pid)
{
}
else
{
perror("fork");
return -1;
}
return 0;
}
3. 守护进程
又称之为幽灵进程。
1)守护进程常常是在系统启动的时候开始运行,在系统关闭的时候终止
2)独立于控制终端,且周期性执行某个任务或等待处理某些事情
3)大多数服务器都是由守护进程实现的。
守护进程的创建
1)创建孤儿进程
fork()创建子进程,父进程退出
2)创建新的会话
子进程创建新的会话,就不再依附于父进程的会话
setsid函数
#include <unistd.h>
原型:
pid_t setsid(void);
成功,返回新的会话id;
失败,返回-1,更新errno;
1.创建新的会话,成为会话组组长;
2.创建新的进程组,成为进程组组长;
3.脱离父进程的控制。
3)修改当前孤儿进程的运行目录为根目录
chdir函数
#include <unistd.h>
int chdir(const char *path);
参数:
char *path:指定修改后的运行目录 "/";
返回值:
成功,返回0;
失败,返回-1;
注意:从这一步往后的程序,当前进程运行在根目录下
4)重设文件权限掩码
守护进程对文件进行权限设置,一般保留文件原有权限。
umask(0);
5)关闭所有文件描述符
子进程拷贝了父进程的文件描述符,需要将所有文件文件描述符关闭。包括0,1,2
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
int main(int argc, const char *argv[])
{
//创建孤儿进程
pid_t pid = fork();
if(pid > 0)
{
}
else if(pid == 0)
{
//子进程
//创建新的会话
pid_t sid = setsid();
printf("sid = %d\n", sid);
int fd = open("./1.png", O_RDONLY);
if(fd < 0)
{
perror("open");
return -1;
}
//修改运行目录
chdir("/");
//修改文件权限掩码
umask(0);
//关闭文件描述符
int i = 0;
for(i=0; i<1024; i++)
{
close(i);
}
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[]);