摘要:此为Linux中系统进程与状态管理的具体操作及其程序设计的方法来实现进程管理。
Linux 进程与状态管理:
状态查看命令
进程控制命令
进程与子进程
信号
常见状态管理命令
w: 查看负载
主要查看1分钟内的负载,需要小于CPU个数或核心数。
vmstat: 查看负载具体位置
free: 查看内存负载
进程管理
ps:查看静态进程状态
常见参数: -ef 、 -aux
ps -aux | grep 3149 :查看所以与3149有关的进程(过滤)
top: 查看准动态信息
stat: S, sleep,终端的进程,绝大多数进程都是S
stat: R ,run,运行中的进程
stat: T, terminate, 停止或者终止的进程
stat: s, 主进程
stat: l,多线程进程
进程创建方式
多进程:唯一创建标志 –>进程号
特殊进程号:1(一切进程的父进程)
父进程:把子进程创建的进程
子进程:被父进程创建的进程
生成关系: 1:n
创建进程的函数
ret = fork()
同时返回多于一个的返回值。
ret = -1 失败
ret != -1 返回两个返回值,分别为
0
和大于0的数
,其中大于零的返回值为子进程的进程号,所以大于零的返回值给父进程,等于零的返回值给子进程。(相对于一个给父进程,一个给子进程)
获取进程号函数
getpid():获得当前进程的进程号
getppid():获得当前进程的父进程的进程号
问题:当前进程与父进程来确定进程父子关系(√)/当前进程与子进程来确定进程父子关系(X)
答:因为一个父进程有的多个子进程,而一个子进程只有一个父进程,获得父进程编号比较容易。
创建子进程
1 |
|
可以看出来fork()函数的返回值中 大于零的返回值给:父进程
等于零的返回值给:子进程
如果生成三个子进程,下面这段代码中使用for循环会发现出错了
1 | for(i=0;i<3;i++) |
主要在于fork()函数创建子进程时,不但生成一个进程,子进程的结构、数据段均来自于父进程。
正确方法如下,调整子进程的部分即可:
子进程要返回:return
父进程要等待:wait
1 | #include <unistd.h> |
子进程调用程序(替换)
目标:执行用户指定的程序。
开始:子进程代码段数据段都来自父进程。
调用后:子进程的代码段数据段来自于被调用程序。
execl()函数
使用子程序实现: ls -l > aaa
调用要点:调用的程序位置、名称、path+file
调用参数:不定长的字符串,字符串的个数不一定,当最后一个有效参数给定后用NULL来表示结束
例如:execl(“/bin/ls” , “ls” , “-l” , “>” , “aaa” , NULL)
先获取ls路径
1 |
|
执行就可以看到如下图所示:
小结:
execl()函数
execl(“/bin/ls” , “ls” , “-l” , “>” , “aaa” , NULL)
execlp()函数
例如:execlp(“ls”,”ls”,”-l”,NULL)
写的时候只要给出调用程序的名称,不要给出路径,系统会在默认路径中查找
execv()函数(v:vector向量)
例如:
const char* args[] = {“ls”,”-l”,”>”,”aaa”,NULL}
execv(“/bin/ls”,args)
execvp()函数
例如:
const char* args[] = {“ls”,”-l”,”>”,”aaa”,NULL}
execvp(“ls”,args)
进程控制与通信
信号
信号:本质上是一种软中断,系统提供唯一一种异步机制。(中断是系统进程通信的一种形式)
信号分类:标准和实时
信号俘获:
- 默认处理:系统自动完成
- 忽略处理:不处理
- 用户自定义:按用户定义函数处理
发送和接受:
- 终端输入
- 硬件产生
- 软件产生:进程调用、超时、异常。
进程控制命令:
kill
功能: 发送信号给某个进程
kill <信号编号/宏命令> <进程编号>
进程根据收到的信号、执行相关的操作。
列举几个常用的:SIGINT(中断信号 CTRL+C)、 SIGQUIT(退出 CTRL+\)、 SIGKILL(结束当前进程 CTRL+Z)
alarm函数
定时器:alarm
unsigned int alarm(unsigned int seconds);
每隔指定秒数 发生一次信号
触发alarm事件:如果同时安装多个alarm函数,则只要最后一个有效
1 |
|
raise函数
发送信号给自身:raise
int raise(int sig);
sig:信号值
成功返回:0 失败返回:非0
1 |
|
可以看到并没有输出最后一个printf就退出了。
kill函数
给指定的进程发送信号:kill
这个和上面的kill有点不一样,这个是介绍程序中的kill,而之前那个是命令kill
int kill(pid_t pid, int sig);
1 |
|
首先在后台执行一个死循环的程序,接着查看进程号,再运行kill程序就可以发现进程被杀死了。
signal函数
指针函数和函数指针的问题:
指针函数:char* getinf()
函数指针:指向函数首地址的指针,一旦指向函数首地址则表示执行该函数。
typedef void (*sighandler_t)(int); ——> void ( * )(int); //函数指针
举个栗子:
int func (int , int) 返回值 名称 参数
int (*p) (int , int) 返回值 指针说明 参数 (p为指针变量,指向类型int ( * )(int , int)
1 |
|
接下来我们说一下signal函数:
signal函数
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
工作方式:
- 加载signal函数
- 等待触发
- 触发后执行
调用系统函数
1 |
|
调用用户自定义函数
单个signal信号:
1 | #include <stdio.h> |
多个signal信号:
1 |
|
IPC
IPC:InterProcess Communication 进程间通信
管道(信道,用标识符标定,用read和write读写):
分类:
- 有名管道:文件系统中的标识
- 无名管道:无法按名称进行访问
无名管道
pipe:int pipe(int fd[2]) 成功返回0并生成文件描述
fd[2]:记录文件描述符( fd[0]:读端口 fd[1]:写端口 )
通信流程:
读:缓冲区是否为空,如为空则进程阻塞等待。
写:缓冲区是否为满,如为满则进程堵塞等待。
1 | #include <unistd.h> |
有名管道FIFO
FIFO:First in First out
管道:缓冲区、单向、阻塞
有名管道:文件系统中的名称,是特殊文件(open、read、write、close)
创建管道:
- 命令: mkfifo <管道名称>
- 函数调用(C):mkfifo(<名称>,<访问权限>)
1 |
|
传送信息:
tips:不可用读写权限进行打开,因为管道是单向的。只能是只读或只写。
fifoA.C:
1 |
|
fifoB.c
1 |
|