Linux进程与状态管理及其程序设计

摘要:此为Linux中系统进程与状态管理的具体操作及其程序设计的方法来实现进程管理。

Linux 进程与状态管理:

状态查看命令

进程控制命令

进程与子进程

信号

常见状态管理命令

w: 查看负载

主要查看1分钟内的负载,需要小于CPU个数或核心数。

image-20200323150521091

vmstat: 查看负载具体位置

image-20200323151818068

free: 查看内存负载

image-20200323152023941

进程管理

ps:查看静态进程状态

常见参数: -ef 、 -aux

image-20200323152214907

ps -aux | grep 3149 :查看所以与3149有关的进程(过滤)

top: 查看准动态信息

stat: S, sleep,终端的进程,绝大多数进程都是S

stat: R ,run,运行中的进程

stat: T, terminate, 停止或者终止的进程

stat: s, 主进程

stat: l,多线程进程

image-20200323153148107

进程创建方式

多进程:唯一创建标志 –>进程号

​ 特殊进程号:1(一切进程的父进程)

父进程:把子进程创建的进程

子进程:被父进程创建的进程

生成关系: 1:n

创建进程的函数

ret = fork()

同时返回多于一个的返回值。

ret = -1 失败

ret != -1 返回两个返回值,分别为0大于0的数 ,其中大于零的返回值为子进程的进程号,所以大于零的返回值给父进程,等于零的返回值给子进程。(相对于一个给父进程,一个给子进程)

获取进程号函数

getpid():获得当前进程的进程号

getppid():获得当前进程的父进程的进程号

问题:当前进程与父进程来确定进程父子关系(√)/当前进程与子进程来确定进程父子关系(X)

答:因为一个父进程有的多个子进程,而一个子进程只有一个父进程,获得父进程编号比较容易。

创建子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <error.h>
void bail(const char *err)
{
perror(err);
exit(-1);
}
int main()
{
pid_t pid = -1;
pid_t ppid = -1;
pid_t ret = -1;
ret = fork();
if(ret < 0)
bail("fork error!\n");
else if(ret == 0)
{
printf("return = 0:pid =%d,ppid=%d.\n",getpid(),getppid());
}
else
{
printf("return > 0:pid =%d,ppid=%d.\n",getpid(),getppid());
}
return 0;
}

image-20200325103953336

可以看出来fork()函数的返回值中 大于零的返回值给:父进程 等于零的返回值给:子进程

如果生成三个子进程,下面这段代码中使用for循环会发现出错了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
for(i=0;i<3;i++)
{
ret = fork();
if(ret < 0)
bail("fork error!\n");
else if(ret == 0)
{
printf("pid =%d,ppid=%d.\n",getpid(),getppid());
}
else
{
printf("pid =%d,ppid=%d.\n",getpid(),getppid());
}
}

主要在于fork()函数创建子进程时,不但生成一个进程,子进程的结构、数据段均来自于父进程。

正确方法如下,调整子进程的部分即可:

子进程要返回:return

父进程要等待:wait

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <error.h>
void bail(const char *err)
{
perror(err);
exit(-1);
}
int main()
{
pid_t pid = -1;
pid_t ppid = -1;
pid_t ret = -1;
int i = 0;
for(i = 0;i<3;i++)
{
ret = fork();
if(ret < 0)
bail("fork error!\n");
else if(ret == 0)
{
printf("pid =%d,ppid=%d.\n",getpid(),getppid());
+ return 0;
}
else
{
- printf("pid =%d,ppid=%d.\n",getpid(),getppid());
+ wait(pid,NULL,0);
}
}
return 0;
}

image-20200325110123256

子进程调用程序(替换)

目标:执行用户指定的程序。

开始:子进程代码段数据段都来自父进程。

调用后:子进程的代码段数据段来自于被调用程序。

execl()函数

image-20200325111625439

使用子程序实现: ls -l > aaa

调用要点:调用的程序位置、名称、path+file

调用参数:不定长的字符串,字符串的个数不一定,当最后一个有效参数给定后用NULL来表示结束

