linux-进程
一、基本概念
- 程序:存放在磁盘上的指令和数据的有序集合(文件),静态的
- 进程:执行一个程序所分配的资源的总称,进程是程序的一次执行过程,动态的,包括创建、调度、执行和消亡
1.1、进程内容
进程包含的内容:
- BSS段:存放程序中未初始化的全局变量
- 数据段:已初始化的全局变量
- 代码段:程序执行代码
- 堆(heap):malloc等函数分配内存
- 栈(stack):局部变量,函数参数,函数的返回值
- 进程控制块(pcb): 进程标识PID ,进程用户 ,进程状态、优先级,文件描述符表
1.2、进程类型
- 交互进程:在shell下启动。以在前台运行,也可以在后台运行
- 批处理进程:和在终端无关,被提交到一个作业队列中以便顺序执行
- 守护进程:和终端无关,一直在后台运行
1.3、进程状态
- 运行态:进程正在运行,或者准备运行
- 等待态:进程在等待一个事件的发生或某种系统资源,可中断,不可中断
- 停止态:进程被中止,收到信号后可继续运行
- 死亡态:已终止的进程,但pcb没有被释放
二、常用命令
2.1、查看进程信息
ps | 查看系统进程快照 |
top | 查看进程动态信息 |
/proc | 查看进程详细信息 |
ps 命令详细参数:
-e | 显示所有进程 |
-l | 长格式显示更加详细的信息 |
-f | 全部列出,通常和其他选项联用 |
aux | 相对于ps -ef还能够显示进程的当前状态(运行态、停止态、前台进程等) |
例如
top 查看进程动态信息
shift +> 后翻页
shift +< 前翻页
top -p PID 查看某个进程
2.2、前后台进程切换
jobs | 查看后台进程 |
bg | 将挂起的进程在后台运行 |
fg | 把后台运行的进程放到前台运行 |
ctrl+z 把运行的前台进程转为后台并停止。
./test & 把test程序后台运行
2.3、改变进程优先级
nice | 按用户指定优先级运行进程 |
renice | 改变正在运行进程的优先级 |
nice 按用户指定的优先级运行进程
nice [-n NI值] 命令
NI 范围是 -20~19。数值越大优先级越低
普通用户调整 NI 值的范围是 0~19,而且只能调整自己的进程。
普通用户只能调高 NI 值,而不能降低。如原本 NI 值为 0,则只能调整为大于 0。
只有 root 用户才能设定进程 NI 值为负值,而且可以调整任何用户的进程。
renice 改变正在运行进程的优先级
renice [优先级] PID
nice -n 2 ./test#将test进程的nice值设置为2
renice -n 2 29070#将进程号为29070的nice值设置为2
三、进程的创建和结束
3.1、创建子进程
#include <unistd.h>
pid_t fork(void);
创建新的进程,失败时返回-1
成功时父进程返回子进程的进程号,子进程返回0
通过fork的返回值区分父进程和子进程
#include<stdio.h>
#include<unistd.h>
int main()
{
pid_t pid;
if((pid=fork())<0){
perror("fork");//创建失败,打印错误信息
return -1;
}
else if(pid==0){
printf("child prosess:my pid is %d\n",getpid());//创建子进程,返回子进程的pid号
}
else{
printf("parent process:my pid is %d\n",getpid());//打印父进程的进程号
}
}
3.2、父子进程
- 子进程继承了父进程的内容(几乎赋值了父进程的全部内容,但是pid、ppid不同)
- 父子进程有独立的地址空间,互不影响
- 若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程
- 若子进程先结束,父进程如果没有及时回收,子进程变成僵尸进程
注意:
1. 子进程是从fork函数的下一条语句开始执行,并没有执行fork(子进程只执行fork之后的代码)
2. 内核先调用父进程就先执行父进程,先调用子进程就先执行子进程(父子进程执行顺序是操作系统决定的)
3. 父进程可以多次调用fork,子进程也可以调用fork创建孙进程(注意进程的回收)
3.3、结束进程
#include <stdlib.h>
#include <unistd.h>
void exit(int status);
void _exit(int status);
结束当前的进程并将status返回
exit结束进程时会刷新(流)缓冲区
#include <stdio.h>
#include <stdlib.h>
int main(void)
{
printf("this process will exit");//没有换行符只能写到标准输出流的缓冲区,不会在终端显示
exit(0);//结束进程,刷新缓冲区的流,因此上条语句会显示在终端上
printf("never be discovered");//不会被打印
}
return 和exit的区别
main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。
四、进程的回收
#include <unistd.h>
pid_t wait(int *status);
成功时返回回收的子进程的进程号;失败时返回EOF
若子进程没有结束,父进程一直阻塞
若有多个子进程,哪个先结束就先回收
status 指定保存子进程返回值和结束方式的地址
status为NULL表示直接释放子进程PCB,不接收返回值
#include<stdio.h>
#include<unistd.h>
int main()
{
int status;//接收返回值以及返回方式
pid_t pid;//接收fork的返回值
if((pid=fork())<0){
perror("fork");
exit(-1);
}
else if(pid==0){
sleep(1);//如果是子进程睡眠一秒
exit(2);//退出子进程
}
else{
wait(&status);//等待子进程结束
printf("%x\n",status);//打印输出结果
}
}
子进程通过exit/_exit/return返回某个值(0~255)
父进程通过调用wait(&status)回收
WIFEXITED(status) | 判断子进程是否正常结束 |
WEXITSTATUS(status) | 获得子进程的返回值 |
WIFSIGNALED(status) | 判断子进程是否被信号结束 |
WTERMSIG(status) | 获取结束子进程的信号类型 |
#include <unistd.h>
pid_t waitpid(pid_t pid, int *status, int option);
wait(wait_stat) 等价于waitpid(-1,wait_stat,0)
- 成功时返回回收子进程的pid或0;失败(如:没有子线程)返回EOF
- pid可用于指定回收那个子进程或任意子进程
- status指定用于保存子进程的返回值和结束方式的地址
- option指定回收方式,0(阻塞的方式,等待子进程结束)或WNOHANG(非阻塞)
参数:
pid
pid>0 | 只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。 |
pid=-1 | 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。 |
pid=0 | 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。 |
pid<-1 | 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。 |
options
WNOHANG | 若由pid指定的子进程未发生状态改变(没有结束),则waitpid()不阻塞,立即返回0 |
WUNTRACED | 返回终止子进程信息和因信号停止的子进程信息 |
waitpid(pid,&status,0);//指定进程的进程号,阻塞方式
waitpid(pid,&status,WNOHABG);//子进程结束返回子进程的进程号,没有结束返回0
waitpid(-1,&status,0);//-1表示回收任意一个子进程,等价于wait
waitpid(-1,&status,WNOHANG);//非阻塞的方式回收任意进程
五、exec函数族
背景:fork创建进程之后,子进程和父进程执行相同的代码,但是在实际开发当中,我们希望父子进程执行不同的代码。
作用:执行指定的程序
- 进程调用exec函数族执行某个程序
- 进程当前内容被指定程序替换
- 实现父子进程执行不同程序
- 父进程创建子进程
- 父进程调用exec函数族
- 父进程不受影响
#include <unistd.h>
int execl(const char *path, const char *arg, …);
int execlp(const char *file, const char *arg, …);
成功时执行指定的程序;失败时返回EOF
path:执行的程序名称,包含路径
arg :传递给执行的程序的参数列表
file :执行的程序的名称,在PATH中查找
#include <unistd.h>
int execv(const char *path,char *const argv[]);//arg...封装成指针数组的形式
int execvp(const char*file,char *const argv[]);//arg...封装成指针数组的形式
方式1:execl
if(execl("/bin/ls","ls","-a","-l","/etc",NULL)<0{//一定要以NULL结尾,并判断函数是否执行成功
perror("execl");
}
方式2:execlp
if(execlp("ls","ls","-a","-l","/etc",NULL)<0{//会自动在PATH路劲搜索
perror("execl");
}
方式3:execv
char *arg[]={"ls","-a","-l","/etc",NULL};//将要传递的参数放在数组中
if(execv("/bin/ls",arg)<0){
perror("execv");
}
方式3:execvp
if(execvp("ls",arg)<0){
perror("execvp");//会自动在PATH路径中搜索
}
#include <stdlib.h>
int system(const char *command)
成功时返回command的返回值;失败时返回EOF(自动创建一个子进程,子进程执行命令)
当前进程(父进程)等待command执行结束后才继续执行。
六、守护进程
6.1、概念:
守护进程(Daemon)是Linux三种类型之一,通常在系统启动时运行,系统结束时关闭,Linux系统中大量使用,很多服务程序就是以守护进程形式运行。
Linux以会话(session)、进程组的方式管理进程,每个进程就属于一个进程组。而会话是一个或者多个进程组的集合。通常用户打开一个终端时,系统会创建一个临时会话,所有通过该终端运行的进程都属于这个会话。终端关闭时,所有进程会被结束。那么我们要使得守护进程与终端无关,当终端关闭的时候守护进程依旧可以运行。
进程组(Process Group): 进程集合,每个进程组有一个组长(Leader),其进程 ID 就是该进程组 ID。
会话(Session): 进程组集合,每个会话有一个组长,其进程 ID 就是该会话组 ID。
控制终端(Controlling Terminal):每个会话可以有一个单独的控制终端,与控制终端连接的 Leader 就是控制进程(Controlling Process)。
6.2、特点:
- 始终在后台运行
- 独立于任何终端
- 周期性的执行某种任务或等待处理特定事件
1、创建子进程,父进程退出
if(fork()>0){
exit(0);
}
子进程变成孤儿进程,被init进程收养
子进程在后台运行,但是依旧和终端相关联
2、子进程创建新会话
if(setsid()<0)//通过setsid创建一个新的会话
{
exit(-1);
}
子进程成为新的会话组长
子进程脱离原先的终端
3、更改当前工作目录
chdir("/");
chdir("/tmp");//所有用户可读可写可执行
守护进程一直在后台运行,其工作目录不能被卸载
重新设定当前工作目录cwd
4、重设文件权限掩码
if(umask(0)<0){
exit(-1);
}
5、关闭打开的文件描述符
int i;
for(i=0;i<3;i++)//文件描述符最小就是0。getdtablesize()返回当前进程打开最大文件数
{
close(i);
}
关闭所有从父进程继承的打开文件
已脱离终端,stdin/stdout/stderr无法再使用
创建守护进程,没隔1秒将系统时间写入文件time.log
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <time.h>
int main()
{
pid_t pid;
FILE *fp;
time_t t;
int i;
if((pid = fork())< 0)
{
perror("fork");
exit(-1);
}
if(pid > 0)
{
exit(0);
}
else if(pid == 0)
{
if(setsid()< 0)//创建一个新的会话
{
perror("setsod");
exit(-1);
}
else
{
chdir("/tmp");//tmp目录可读可写可执行
if(umask(0) < 0)//重设文件权限掩码
{
exit(-1);
}
else
{
for(i=0;i<getdtablesize();i++)
{
close(i);
}
}
while(1)
{
fp = fopen("time.log","a+");
time(&t);
fprintf(fp,"%s",ctime(&t));
fflush(fp);
sleep(1);
}
}
}
}
setsid函数:
pid_t setsid(void);
成功:返回调用进程的会话ID;失败:-1,设置errno。
调用了setsid函数的进程,既是新的会长,也是新的组长
getsid函数
pid_t getsid(pid_t pid)
成功:返回调用进程的会话ID;失败:-1,设置errno
1.pid为0表示察看当前进程session ID
2.ps ajx命令查看系统中的进程。参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。
3.组长进程不能成为新会话首进程,新会话首进程必定会成为组长进程。
getpid:pid_t getpid(void); 获取进程id
getpgid:pid_t getpgid(pid_t pid); 获取进程组id
更简便地创建守护进程: nohup 命令
nohup xxxx &
七、GDB 调试多进程程序
set follow-fork-mode child 设置GDB调试子进程
set follow-fork-mode parent 设置GDB调试父进程
set detach-on-fork on/off 设置GDB跟踪调试单个进程或多个
on: 只调试父进程或子进程的其中一个,(根据follow-fork-mode来决定),这是默认的模式
off:父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定),另一个进程会被设置为暂停状态。
info inferiors 显示GDB调试的进程
inferiors 进程序号(1,2,3…) 切换GDB调试的进程