进程

所谓进程顾名思义就是描述一个程序的执行过程

进程与程序的区别

  • 程序是存储在磁盘资源中的静态指令集合没有执行的概念
  • 进程是程序的动态过程包括创建凋亡等等

并发与并行的区别

  • 并发是指在同一时间段有多个任务同时执行由操作系统的调度算法来实现比较经典的就是时间片轮转
  • 并行是指在多个处理器核心下同时执行任务比如一个cpu4核心那么可以同时在每个核心中执行任务也就是可以同时执行四个任务

进程的地址空间由内核空间和用户空间组成下面主要介绍用户空间

stack用来存储非静态的局部变量
heap用来存储动态申请的内存
.bss用来存放未初始化的全局变量
.data用来存放初始化过并且值不等于0的全局变量
.rodata用来存放只读的变量
.text用来存放程序的文本段

当用户进程需要通过内核获取资源时,会切换到内核态运行,这时当前进程会使用内核空间的资源


进程的状态

  • 运行态
  • 睡眠态

    可中断的睡眠可被信号唤醒或者等待事件或者资源就绪
    不可中断的睡眠只能等待特定的事件或者资源就绪

  • 停止态(进程暂停接受某种处理例如gdb调试断点信息处理)
  • 僵尸态(进程已经结束但是资源未被释放)

进程之间的通信
进程之间的通信方式有:管道,信号,消息队列,共享内存,网络


1.管道
管道

  • 无名管道
    无名管道用于父子进程之间通讯而且属于单向通讯
    无名管道读端与写端抽象成两个文件进行操作在无名管道创建成功之后则会返回读端与写端的文件描述符.

    创建无名管道
    int pipe(int pipefd[2]);
    特点
    1.当管道为空时读管道会阻塞读进程
    2.当管道的写端被关闭了从管道中读取剩余数据后read 函数返回 0
    3.管道的大小是有限的,不能让父/子进程同时对管道进行读/写操作
  • 有名管道
    有名管道是在 文件系统中可见的文件,但是不占用磁盘空间,仍然在内存中,可以通过 mkfifo 命令创建有名管道
    有名管道用于 任意进程之间的通讯,当管道为空时,读进程会阻塞.
    创建有名管道
    1
    2
    3
    4
    5
    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);
    //pathname : 有名管道路径名
    //mode : 有名管道文件访问权限
    优缺点
    优点可以实现任意进程间通信操作起来和文件操作一样
    缺点
    1.打开的时候需要读写一起进行否则就会阻塞管道大小是 4096个字节
    2.半双工的工作模式如果和多个进程通信则需要创建多个管道

2.信号
定义

信号是在软件层次上 是一种通知机制,对中断机制的一种模拟是一种异步通信方式

信号的种类
信号的种类有很多在linux下可以根据 kill -l命令查看由于命令很多就不在此列举了常用的一个信号是SIGKILL 该信号用来立即结束程序的运行并且不能被阻塞处理和忽略 它在操作系统中的定义如下:
#define SIGKILL 9
所以我们想杀死进程可以用
kill -9 someprocess
信号的处理流程
1.信号的发送由进程发出
2.信号的投递和处理进程发出的信号由内核接受然后投递给具体的进程处理

如图中所示处理方式有三种

  • 忽略(除SIGKILL及SIGSTOP)
  • 根据自定义处理函数进行处理
  • 执行默认操作

信号的发送
函数
kill()和raise()
信号的接收
函数
pause()//阻塞等待信号
信号处理之用户自定义函数
先了解信号处理时注册到内核的函数
sighandler_t signal(int signum, sighandler_t handler);
其后参数的sighandler_t类型为:
typedef void (*sighandler_t)(int); typedef void (*)(int) sighandler_t;
以下为一个父进程向子进程发信号子进程按照自定义处理的代码:

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
37
38
39
40
41
42
43
44
45
46
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>

void do_sig_usr(int sig)
{
printf(" Receive %s \n",strsignal(sig));
}

int main(void)
{
pid_t cpid;

// 设置信号处理方式
if(signal(SIGUSR1,do_sig_usr) == SIG_ERR){
perror("[ERROR] signal(): ");
exit(EXIT_FAILURE);
}

cpid = fork();

if(cpid == -1){
perror("fork(): ");
exit(EXIT_FAILURE);
}else if(cpid == 0){
printf("Child Process < %d > start.\n",getpid());

pause(); // 等待信号唤醒,这里需要信号到达之前进入睡眠状态

exit(EXIT_SUCCESS);
}else if(cpid > 0){

sleep(1); // 延时 1s,让子进程先执行

kill(cpid,SIGUSR1);// 给子进程发送 SIGUSR1 信号

wait(NULL);// 等待子进程退出
}

return 0;
}


子进程退出信号
因为父进程fork子进程后如果用wait阻塞等待子进程的结束就过于占用资源而且极其不方便所以可以改进一下.
我们从子进程的退出事件为着手点因为子进程的退出是异步事件即不影响父进程而且子进程退出后会自动给父进程发送 SIGCHLD 信号
我们就利用这点
想法自定义处理函数do_sig_child对SIGCHLD进行处理处理方法函数内部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
34
35
36
37
38
39
40
41
42
43
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <string.h>

void do_sig_child(int sig)
{
printf("Receive signal < %s >\n",strsignal(sig));
wait(NULL);
}

int main(void)
{
pid_t cpid;
__sighandler_t sigret;

sigret = signal(SIGCHLD,do_sig_child);
if (sigret == SIG_ERR){
perror("[ERROR] signal(): ");
exit(EXIT_FAILURE);
}

cpid = fork();
if (cpid == -1){
perror("[ERROR] fork(): ");
exit(EXIT_FAILURE);
}else if (cpid == 0){
printf("Child process < %d > start.\n",getpid());
sleep(2);
exit(EXIT_SUCCESS);
}else if (cpid > 0){
while(1){
/*nothing*/
}
}
return 0;
}