例如:execl(“/bin/ls” , “ls” , “-l” , “>” , “aaa” , NULL)

先获取ls路径

image-20200325112651718

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <error.h>
#include <stdlib.h>
void bail(const char *err)
{
perror(err);
exit(-1);
}
int main()
{
pid_t pid = -1;
pid_t ppid = -1;
pid_t ret = -1;
ret = fork();
if(ret < 0)
bail("fork error!\n");
else if(ret == 0)
{
printf("return = 0:pid =%d,ppid=%d.\n",getpid(),getppid());
execl("/usr/bin/ls","ls","-l");
return ret;
}
else
{
printf("return > 0:pid =%d,ppid=%d.\n",getpid(),getppid());
wait(pid,NULL,0);
}
return 0;
}

执行就可以看到如下图所示:

image-20200325112942475

小结:

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)

进程控制与通信

信号

信号:本质上是一种软中断,系统提供唯一一种异步机制。(中断是系统进程通信的一种形式)

信号分类:标准和实时

信号俘获:

  1. 默认处理:系统自动完成
  2. 忽略处理:不处理
  3. 用户自定义:按用户定义函数处理

发送和接受:

  1. 终端输入
  2. 硬件产生
  3. 软件产生:进程调用、超时、异常。

进程控制命令:kill

功能: 发送信号给某个进程

kill <信号编号/宏命令> <进程编号>

进程根据收到的信号、执行相关的操作。

列举几个常用的:SIGINT(中断信号 CTRL+C)、 SIGQUIT(退出 CTRL+\)、 SIGKILL(结束当前进程 CTRL+Z)

image-20200330144025850

alarm函数

定时器:alarm

unsigned int alarm(unsigned int seconds);

每隔指定秒数 发生一次信号

触发alarm事件:如果同时安装多个alarm函数,则只要最后一个有效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

int main()
{
int i = 0;
alarm(2); //默认情况下是中断
alarm(3); //安装第二个定时器,同时屏蔽了上面的alarm
alarm(5); //第三个定时器,屏蔽上面所有的alarm事件,只执行最后一个
for(i = 0;i<=65535000;i++)
{
printf("i = %d\n",i);
}
return 0;

}

raise函数

发送信号给自身:raise

int raise(int sig);

sig:信号值

成功返回:0 失败返回:非0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <unistd.h>
#include <signal.h>
#include <stdio.h>

int main()
{
int i = 0;
for( i = 0;i<=65535;i++)
{
printf("i = %d \n",i);
}
raise(SIGKILL);
printf(" I am dead!");
return 0;
}

image-20200330153019792

可以看到并没有输出最后一个printf就退出了。

kill函数

给指定的进程发送信号:kill

这个和上面的kill有点不一样,这个是介绍程序中的kill,而之前那个是命令kill

int kill(pid_t pid, int sig);

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc,char** argv)
{
int i = 0;

kill(atoi(argv[1]),atoi(argv[2]));

return 0;
}

首先在后台执行一个死循环的程序,接着查看进程号,再运行kill程序就可以发现进程被杀死了。

image-20200330154559163

signal函数

指针函数和函数指针的问题:

指针函数:char* getinf()

函数指针:指向函数首地址的指针,一旦指向函数首地址则表示执行该函数。

typedef void (*sighandler_t)(int); ——> void ( * )(int); //函数指针

举个栗子:

int func (int , int) 返回值 名称 参数

int (*p) (int , int) 返回值 指针说明 参数 (p为指针变量,指向类型int ( * )(int , int)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int add(int x,int y);
int main()
{
int a = -10;
int b = -5;
int ret = 0;
int(*p)(int,int); //函数指针
p = add;
ret = (*p)(a,b); //p是地址 取出d的内容,相当于调用函数
printf("ret = %d \n",ret);
return 0;
}

int add(int x,int y)
{
return x+y;
}

接下来我们说一下signal函数:

signal函数

typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);

