Linux 学习记录24(进程线程篇)
一、进程
1. 特殊进程(PID: 0,1,2)
- 0进程(idel进程):是linux系统启动后的第一个进程,这个进程也叫空闲进程当没有其他进程执行时回执行该进程
- 1进程(init进程):他是在0号进程产生,这个进程主要用于硬件的初始化工作,也是其他进程的父进程,可以完成对一些进程的收尸工作
- 2进程(kthreadd进程):也称为调度进程,这个进程也是由0号进程产生,他的任务是完成任务调度工作
2. 进程相关命令
(1. ps指令
- ps -ef:可以显示进程间的关系
UID:用户ID
PID:当前进程的ID
PPID:当前进程的父进程的ID
C:目前不用关注
TIME:运行日期
TTY:如果显示问号,则说明没有终端与该进程对应
CMD:进程名
- ps -ajx:查看进程的运行状态
PGID: 组id号
SID:会话ID号
TPGID:如果值为-1说明是守护进程(服务进程)
STAT:进程的状态
- ps -aux:显示进程在计算机资源的占比
(2. kill指令
- kill -l:查看系统提供的信号有那些
ctrl+c 等同 2) SIGINT
ctrl+z 等同 19) SIGSTOP
9) SIGKILL 杀死一个进程
18) SIGCONT 继续一个进程
前30个是稳定信号,后面的是不稳定信号
- killall 进程名:杀死所有的该进行名对应的进程>>
程序
终端
杀死
- kill -信号号 pid:给进程发送信号
(3. 查看进程号
pidof 进程名:给进程发送信号
(4. 动态显示进程状态
top
(5. 进程树
pstree
二、进程的状态
1. 进程状态描述
进程普通状态
D uninterruptible sleep (usually IO) 不可中断
R running or runnable (on run queue) 运行/可运行状态
S interruptible sleep (waiting for an event to complete) 可终断的睡眠态
T stopped by job control signal 停止态
t stopped by debugger during the tracing 调试停止态
W paging (not valid since the 2.6.xx kernel) 弃用
X dead (should never be seen) 死亡态,不能被看到
Z defunct (“zombie”) process, terminated but not reaped by its parent 僵尸态,子进程结束,父进程没有为其收尸进程附加状态
- < high-priority (not nice to other users) 高优先级进程
- N low-priority (nice to other users) 低优先级进程
- L has pages locked into memory (for real-time and custom IO) 加锁的进程,不可用放入swap分区内
- s is a session leader 会话组组长进程
- l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) 多线程的进程
- + is in the foreground process group 前台进程
2. 进程切换
3. 进程状态切换的实例
在程序运行时查看经常状态
程序
状态
ctrl + z 进入停止态
恢复运行态 kill -18 PID 恢复到运行态并后台运行
恢复运行态 kill -19 PID 改为停止态查看作业号
查看作业号bg 有&说明是后台运行
将后台运行变为前台运行
作业号fg 作业号 以将后台运行的进程切换成前台运行,如果后台只有一个进程,则直接执行fg即可
(1. 休眠进程的切换
休眠状态
运行时的状态(为前台休眠状态)
切换为停止态
+bg 将停止态的进程变为后台休眠态
+fg将后台休眠态改为前台休眠态
三、进程的创建
进程创建过程,是通过拷贝父进程得到的,进程的内核空间中是通过task_struct结构体表述的,新经常在创建过程中,直接拷贝父进程的该结构体得到,只需要稍微修改即可,保留了父进程的的大部分遗传信息
1. 进程创建函数fork
注意事项:
子进程和父进程在不同的内存空间在运行,同时其中一个进程的运行不会影响另外一个进程的运行
父进程会拷贝一份资源给子进程,他们的资源是一致的,但是子进程不会执行fork之前的语句
父子进程先执行哪一个是不确定的,由CPU的调度机制决定
子进程会拷贝父进程的虚拟空间,父子进程的0-3G的空间内容完全一致,3-4G的内核空间共享
函数原型:pid_t fork(void);
功能:拷贝父进程得到一个子进程(吵架呢子进程)
参数:无
返回值:
成功:父进程会得到子进程的PID,子进程会得到0
失败:父进程得到-1,并置位错误码
2. 使用fork创建进程(不关注返回值)
(1. 创建单个进程
int main(int argc, char const *argv[])
{
sleep(10);//休眠10s
fork();
while (1)
return 0;
}
运行:
在前10秒休眠期,只有父进程,并处于休眠期,同时父进程PID为5975
10秒后,执行fork(),得到子进程PID为5990,此时main.out相当于有两个进程同时执行while(1),并且两个进程都处于运行态
(2. 创建多个进程
int main(int argc, char const *argv[])
{
fork();
fork();
while (1);
return 0;
}
运行
可以看到产生了4个进程
- 当执行第一个fork:先由终端进程PID5907,产生他的子进程PID6041,执行完成后共有两个进程:进程6041,进程6042
- 当执行第二个fork:此时有两个进程在同时运行,分别为进程6041和他的子进程6042。他们同时运行了第二个fork,所以又各自创建了一个子进程:进程6041创建了进程6042以及进程6042创建了进程6044
- 最终得到的进程:进程6041,进程6042,进程6043,进程6044
3. 使用fork创建进程(关注返回值)
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork error: ");
return -1;
}else if(pid == 0)
{//此部分为子进程的代码
printf("这是子进程\r\n");
}else
{//此部分为父进程的代码
printf("这是父进程\r\n");
}
printf("hello world\r\n");
while (1);
return 0;
}
运行:
进程:
4. fork出来的进程与原进程的执行先后顺序
子进程和父进程执行没有先后顺序,原因是系统进行时间片轮询机制,上下文切换原则,调度到哪个就执行哪个
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork error: ");
return -1;
}else if(pid == 0)
{//此部分为子进程的代码
while(1)
printf("这是子进程\r\n");
}else
{
while(1)
printf("这是父进程\r\n");
}
printf("hello world\r\n");
while (1);
return 0;
}
5. fork出来的进程与原进程内存问题
子进程在写数据时才会创建出子进程的资源空间
6. 父子进程的进程号获取
函数原型:
pid_t getpid(void);
pid_t getppid(void);
功能:获取自身进程的PID或PPID
参数:无
返回值:
不可能调用失败:成功返回进程号
============================================
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork error: ");
return -1;
}else if(pid == 0)
{//此部分为子进程的代码
printf("子进程PID:%d\r\n",getpid());
printf("子进程PPID:%d\r\n",getppid());
}else
{
printf("父进程PID:%d\r\n",getpid());
printf("父进程PPID:%d\r\n",getppid());
}
printf("hello world\r\n");
while (1);
return 0;
}
运行:
7. 进程的退出(exit/_exit)
主要区别在于是否会刷新缓冲区
(1. exit
库函数:void exit(int status);
功能:结束进程,同时刷新缓冲区
参数:退出时的状态
EXIT_FAILURE:0 成功
EXIT_SUCCESS:1 失败
返回值:无
(2. _exit
系统调用:void _exit(int status);
功能:结束进程,不会刷新缓冲区
参数:退出时的状态
EXIT_FAILURE:0 成功
EXIT_SUCCESS:1 失败
返回值:无
8. 进程资源回收(wait/waitpid)
僵尸进程:子进程已结束,父进程未收尸,这子进程就是僵尸进程
孤儿进程:父进程已经结束,子进程则成了孤儿进程,被1进程收养,子进程结束后,有1进程为其收尸
1. 如果不使用wait或waitpid会产生僵尸进程
2. 所以要使用wait或waitpid回收资源
(1. wait
所需头文件:#include <sys/wait.h>
函数原型:pid_t wait(int *wstatus);
功能:阻塞等待回收子进程资源
参数1:子进程使用exit/_exit退出时的状态,一般不用接收,直接传进去NULL就行
返回值:成功返回回收了资源的子进程的pid,失败返回-1,并置位错误码
=======================================================================
int main(int argc, char const *argv[])
{
pid_t pid = fork();
if(pid < 0)
{
perror("fork error: ");
return -1;
}else if(pid == 0)
{//此部分为子进程的代码
printf("子进程PID:%d\r\n",getpid());
printf("子进程结束\r\n");
exit(EXIT_FAILURE);
}else
{
printf("父进程PID:%d\r\n",getpid());
wait(NULL);
while(1);
}
printf("hello world\r\n");
while (1);
return 0;
}
运行
(2. waitpid
函数原型:pid_t waitpid(pid_t pid, int *wstatus, int options);
功能:可以阻塞也可以不阻塞等待回收子进程号为pid的进程资源
参数1:要回收的子进程的pid号
小于-1:回收绝对值与pid相等的同一组id下的所有子进程
等于-1:回收任意一个子进程《常用)
等于 0:回收多个pid中的一个,与当前进程所在的同一组的任意一个进程《常用)
大于 0:回收值为pid的进程
参数2:子进程使用exit/ exit退出时的状态,一般不用接收,直接传进去NULL就行
参数3:
0:阻塞等待回收资源
WNOHANG :非阻塞等待回收资源
返回值:
成功返回回收了资源的子进程的pid,如果是非阳塞等待,并且没有子进程结束,则返回0
失败返回-1并置位错误码
思维导图
练习
1. 使用多线程拷贝文件
主函数
#include "public.h"
int main(int argc, char const *argv[])
{
struct stat sb;
/*获取文件属性*/
stat("./01file.txt", &sb);//打开文件
pid_t pid1 = fork();//创建子进程
if(pid1 < 0)
{
perror("fork error: ");
return -1;
}else if(pid1 == 0)
{//此部分为子进程的代码
printf("子进程PID:%d\r\n",getpid());
/*子进程负责上半部分的拷贝*/
Subprocess(sb.st_size);
}else
{
printf("父进程PID:%d\r\n",getpid());
/*父进程负责下半部分的拷贝*/
Parent_process(sb.st_size,pid1);
}
return 0;
}
头文件
#ifndef __PUBLIC_H_
#define __PUBLIC_H_
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/wait.h>
#define R O_RDONLY
#define W O_WRONLY|O_CREAT|O_TRUNC
#define A O_WRONLY|O_CREAT|O_ACCMODE
#define Rp O_RDWR
#define Wp O_RDWR|O_CREAT|O_TRUNC
#define Ap O_RDWR|O_CREAT|O_TRUNC
/*子进程*/
int Subprocess(int file_size);
/*父进程*/
int Parent_process(int file_size,pid_t pid);
#endif
子进程
/*子进程*/
int Subprocess(int file_size)
{
int fp1;
int fp2;
int i = 0;
char ch = 0;
if((fp1 = open("./01file.txt",R)) == -1)
{//文件打开失败
perror("open file");
return -1;
}
if((fp2 = open("./02file.txt",Wp)) == -1)
{//文件打开失败
perror("open file");
return -1;
}
for(i = 0;i<(file_size/2);i++)
{
read(fp1,&ch,1);//读文件
write(fp2,&ch,1);
}
close(fp1);
printf("子进程任务结束 关闭进程\r\n");
exit(EXIT_FAILURE);
}
父进程
/*父进程*/
int Parent_process(int file_size,pid_t pid)
{
int fp1;
int fp2;
int i = 0;
char ch = 0;
int dt = file_size-(file_size/2);
if((fp1 = open("./01file.txt",R)) == -1)
{//文件打开失败
perror("open file");
return -1;
}
if((fp2 = open("./02file.txt",Wp)) == -1)
{//文件打开失败
perror("open file");
return -1;
}
/*两个光标向后偏移*/
lseek(fp1,dt,SEEK_SET);
lseek(fp2,dt,SEEK_SET);
for(i = 0;i<dt;i++)
{
read(fp1,&ch,1);//读文件
write(fp2,&ch,1);
}
close(fp1);
while (1)
{//等待子进程结束
if(waitpid(pid,NULL,WNOHANG )!=0 && pid!=0)
{
printf("子进程回收完成\r\n");
pid=0;
break;
}
}
printf("父进程任务结束 关闭进程\r\n");
exit(EXIT_FAILURE);
}
运行结果
文件1
文件2