线程VS进程之进程
1. 进程与程序
程序的定义:程序是指令、数据及其组织形式的描述,是一个在时间上按照严格次序前后相继的操作序列。
进程的定义:进程是计算机中处于运行中程序的实体,是可并发执行的程序在数据集上的一次执行程。
进程与程序的主要区别:
(1)程序是永存的;进程是暂时存在的。即进程是有生命周期的,创建,执行,撤销等。
(2)程序是静态的观念,进程是动态的观念;、
(3)一个程序可对应多个进程; 一个进程可以执行一个程序或多个程序
(4)进程具有并发性,而程序没有;
(5)进程是竞争计算机资源的基本单位,程序不是。
进程结构:
每个进程所分配的内存由很多部分组成,通常称之为“段(segment)”:
1、文本段:
包含了进程运行的程序机器语言指令。文本段具有只读属性,以防止进程通过错误指针意外修改自身指令。因为多个进程可同时运行同一程序,所以又将文本段设为可共享,这样,一份程序代码的拷贝可以映射到所有这些进程的虚拟地址空间中。
2、初始化数据段:
包含显示初始化的全局变量和静态变量。当程序加载到内存时,从可执行文件中读取这些变量的值。
3、未初始化数据段:
包含了未进行显示初始化的全局变量和静态变量。程序启动之前,系统将本段内所有内存初始化为0。出于历史原因,此段常被称为BSS段,这源于老版本的汇编语言助记符“block started by symbol”。将经过初始化的全局变量和静态变量与未初始化的全局变量和静态变量分开存放,其主要原因在于程序在磁盘上存储时,没有必要为未经初始化的变量分配存储空间。相反,可执行文件只需记录未初始化数据段的位置及所需大小,直到运行时再由程序加载器来分配空间。
4、栈(stack):
是一个动态增长和收缩的段,有栈帧(stack frames)组成。系统会为每个当前调用的函数分配一个栈帧。栈帧中存储了函数的局部变量(所谓自动变量)、实参和返回值。
5、堆(heap):
是可在运行时(为变量)动态进行内存分配的一块区域。堆顶端称为program break。
对于初始化和未初始化的数据段而言,不太常用、但表达更清晰的称为分别是用户初始化数据段(user-initialized data segment)和零初始化数据段(zero-initialized data segment)。
在大多数Unix(包括Linux)中的C语言编程环境提供了3个全局符号(symbol):etext、edata、end,可以在程序中使用这些符号以获取相应程序文本段、初始化数据段和非初始化数据段结尾处下一字节的地址。
进程存在的唯一标识PCB:
PCB处于进程核心堆栈底部,不需要额外分配空间,系统通过PCB来感知进程的存在,并通过此进行管理调度,PCB包括创建进程、执行程序、退出程序以及改变进程的优先级等。
程序编译的四个过程:
参考
https://blog.csdn.net/u010183728/article/details/81630554
程序转换为进程:
程序转换为进程的几个步骤:
- 内核将程序读入内存,为程序分配内存空间。
- 内核为进程分配进程标识符PID和进程所需资源。
- 内核为进程保存PID以及相应的状态信息,把进程放入运行队列中等待执行。
PID与进程是一对一的关系,而程序文件与进程是一对多的关系。
2、进程的创建
Linux系统下使用fork()函数创建一个子进程
#include<unistd.h>
pid_t fork(void);
fork()函数不需要参数,返回值是一个pid,fork()函数有两次返回:
- 对于父进程,fork()函数返回新创建的子进程的pid。
- 对于子进程,fork()函数返回0。
- 如果创建出错,fork()函数返回-1。失败通常是因为父进程所拥有的子进程数目超过了规定的限制(CHILD_MAX),此时errno将被设为EAGNIN。如果是因为进程表里没有足够的空间用于创建新的表单或虚拟内存不足,errno变量将被设为ENOMEM。
getpid() :获取当前进程pid。 getppid():获取父进程pid。
子进程继承:
fork() 函数得到的子进程从父进程继承了整个进程的地址空间,包括:进程上下文、进程堆栈、内存信息、打开的文件描述符、信号控制设置、进程优先级、进程组号、当前工作目录、根目录、资源限制、控制终端等、权限掩码umask等。
僵尸进程、孤儿进程、守护进程、进程组、会话:
参考:https://blog.csdn.net/zhangzhebjut/article/details/39034327
3、进程的结束
Linux下使用exit()函数退出进程,其函数原型如下:
#include<stdlib.h>
void exit(int status);
exit()函数的参数表示进程的退出状态,这个值是一个整值,保存在全局变量$?中。
shell中执行‘echo $?’命令可以查看。$?保存的是最近一次运行的进程的返回值,如果shell找不到指定进程那么$?的值为1。
Linux进程的退出分为正常退出和异常退出两种
正常退出
- 在main函数中执行return(函数执行完后的返回,return执行完后把控制权交给调用函数)
- 调用exit( )函数
- 调用_exit( )函数
异常退出
- 调用abort( )函数
- 进程收到某个信号,该信号使程序终止
exit( )函数和_exit( )函数的区别:
exit() 和 _exit() 函数功能和用法是一样的,无非时所包含的头文件不一样,还有的区别就是:exit()属于标准库函数,_exit()属于系统调用函数。
_exit()函数主要做了清理内存空间,结束进程调用等工作。exit()也是通过调用_exit()实现的但是在调用_exit()前exit()还做了以下工作:
- 调用atexit()注册的函数(出口函数):atexit()函数来注册程序正常终止时要被调用的函数。
- 调用cleanup()关闭所有的流:这一步操作导致所有的缓冲被输出。
- 最后调用_exit()函数终止进程
参考:
https://blog.csdn.net/tennysonsky/article/details/45917409
4、进程间通信
System v 和 Posix作用和区别(进程间通信IPC)
参考:https://blog.csdn.net/derkampf/article/details/60958086
linux使用的进程间通信方式
- 管道(pipe),流管道(s_pipe)和有名管道(FIFO)
- 信号(signal)
- 消息队列
- 共享内存
- 信号量
- 套接字(socket)
管道( pipe )
- 无名管道:这种通讯方式有两种限制,一是半双工的通信,数据只能单向流动(写管道的同时关闭读管道,读管道的同时关闭写管道),二是只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系或兄弟进程。
#includ<unistd.h>
Int pipe(int fd[2]);
- 流管道s_pipe: 去除了第一种限制,可以双向传输。
- 有名管道:可用于具有亲缘关系进程间的通信,命名管道:name_pipe克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信;管道可以通过路径名指出,并且在文件系统中可见。
#include<sys/types.h>
#include<sys/stat.h>
int mkfifo(const char *pathname, mode_t mode);
读者使用:read( )
写者使用:write( )
信号量( semophore )
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
system v信号量
semget()用于创建
semop()用于改变信号量值
semctl()用于控制信号量信息
struct sembuf{
short sem_num; //除非使用一组信号量,否则为0
short sem_op; //-1(p等待)+1(v发生信号)
short sem_flg; //通常为SEM_UNDO
}
消息队列( message queue )
消息队列是以消息链表的形成出现,包括Posix消息队列system V消息队列,存放在内核中并由消息队列标识符标识,有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
信号 ( singal )
信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
主要作为进程间以及同一进程不同线程之间的同步手段。
共享内存( shared memory )
共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信,使得多个进程可以访问同一块内存空间。
shmget()创建共享内存
shmat()连接到自身地址空间
shmdt()将共享内存从当前进程分离,不是删除!
shmctl(shmid, IP_RMID, 0)删除共享内存
套接字( socket )
套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
参考:https://songlee24.github.io/2015/04/21/linux-IPC/
5、ipcs命令和ipcrm命令
ipcs命令查询进程间通信状态
ipcrm命令用来清除IPC资源
参考:https://linuxtools-rst.readthedocs.io/zh_CN/latest/tool/ipcs.html