进程的由来
程序
静态文件
进程
运行着的实体
查看进程之间的关系
pstree
操作系统如何区分进程
PID:进程的身份证
创建一个新进程
fork函数
头文件:
#include <unistd.h>
函数原型:
pid_t fork(void);
返回值:
成功 :0或其他正整数
失败:-1
fork函数特性
-
执行fork函数之后,fork函数会返回两次
-
在旧进程中返回时,返回值为0
-
在新进程返回时,返回值为进程的pid
叫做复制一个进程更加贴切
fork函数要点总结
在执行fork函数之前,操作系统只有一个进程,fork函数之前的代码只会被执行一次。
在执行fork函数之后,操作系统有两个几乎一样的进程,fork函数之后的代码会被执行两次。
子进程偷梁换柱
exec函数族
常用后缀:
l:代表以列表形式传参(list)
v:代表以矢量数组形式传参(vector)
p:代表使用环境变量Path来寻找指定执行文件
e:代表用户提供自定义的环境变量
头文件:
#include <unistd.h>
函数原型:
int execl(const char *path, const char *arg, ...)
int execlp(const char *file, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execve(const char *path, char *const argv[],char *const envp[])
返回值:
成功:不返回
失败:-1
要点总结
-
l后缀和v后缀必须两者选其一来使用
-
p后缀和e后缀是可选的,可用可不用
-
组合后缀的相关函数还有很多,可自己进一步了解
exce函数有可能执行失败,需要预防
-
新程序的文件路径出错
-
传参或者是自定义环境变量时,没有加NULL
-
新程序没有执行权限
进程的退出
正常退出:
-
从main函数return
-
调用exit()函数终止
-
调用_exit()函数终止
exit和_exit退出函数
头文件:
#include <unistd.h> #include <stdlib.h>
原型:
void _exit(int status); void exit(int status);
返回值:
不返回
等待子进程的终结
wait函数
头文件
#include <sys/wait.h>
函数原型
pid_t wait(int *status)
返回值长度
-
成功:退出的子进程的pid
-
失败:-1
处理子进程退出状态值的宏
-
WIFEXITED(status) :如果子进程正常退出,则该宏为真
-
WEXITSTATUS(status):如果子进程正常退出,则该宏获取子进程的退出值
进程的"生老病死"
进程状态:
-
TASK_RUNNING:就绪/运行状态
-
TASK_INTERRUPTIBLE:可中断睡眠状态
-
TASK_UNINTERRUPTIBLE:不可中断睡眠状态
-
TASK_TRACED:调试态
-
TASK_STOPPED:暂停状态
-
EXIT_ZOMBIE:僵死状态
-
EXIT_DEAD:死亡态
进程组、会话、终端
进程组
作用:对相同类型的进程进行管理
进程组的诞生
-
在shell里面直接执行一个应用程序,对于大部分进程来说,自己就是进程组的首进程。进程组只有一个进程
-
如果进程调用了fork函数,那么父子进程同属一个进程组,父进程为首进程
-
在shell中通过管道执行连接起来的应用程序,两个程序同属一个进程组,第一个程序为进程组的首进程
进程组id:pgid,由首进程pid决定
会话
作用:管理进程组
会话的诞生
-
调用setsid函数,新建一个会话,应用程序作为会话的第一个进程,称为会话首进程
-
用户在终端正确登录之后,启动shell时linux系统会创建一个新的会话,shell进程作为会话首进程
会话id:会话首进程id,SID
前台进程组
shell进程启动时,默认是前台进程组的首进程。
前台进程组的首进程会占用会话所关联的终端来运行,shell启动其他应用程序时,其他程序成为首进程
后台进程组
后台进程中的程序是不会占用终端
在shell进程里启动程序时,加上&符号可以指定程序运行在后台进程组里面
ctrl+z
jobs:查看有哪些后台进程组
fg+job id可以把后台进程组切换为前台进程组
终端
-
物理终端
-
串口终端
-
lcd终端
-
-
伪终端
-
ssh远程连接产生的终端
-
桌面系统启动的终端
-
-
虚拟终端
linux内核自带的,ctrl+alt+f0~f6可以打开7个虚拟终端
守护进程
会话用来关联前后台进程组
会话一般关联着一个终端
当终端被关闭之后,会话中的所有进程都会被关掉
守护进程
不受终端影响,就算终端退出,也可以继续在后台运行
如何来写一个守护进程
1、创建一个子进程,让父进程直接退出
方法:通过fork函数
2、创建一个新的会话,摆脱终端的影响
方法:通过setsid()函数
3、改变守护进程的当前工作目录,改为“/”
方法:通过chdir()函数
4、重设文件权限掩码
新建文件权限受文件权限掩码影响
umask:022:只写
新建文件默认执行权限:666
真正的文件权限:666&~umask
方法:通过umask函数
5、关闭不需要的文件描述符
0,1,2:标准输入、输出、出错
方法:通过close函数
普通进程伪装成守护进程:
nohup
ps命令详解
aux
axjf
-
a:显示一个终端所有的进程
-
u:显示进程的归属用户及内存使用情况
-
x:显示没有关联控制终端的进程
-
j:显示进程归属的进程组id、会话id、父进程id
-
f:以ascii形式显示出进程的层次关系
ps aux
-
user:进程是哪个用户产生的
-
pid:进程的身份证号码
-
%cpu:表示进程占用了cpu计算能力的百分比
-
%mem:表示进程占用了系统内存的百分比
-
vsz:进程使用的虚拟内存大小
-
rss:进程使用的物理内存大小
-
tty:表示进程关联的终端
-
stat:表示进程当前状态
-
start:表示进程的启动时间
-
time:记录进程的运行时间
-
command:表示进程执行的具体程序
ps axjf
-
ppid:表示进程的父进程id
-
pid:进程的身份证号码
-
pgid:进程所在进程组的id
-
sid:进程所在会话的id
-
tty:表示进程关联的终端
-
tpgid:值为-1,表示进程为守护进程
-
stat:表示进程当前状态
-
uid:启动进程的用户id
-
time:记录进程的运行时间
-
command:表示进程的层次关系
使用场景
关注进程本身:ps aux
关注进程间的关系:ps axjf
常见的状态有以下几种:
-D:不可被唤醒的睡眠状态,通常用于I/O情况
-R:该进程正在运行
-S:该进程处于睡眠状态,可被唤醒
-T:停止状态,可能是在后台暂停或进程处于出错状态
-X:死掉的进程
-Z:僵尸状态
-N:低优先级
-s:进程是会话首进程
-I:多进程(小写L)
-+:位于后台
僵尸进程和托孤进程
进程的正常退出步骤:
-
子进程调用exit()函数退出
-
父进程调用wait()函数为子进程处理其他事情
僵尸进程
子进程退出后,父进程没有调用wait()函数处理身后事,子进程变成僵尸进程
托孤进程
父进程比子进程先退出,子进程变孤儿
什么是进程间通信(ipc)
进程间通信
-
数据传输
-
资源共享
-
事件通知
-
进程控制
Linux系统下的ipc
-
早期unix系统ipc
-
管道
-
信号
-
fifo
-
-
system-v ipc(贝尔实验室)
-
system-v 消息队列
-
system-v 信号量
-
system-v 共享内存
-
-
socket ipc(BSD)
-
posix ipc(IEEE)
-
posix 消息队列
-
posix 信号量
-
posix 共享内存
-
无名管道
pipe函数
头文件:
#include <unistd.h>
函数原型:
int pipe(int pipefd[2]);
返回值:
成功:0
失败:-1
特点
-
特殊文件(没有名字),无法使用open,但是可以使用close。
-
只能通过子进程继承文件描述符的形式来使用
-
write和read操作可能会阻塞进程
-
所有文件描述符被关闭之后,无名管道被销毁
使用步骤
-
父进程pipe无名管道
-
fork子进程
-
close无用端口
-
write/read读写端口
-
close读写端口
有名管道
mkfifo函数
头文件:
#include <sys/types.h> #include <sys/state.h>
函数原型:
int mkfifo(const char *filename,mode_t mode)
返回值:
成功:0
失败:-1
特点
-
有文件名,可以使用open函数打开
-
任意进程间数据传输
-
write和read操作可能会阻塞进程
-
write具有"原子性"
使用步骤
-
第一个进程mkfifo有名管道
-
open有名管道,write/read数据
-
close有名管道
-
第二个进程open有名管道,read/write数据
-
close有名管道
信号简介
信号的基本概念
软件模拟中断,进程接受信号后做出相应响应
怎么产生信号?
-
硬件
-
执行非法指令
-
访问非法内存
-
驱动程序
-
...
-
-
软件
-
控制台:
-
ctrl+c:中断信号
-
ctrl+|:退出信号
-
ctrl+z:停止信号
-
-
kill命令
-
程序调用kill()函数
-
信号的处理方式:
-
忽略:进程当信号从来没有发生过
-
捕获:进程会调用相应的处理函数,进行相应的处理
-
默认:使用系统默认处理方式来处理信号
常用信号分析
信号名 | 信号编号 | 产生原因 | 默认处理方式 |
---|---|---|---|
SIGHUP | 1 | 关闭终端 | 终止 |
SIGINT | 2 | ctrl+c | 终止 |
SIGQUIT | 3 | ctrl+\ | 终止+转储 |
SIGABRT | 6 | abort() | 终止+转储 |
SIGPE | 8 | 算术错误 | 终止 |
SIGKILL | 9 | kill -9 pid | 终止,不可捕获/忽略 |
SIGUSR1 | 10 | 自定义 | 忽略 |
SIGSEGV | 11 | 段错误 | 终止+转储 |
SIGUSR2 | 12 | 自定义 | 忽略 |
SIGALRM | 14 | alarm() | 终止 |
SIGTERM | 15 | kill pid | 终止 |
SIGCHLD | 17 | (子)状态变化 | 忽略 |
SIGTOP | 19 | ctrl+z | 暂停,不可捕获/忽略 |
pkill命令
ps -A:去查看所有进程
ps -e:也是查看所有进程
ps -u root:查看root用户的所有进程
ps -aux:显示所有包含其他使用者的进程
%CPU:占用的CPU使用率 top 命令查看服务器资源的使用情况
STAT:进程的运行状态:
D: 无法中断的休眠状态(通常IO的进程) dead
R:正在执行中 running
S : 静止状态
T : 暂停执行
Z:不存在但暂时无法消除
signal_kill_raise函数
signal函数
头文件:
#include <signal.h>
函数原型:
typedef void (*sighandler_t)(int); sighandler_t signal(int signum,sighandler_t handler);
参数:
-
signum: 要设置的信号
-
handler:
-
SIG_IGN:忽略(ignore)
-
SIG_DFL:默认 (default)
-
void (*sighandler_t)(int):自定义
-
返回值:
成功:上一次设置的handler
失败:SIG_ERR
kill函数
头文件:
#include <sys/types.h> #include <signal.h>
原型函数:
int kill(pid_t pid,int sig);
参数:
-
pid:进程id
-
sig:要发送的信号
返回值:
成功:0
失败:-1
raise函数
头文件:
#include <signal.h>
原型函数:
int raise(int sig);
参数:
sig:发送信号
返回值:
成功:0
失败:非0
信号集处理函数
屏蔽信号集
屏蔽某些信号
-
手动
-
自动
未处理信号集
信号如果被屏蔽,则记录在未处理信号集中
-
非实时信号(1~31),不排队,只留一个
-
实时信号(34~64),排队,保留全部
信号集相关API
-
int sigemptyset(sigset_t *set);
-
将信号集合初始化为0
-
-
int sigfillset(sigset_t *set);
-
将信号集合初始化为1
-
-
int sigaddset(sigset_t *set,int signum);
-
将信号集合某一位设置为1
-
-
int sigdelset(sigset_t *set,int signum);
-
将信号集合某一位设置为0
-
-
int sigprocmask(int how,const sigset_t *set,sigset_t *oldset);
-
使用设置好的信号集去修改信号屏蔽集
-
参数how:
-
SIG_BLOCK:屏蔽某个信号(屏蔽集 | set)
-
SIG_UNBLOCK:打开某个信号(屏蔽集 & (~set))
-
SIG_SETMASK:屏蔽集 = set
-
-
参数oldset:保存就的屏蔽集的值,NULL表示不保存
-
system-V 消息队列
system-V ipc特点
-
独立于进程
-
没有文件名和文件描述符
-
IPC对象具有key和ID
消息队列用法
-
定义一个唯一key(ftok)
-
构造消息对象(msgget)
-
发送特定类型消息(msgsnd)
-
接受特定类型消息(msgrcv)
-
删除消息队列(msgctl)
ftok函数
功能:获取一个key
函数原型:
key_t ftok(const char *path,int proj_id)
参数:
-
path:一个合法路径
-
proj_id:一个整数
返回值:
成功:合法键值
失败:-1
msgget函数
功能:获取消息队列ID
函数原型:
int msgget(key_t key,int msgflg)
参数:
-
key:消息队列的键值
-
msgflg:
-
IPC_CREAT:如果消息队列不存在,则创建
-
mode:访问权限
-
返回值:
成功:该消息队列的ID
失败:-1
msgsnd函数
功能:发送消息到消息队列
函数原型:
int msgsnd(int msqid,const void *msgp,size_t msgsz,int msgflg);
参数:
-
msqid:消息队列ID
-
msgp:消息缓存区
-
struct msgbuf
{
long mtype; //消息标识
char mtext[1]; //消息内容
}
-
-
msgsz:消息正文的字节数
-
msgflg:
-
IPC_NOWAIT:非阻塞发送
-
0:阻塞发送
-
返回值:
成功:0
失败:-1
msgrcv函数
功能:从消息队列读取消息
函数原型:
ssize_t msgrcv(int msqid,void *msgp,size_t msgsz,long msgtyp,int msgflg)
参数:
-
msqid:消息队列ID
-
msgp:消息缓存区
-
msgsz:消息正文的字节数
-
msgtyp:要接受消息的标识
-
msgflg:
-
IPC_NOWAIT:非阻塞读取
-
MSG_NOERROR:截断消息
-
0:阻塞读取
-
返回值:
成功:0
失败:-1
msgctl函数
功能:设置或获取消息队列的相关属性
函数原型:
int msgctl(int msgqid,int cmd,struct maqid_ds *buf)
-
msgqid:消息队列的ID
-
cmd
-
IPC_STAT:获取消息队列的属性信息
-
IPC_SET:设置消息队列的属性
-
IPC_RMID:删除消息队列
-
-
buf:相关结构体缓冲区
system-V 信号量
本质
计数器
作用
保护共享资源
-
互斥
-
同步
信号量用法
-
定义一个唯一key(ftok)
-
构造一个信号量(semget)
-
初始化信号量(semctl SETVA)
-
对信号量进行P/V操作(semop)
-
删除信号量(semctl RMID)
semget函数
功能:获取信号量的ID
函数原型:
int semget(key_t key,int nsems,int semflg)
参数:
-
key:信号量键值
-
nsems:信号量数量
-
semflg:
-
IPC_CREATE:信号量不存在则创建
-
mode:信号量的权限
-
返回值:
成功:信号量ID
失败:-1
semctl函数
功能:获取或设置信号量的相关属性
函数原型:
int semctl(int semid,int semnum,int cmd,union semun arg)
参数:
-
semid:信号量ID
-
semnum:信号量编号
-
cmd:
-
IPC_STAT:获取信号量的属性信息
-
IPC_SET:设置信号量的属性
-
IPC_RMID:删除信号量
-
IPC_SETVAL:设置信号量的值
-
-
arg:
union semun { int val; struct semid_ds *buf; }
返回值:
成功:由cmd类型决定
失败:-1
semop函数
函数原型:
int semop(int semid,struct sembuf *sops,size_t nsops)
参数:
-
semid:信号量ID
-
sops:信号量操作结构体数组
struct sembuf
{
short sem_num; //信号量编号
short sem_op;//信号量P/V操作
short sem_flg; //信号量行为,SEM_UNDO
}
-
nsops:信号量数量
-
返回值:
成功:0
失败:-1
system-V 共享内存
作用
高效率传输大量数据
共享内存用法
-
定义一个唯一key(ftok)
-
构造一个共享内存对象(shmget)
-
共享内存映射(shmat)
-
解除共享内存映射(shmdt)
-
删除共享内存(shmctl RMID)
shmget函数
功能:获取共享内存对象的ID
函数原型:
int shmget(key_t key,int size,int shmflg)
参数:
-
key:共享对象键值
-
nsems:共享内存大小
-
shmflg:
-
IPC_CREATE:共享内存不存在则创建
-
mode:共享内存的权限
-
返回值:
成功:共享内存ID
失败:-1
shmat函数
功能:映射共享内存
函数原型:
int shmat(int shmid,const void *shmaddr,int shmflg)
参数:
-
shmid:共享内存ID
-
shmaddr:映射地址,NULL为自动分配
-
shmflg:
-
SHM_RDONLY:只读方式映射
-
0:可读可写
-
返回值:
成功:共享内存首地址
失败:-1
shmdt函数
功能:解除共享内存映射
函数原型:
int shmdt(const void *shmaddr)
参数:
shmaddr:映射地址
返回值:
成功:0
失败:-1
shmctl函数
功能:获取或设置共享内存的相关属性
函数原型:
int shmctl(int shmid,int cmd,struct shmid_ds *buf)
参数:
-
shmid:共享内存ID
-
cmd:
-
IPC_STAT:获取共享内存的属性信息
-
IPC_SET:设置共享内存的属性
-
IPC_RMID:删除共享内存
-
-
buf:属性缓冲区
返回值:
成功:由cmd类型决定
失败:-1