工作方式:

  1. 加载signal函数
  2. 等待触发
  3. 触发后执行

image-20200330153306297

调用系统函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

int main()
{
while(1)
{
signal(SIGINT,SIG_DEL);
//signal(SIGINT , SIG_IGN); //忽略sigint信号
printf("TEST\n");
}

return 0;
}

调用用户自定义函数

单个signal信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void abc(int sig)
{
printf("\n abc : signal is %d\n",sig);
}
int main()
{
signal(SIGINT,abc); //调用用户自定义函数
while(1)
{
printf("TEST\n");
sleep(1);
}

return 0;
}

image-20200401103539531

多个signal信号:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <stdio.h>
#include <signal.h>
#include <unistd.h>

void abc(int sig)
{
switch(sig)
{
case SIGINT: printf(" This is SIGINT.\n"); break;
case SIGUSR1: printf(" This is SIGUSR1.\n"); break;
case SIGUSR2: printf(" This is SIGUSR2.\n"); break;
default:
printf("NULL\n");
}
}
int main()
{
signal(SIGINT,abc);
signal(SIGUSR1,abc);
signal(SIGUSR2,abc);
while(1)
{
//signal(SIGINT , SIG_IGN);
printf("TEST\n");
sleep(1);
}

return 0;
}

image-20200401104226138

IPC

IPC:InterProcess Communication 进程间通信

管道(信道,用标识符标定,用read和write读写):

分类:

  1. 有名管道:文件系统中的标识
  2. 无名管道:无法按名称进行访问

无名管道

pipe:int pipe(int fd[2]) 成功返回0并生成文件描述

​ fd[2]:记录文件描述符( fd[0]:读端口 fd[1]:写端口 )

通信流程:

​ 读:缓冲区是否为空,如为空则进程阻塞等待。

​ 写:缓冲区是否为满,如为满则进程堵塞等待。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main()
{
int fd[2] = {0};
char buf[128] = {0};
char msg[128] = {0};
int ret = -1;

ret = pipe(fd);
ret = fork();
if (ret == 0)
{
printf("Child[%d]\n",getpid());
sprintf(msg,"%s:%d\n","child",getpid());//把打印结果显示到字符串中
sleep(2);
write(fd[1],msg,strlen(msg));
close(fd[1]);
exit(0);
}
else if(ret < 0)
{
printf("error!\n");
}
else
{
printf("Father[%d]\n",getpid());
read(fd[0],buf,sizeof(buf));
printf("recv:%s \n",buf);
close(fd[0]);
sleep(0);
exit(0);
}
}

有名管道FIFO

FIFO:First in First out

管道:缓冲区、单向、阻塞

有名管道:文件系统中的名称,是特殊文件(open、read、write、close)

创建管道:

  1. 命令: mkfifo <管道名称>
  2. 函数调用(C):mkfifo(<名称>,<访问权限>)
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc,char** argv)
{
int ret = -1;
umask(111);
ret = mkfifo(argv[1],0664);

return 0;
}

image-20200408102830668

传送信息:

tips:不可用读写权限进行打开,因为管道是单向的。只能是只读或只写。

fifoA.C:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc,char** argv)
{
int ret = -1;
int ffd = -1;
int pfd = -1;
char buf[4096] = {0};
char msg[512] = {0};

ffd = open(argv[1], O_RDONLY);
pfd = open("./FIFO",O_WRONLY);

while((ret = read(ffd,buf,sizeof(buf)))>0)
{
ret = write(pfd,buf,ret);
memset(buf,0,sizeof(buf));
}

close(ffd);
close(pfd);
return 0;
}

fifoB.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc,char** argv)
{
int ret = -1;
int pfd = -1;
char buf[4096] = {0};
char msg[512] = {0};

pfd = open("./FIFO",O_RDONLY);

while(ret = read(pfd,buf,sizeof(buf))>0)
{
puts(buf);
memset(buf,0,sizeof(buf));
}

close(pfd);
return 0;
}

image-20200408105947811

------- 本文结束  感谢您的阅读 -------