文章目录
进程
进程是一个具有一定独立功能的程序关于某个数据集合的一次运动活动,她是操作系统动态执行的基本单元,在传统的操作系统中,进程即是基本的分配单元,也是基本的执行单元。
进程的概念主要由两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域、数据区域和堆栈。文本区域存储处理器执行的代码;数据区域储存变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,即进程就是运行中的程序。
进程的特征
-
动态性
进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。 -
并发性
任何进程都可以同其他进程一起并发执行。 -
异步性
由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。 -
结构特征
进程由程序、数据、和进程控制块cpu三部分组成。
** 进程控制块(PCB)是操作系统核心中一种数据结构,主要表示进程状态。其作用是使一个在多道程序环境下不能独立运行的程序(含数据),成为一个能独立运行的基本单位或与其它进程并发执行的进程。PCB通常是系统内存占用区中的一个连续区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息。**有进程表示PID、进程用户、进程状态、优先级、文件描述符表等。
进程的类型
-
交互进程
在shell下启动。在前台后台都可以运行。 -
批处理进程
和所在的终端无关,被提交到一个作业队列中以便顺序执行 -
守护进程
和终端无关,一直在后台运行
进程的状态
-
运行态
进程正在运行,或准备运行 -
等待态
进程在等待一个事件的发生或某种系统资源
分可中断和不可中断。 -
停止态
进程被中止,收到信号后可继续执行 -
死亡态
已终止的进程,但PCB没有被释放。
shell中查看进程的命令
ps -elf查看系统进程快照
ps aux
top 查看进程动态信息
/proc查看进程详细信息
优先级的范围 -20 —— 19(-20最高)
nice -n 19 ./test 按用户指定的优先级运行进程
renice 10 进程pid 改变正在运行进程的优先级
jobs 查看后台进程
bg 将挂起的进程在后台运行
fg 把后台运行的进程放到前台运行
contr Z 把前台放后台。
进程的创建
父子进程
-
子进程继承了父进程的内容
-
父子进程有独立的地址空间,互不影响
-
若父进程先结束
(1)子进程成为孤儿进程,被init进程收养
(2)子进程变成后台进程 -
若子进程先结束
(1)父进程没有及时回收,子进程变成僵尸进程 -
子进程从fork后开始运行,父子进程谁先运行依赖于操作系统调度策略
进程的退出
进程的回收
子进程结束时由父进程回收,孤儿进程由init进程回收,若没有及时回收会出现僵尸进程。
进程回收函数——wait
pid_t wait(int * status);
- 头文件 #include <unistd.h>
- 成功时返回回收的子进程的进程号;失败时返回EOF
- 若进程没有结束父进程一直阻塞
- 若有多个子进程,哪个先结束就先回收
- status 指定保存子进程返回值和结束方式的地址
- status 为NULL表示直接释放子进程PCB,不接受返回值
进程返回值和结束方式
进程回收函数——waitpid
pid_t waitpid(pid_t pid, int * status, int option);
- 成功时返回回收的子进程的pid或0;失败返回EOF
- pid可用于指定回收哪个子进程或任意子进程
- status指定用于保存子进程返回值和结束方式的地址
- option指定用于回收方式,0或WNOHANG
参数pid和option
(1)参数pid
- pid<-1 等待进程组识别码为pid 绝对值的任何子进程。
- pid = -1 等待任何子进程,相当于wait()。
- pid = 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。
- pid > 0 只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程还没有结束,waitpid就会一直等下去。
(2)参数option
这里只学习0和WNOHANG两个参数
如果使用了WNOHANG(wait no hung)参数调用waitpid,即使没有子进程退出,它也会立即返回,不会像wait那样永远等下去。不想用WNOHANG可以直接用0。
exec函数族
进程调用exec函数族执行某个程序,进程当前内容被指定的程序替换,实现让父子进程执行不同的程序。
步骤如下:
- 父进程创建子进程
- 子进程调用exec函数族
- 父进程不受影响
execl/execlp函数
int execl(const char path ,const char arg,…);
int execlp(const char * file ,const char arg,…);
- 头文件 #include<unistd.h>
- 成功时执行指定的程序;失败时返回EOF
- path 执行的程序名称,包含路径
- arg… 传递给执行的程序的参数表列
- file 执行的程序的名称,在PATH中查找
execv/execvp函数
int execv(const char path , char const arg[]);
int execvp(const char * file ,char const arg[]);
- 成功时执行指定的程序;失败时返回EOF
- arg[] 传递给执行的程序的参数封装成指针数组的形式
system函数
int system(const char * command);
- 头文件 #include<stdlid.h>
- 成功时返回命令command的返回值;失败时返回EOF
- 当前进程等待command执行结束后才继续执行
守护进程
守护进程(Daemon)是Linux三中进程类型之一,通常在系统启动时运行,系统关闭时结束,在Linux系统中很多的服务程序是以守护进程形式运行的。
守护进程的特点
- 始终在后台运行
- 独立于任何终端
- 周期性的执行某种任务或等待处理特定事件
【会话的概念,在Linux中,会话是一个或多个进程组的集合。通常用户打开一个终端时,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话】
守护进程的创建步骤
一、创建子进程,父进程退出
- 子进程变成孤儿进程,被init进程收养
- 子进程在后台运行
二、子进程创建新的会话
- 子进程成为新的会话组长
- 子进程脱离原先的终端
三、更改当前工作目录
- 守护进程一直在后台运行,其工作目录不能被卸载
四、重设文件权限掩码
- 文件权限掩码设置为0
- 只影响当前进程
五、关闭打开的文件描述符
- 关闭所有从父进程继承的打开文件
- 已脱离终端,stdin/stdout/stderr无法再使用。
进程间通信
进程间通信(IPC,Interprocess communication)就是在不同进程之间传播或交换信息,进程间通信是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。
IPC方法包括管道(PIPE)、消息排队、旗语、共用内存以及套接字。
IPC方法——无名管道
无名管道的特点:
- 只能用于具有亲缘关系的进程之间的通信
- 单工的通信模式,具有固定的读端和写端
- 无名管道创建时会返回两个文件描述符,分别用于读写管道。
无名管道的创建——pipe
int pipe(int pfd[2]);
- 头文件 #include<unistd.h>
- 成功时返回0,失败时返回EOF
- pfd 包含两个元素的整数数组,用来保存文件描述符
- pfd[0]用于读管道,pfd[1]用于写管道。
管道读写说明
用pipe()函数创建的管道两端处于一个进程中,由于管道主要是再不同的进程间通信的,因此在实际应用中没有太大的意义。实际上,通常是先创建一个管道,再调用fork()函数创建一个子进程,该子进程会继承父进程所创建的管道,这时,父子进程管道的文件描述符对应的关系如图
此时的关系看似复杂,实际上已经给不同进程间的读写创造了很好的条件。父子进程分别拥有自己的读写管道,为了实现父子进程间的读写,只需要把无关的读端或者写端关闭即可。另外,父进程还可以创建多个子进程,各个子进程都继承了相应的fd[0]和fd[1]。这时,只需要关闭相应的端口就可以建立其各个子进程间的通道。
管道读写的注意点
- 只有在管道的读端存在时,向管道写入数据才有意义。否则,向管道写入数据的进程将收到内核传来的SIGPIPE信号(通常为BROKEN pipe错误)。
- 向管道写入数据时,Linux将不保证写入的原子性,管道缓冲区有一空闲区域,写进程就会试图向管道写入数据。如果读进程不读取管道缓冲区中的数据,那么写操作会一直阻塞。
- 父子进程在运行时,它们的先后次序并不能保证。因此,为了保证父子进程已经关闭了相应的文件描述符,可在两个进程中调用sleep()函数。当然这种调用不是很好的解决方法,使用进程之间的同步机制与互斥机制,可以很好的解决这样的问题。
IPC方法——有名管道
有名管道特点
- 对应管道文件,可用于任意进程间进行通信
- 打开管道时可指定读写方式
- 通过文件IO操作,内容存放在内存中。
有名管道的创建——mkfifo
int mkfifo(const char * path,mode_t mode);
- 头文件 #include<unistd.h> <fcntl.h>
- 成功时返回0,失败时返回EOF
- path 创建的管道文件路径
- mode 管道文件的权限,如0666
IPC方法——信号
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。Linux内核通过信号通知用户进程,不同的信号类型代表不同的事件,并且Linux在早期的Unix信号机制上进行了扩展。
进程对信号有不同的响应方式:
- 缺省方式
- 忽略信号
- 捕捉信号
常用信号
信号相关命令
1、kill
- kill [-signal] pid
- -signal 可指定信号
- pid 指定发送对象
- 默认发送SIGTERM
2、killall
- killall [ -u user | prog]
- prog 指定进程名
- user 指定用户名
信号发送 ——kill / raise
int kill (pid_t pid ,int sig);
int raise (int sig); 【给自己发信号】
- 头文件 #include<unistd.h> <signal.h>
- pid 接受信号的进程号 【0代表同组进程 ; -1代表所有进程】
- sig 信号类型
闹钟函数——alarm
int alarm(unsigned int seconds);
- 成功时返回上个定时器的剩余时间,失败返回EOF
- seconds 定时器的时间
- 一个进程中只能设计一个定时器,时间到时产生SIGALRM
- 该函数常用于超时检测
暂停函数——pause
int pause(void);
- 进程一直阻塞,直到被信号中断
- 被信号中断后返回-1,errno为EINTR
设置信号响应方式——signal
void (signal (int signo ,void (handler)(int)))(int);
typedef void (sighandler_t)(int);
sighandler_t signal(int signum , sighandler_t handler);
- 头文件 #include<unistd.h> <signal.h>
- signo 要设置的信号类型
- handler 指定的信号处理函数 SIG_DFL代表缺省方式;SLG_IGN代表忽略信号;
System V IPC
- System V引入了三种高级的IPC对象:共享内存、消息队列、信号灯集,
- 每一个IPC对象有唯一的ID,并且有一个关联的KEY【用户空间的标识符】
- IPC对象创建后一直存在,直到被显式的删除【存在于内核中而不是文件系统中,由用户控制释放,不像管道的释放由内核控制】。
- 用于查看System V IPC对象的命令ipcs/ipcrm
以下部分引用大佬的博客:
头文件及函数介绍
头文件:
在使用三种IPC机制的时候,我们肯定是通过系统调用,而这些函数所需要的头文件需要首先搞清楚。System V的IPC操作要用到的头文件有:
#include <sys/types.h> //公共头文件,声明了key_t类型
#include <sys/ipc.h> //公共头文件
#include <sys/msg.h> //消息队列函数的头文件
#include <sys/sem.h> //信号量函数的头文件
#include <sys/shm.h> //共享内存函数的头文件
这里用到的头文件都是在sys目录下的。前面两个是公共的头文件,也就是说三种IPC机制都有用到,而后面三个是和具体的IPC机制相关的,通过头文件的名称我们能发现它们同样满足前面所说的缩写。
ftok()函数:
- 三个IPC机制会用到大量的函数,不同IPC所用到的函数不同但是有一个是相同的——ftok()
- Unix系统中有个重要的概念叫做:万物皆文件。在很多IPC机制中的操作都是针对文件描述符(简称 fd)的,然而System V却不同,它没有对fd进行操作,而是针对 IPC对象的id来操作的,而这个id(标识符)又是通过key(键)来生成的。
- 三种IPC有各自的函数来生成id,但是它们所利用的key却都由函数ftok()生成,看一下函数声明:
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname, int proj_id);
- ftok的英文可以理解为 **file to key **的缩写。即将文件转换成key。
- 参数pathname是文件路径名(该文件必须存在,通常用当前路径名“.”);proj_id被称作子id,自己指定一个整型。注意如果两个进程要通过System V的IPC通信,那么它们的ftok函数的两个参数必须相同,这样才能生成同样的key,从而产出同样的id。
- 返回值类型key_t在<sys/types.h>中被定义,实际是一个整型(32位),该key是路径名和子id共同作用的结果。这里要用到该文件路径的stat结构。(系统中每一个文件都有其对应的stat结构)。
- key的31~24位为ftok函数第二个参数proj_id的低8位。
- key的23~16位为该文件stat结构中st_dev属性的低8位。
- key的15~0位为该文件stat结构中st_ino属性的低16位。
原文链接:https://blog.youkuaiyun.com/qq_38211852/article/details/80475818
System V IPC——共享内存
共享内存的特点:
- 共享内存是一种最为高效的进程间通信的方式,进程可以直接读写内存,而不需要任何数据拷贝
- 共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活
- 由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用
共享内存使用步骤:
- 创建/打开共享内存
- 映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问
- 读写共享内存
- 撤销共享内存映射
- 删除共享内存对象
共享内存的创建——shmget
int shmget(key_t key , int size ,int shmflg);
- 头文件 #include<sys/ipc.h> <sys/shm.h>
- 成功时返回共享内存的id,失败时返回EOF
- key是和共享内存关联的key,IPC_PRIVATE【创建私有的共享内存】 或 ftok生成
- shmflg 共享内存标志位 IPC_CREAT|0666
- size 创建共享内存的大小
共享内存的映射——shmat
void shmat(int shmid ,const void shmaddr,int shmflg);
- 成功时返回映射后的地址,失败时返回(void *)-1
- shmid 需要映射的共享内存id
- shmaddr 映射后的地址 ,NULL 表示有系统自动映射
- shmflg 标志位 0表示可读可写; SHM_RDONLY表示只读
共享内存的读写——示例
共享内存的读写通过指针访问共享内存,指针类型取决于共享内存中存放的数据类型
例如:在共享内存中存放键盘输入的字符串
char*addr;
int shmid;
...
if((addr=(char*)shmat(shmid,NULL,0))==(char *)-1)
{
perror("shmat");
exit(-1);
}
fgets(addr,N,stdin);
...
共享内存的撤销映射——shmdt
int shmdt(void shmaddr);
- 头文件 include<sys/ipc.h> <sys/shm.h>
- 成功时返回0,失败时返回EOF
- 不使用共享内存时应该及时撤销映射
- 进程结束时自动撤销
共享内存的控制——shmctl
int shmctl(int shmid , int cmd ,struct shmid_ds buf);
- 头文件 include<sys/ipc.h> <sys/shm.h>
- 成功时返回0,失败时返回EOF
- shmid 要操作的共享内存id
- cmd 要执行的操作 IPC_STAT IPC_SET IPC_RMIO
- buf 保存或设置共享内存属性的地址
IPC_STAT:得到共享内存的状态,把共享内存的shmid_ds结构复制到buf中
IPC_SET:改变共享内存的状态,把buf所指的shmid_ds结构中的uid、gid、mode复制到共享内存的shmid_ds结构内
IPC_RMID:删除这片共享内存
注意事项
- shmdt(addr)使进程中的shmaddr指针无效化,不可以使用,但是保留空间。
- shmctl(shmid,IPC_RMID,0) 删除共享内存,彻底不可用,释放空间。
- 每一块共享内存有大小限制
System V IPC——消息队列
消息对列的概述:
- 消息队列是System V IPC对象的一种
- 消息队列由消息队列ID来唯一标识
- 消息对列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
- 消息队列可以按照类型来发送/接受消息
消息队列使用步骤:
- 打开/创建消息队列 msgget
- 向消息队列发送消息 msgsnd
- 从消息队列接受消息 msgrcv
- 控制消息队列 msgctl
消息队列的创建/打开——msgget
int msgget(key_t key , int msgflg);
- 头文件 #include<sys/ipc.h> <sys/msg.h>
- 成功时返回消息对列的id,失败时返回EOF
- key 和消息队列关联的key IPC_PRIVATE 【创建私有的消息队列】或ftok
- msgflg 标志位 IPC_CREAT|0666
消息发送
int msgsnd(int msgid ,const void msgp ,size_t size ,int msgflg);
- 头文件 #include<sys/ipc.h> <sys/msg.h>
- 成功时返回0,失败时返回-1
- msgid 消息队列id
- msgp 消息缓冲区地址
- size 消息正文长度
- msgflg 标志位 0或IPC_NOWAIT
0:msgsnd调用阻塞直到发送成功位置
IPC_NOWAIT:若消息无法立即发送(如当前消息队列已满),函数会立即返回
msgp:指向消息结构体的指针,该消息结构msgbuf通常如下
typedef struct{
long mtype;//消息类型,该结构体必须从这个域开始
char mtext[1];//消息正文
};
消息的格式:
- 通信双方首先定义好统一的消息格式
- 用户根据应用需求定义结构体类型
- 首成员类型必须为long,代表消息类型(正整数)
- 其他成员都属于消息正文
- 消息长度不包括首类型 long
示例
typedef struct{
long mtype;
char mtext[64];
}MSG;
define LEN (sizeof(MSG) - sizeof(long))
int main()
{
MSG buf;
...
buf.mtype = 100;
fgets(buf.mtext,64 stdin);
msgsnd(msgid,&buf,LEN,0);
...
return 0;
}
消息接受——msgrcv
int msgrcv(int msgid ,void msgp, size_t size ,long msgtype, int msgflg);
- 头文件 #include<sys/ipc.h> <sys/msg.h>
- 成功时返回收到的消息长度,失败时返回-1
- msgp 消息缓冲区地址
- size 指定接受消息长度
- msgtype 指定接受的消息类型
- msgflg 标志位 0或IPC_NOWAIT
msgtyp
- 0:接受消息队列中第一个消息
- 大于0:接受消息队列中第一个类型为msgtyp的消息
- 小于0:接受消息队列中第一个类型值不小于msgtyp绝对值且类型值最小的消息
控制消息队列——msgctl
int msgctl(int msgid ,int cmd ,struct msgid_ds buf)
- 头文件 #include<sys/ipc.h> <sys/msg.h>
- 成功时0,失败时返回-1
- msgid 消息队列id
- cmd 要执行的操作 IPC_STAT / IPC_SET / IPC_RMID
- buf 存放消息队列属性的地址
System V IPC ——信号灯
信号灯概述:
- 信号灯也叫信号量,用于进程/线程同步或互斥的机制
信号灯的类型:
- Posix 无名信号灯
- Posix 有名信号灯
- System V 信号灯
信号灯的特点: System V 信号灯是一个或多个计数信号灯的集合。可同时笑傲做集合中的多个信号灯,申请多个资源时使用信号灯避免死锁。
System V信号灯使用步骤:
- 打开/创建信号灯 semget
- 信号灯初始化 semctl
- P/V操作 semop
- 删除信号灯 semctl
信号灯创建/打开——semget
int semget(key_t key , int nsems ,int semflg);
- 头文件 #include<sys/ipc.h> <sys/sem.h>
- 成功时返回信号灯的id,失败时返回-1
- key 和信号灯关联的key IPC_PRIVATE【创建私有的信号灯】
- nsems 集合中包含的计数信号灯个数
- semflg 标志位 IPC_CREAT|0666 IPC_EXCL
信号灯初始化——semctl
int semctl(int semid , int semnum ,int cmd,…);
- 成功时返回0,失败时返回EOF
- semid 要操作的信号灯集id
- semnum 要操作的集合中的信号灯编号
- cmd 执行的操作 SETVAL IPC_RMID
- union semun 取决于cmd
信号灯P/V操作 – semop
int semop(int semid, struct sembuf sops, unsigned nsops);
- 成功时返回0,失败时返回-1
- semid 要操作的信号灯集id
- sops 描述对信号灯操作的结构体(数组)
- nsops 要操作的信号灯的个数
sembuf:
struct sembuf
{
short sem_num;
short sem_op;
short sem_flg;
};
// semnum 信号灯编号
//sem_op -1:P操作 1:V操作
//sem_flg 0 / IPC_NOWAIT
总结
进程的知识点太多了,要好好消化。