所谓进程,顾名思义,就是描述一个程序的执行过程。
进程与程序的区别:
- 程序是存储在磁盘资源中的静态指令集合,没有执行的概念
- 进程是程序的动态过程,包括创建,凋亡等等
并发与并行的区别:
- 并发是指在同一时间段有多个任务同时执行,由操作系统的调度算法来实现,比较经典的就是时间片轮转。
- 并行是指在多个处理器核心下同时执行任务,比如一个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);
|
优缺点
优点:可以实现任意进程间通信,操作起来和文件操作一样
缺点:
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);
kill(cpid,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){ } } return 0; }
|