进程
概念
程序:
编译好的可执行文件
存放在磁盘上的指令和数据的有序集合
程序时静态的,没有任何执行的概念
进程:
一个独立的可调度的任务
执行一个程序所分配的资源的总称
进程是程序的一次执行过程
进程是动态的,包括创建、调度、执行和消亡
特点
系统会为每个进程分配0~4G的虚拟空间,用户空间是每个进程所独有的,内核空间是所有进程共有的
处理器调度进程会给进程分配时间片,当时间片用完以后,CPU再调度其他进程,实现进程的轮转,从而实现多任务的操作
进程段:数据段、正文段、堆栈段
进程分类:交互进程,批处理进程,守护进程
进程状态:运行态、睡眠态、可中断睡眠态、不可中断睡眠态、暂停态、死亡态、僵尸态
函数接口
#include <unistd.h>
pid_t fork(void);
//功能:创建子进程
/*
参数:
无
返回值:
成功:
在父进程中,返回子进程的进程号>0
在子进程中,返回值为0
失败:-1并设置errno
*/
特点
子进程几乎拷贝了父进程的全部内容,主要包括代码、数据、系统数据段中的PC值、栈中的数据、父进程中国打开的文件等,但是,他们的PID、PPID是不同的
父子进程有独立的地址空间,互不影响,当在响相应进程中改变全局变量,静态变量,都不受影响
若父进程先结束,子进程成为孤儿进程,被init进程收养,子进程变成后台进程
若子进程先结束,父进程没有及时回收,子进程变成僵尸进程(要尽可能避免僵尸进程的产生)
回收进程资源
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
//功能:回收子进程资源(阻塞)
/*
参数:
status:子进程退出状态,不接受子进程状态设为NULL
返回值:
成功:子进程的ID号
失败:-1
*/
pid_t waitpid(pid_t pid,int *status,int options);
//功能:回收子进程资源
/*
参数:
pid:
>0指定子进程的id号
=-1任意子进程
=0等待其组ID等于调用进程的组ID的任一子进程
<-1等待其组ID等于pid的绝对值的任一子进程
status:子进程的退出状态
option:
0:阻塞
WNOHANG:非阻塞
返回值:
成功:
结束的子进程的进程号
当使用WNOHANG且没有子进程结束时:0
失败:-1
*/
结束进程
void exit(int status);
//功能:结束进程时,刷新缓存
void _exit(int status);
//功能:结束进程时,不刷新缓存
/*
参数:
status:是一个整型的参数,可以利用这个参数传递进程结束时的状态
通常0表示正常结束
其他数值表示出现了错误,进程非正常结束
*/
在实际编程时,子进程中调用exit函数,会将参数值传递给父进程 ,父进程可以进行相应的处理
exit和return的区别
exit:不管在子函数还是主函数,都可以结束进程
return:当子函数中有return时返回到函数调用位置,并不结束进程
exit(EXIT_FAILURE);//失败
exit(EXIT_SUCCESS);//成功
进程间通信
1.早期的进程间通信 无名管道、有名管道、信号
2.system V IPC 共享内存、消息队列、信号灯集
3.BSD 套接字
无名管道
特点
只能用于具有亲缘关系的进程间通信
半双工的通信方式、具有固定的读端和写端
管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO
管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1],其中fd[0]固定用于读管道,fd[1]固定于写管道 ‘’
函数接口
#include <unistd>
int pipe(int fd[2]);
//功能:创建无名管道
/*
参数:
文件描述符 读端:fd[0] 写端:fd[1]
返回值:
成功:0
失败:-1
*/
注意事项
a.当管道中无数据时,读操作会阻塞;管道中无数据,将写端关闭,读操作会立即返回
b.管道中装满(管道大小64K)读数据阻塞,一旦有4K空间,写继续
c.只有在管道读端存在时,向管道中写数据才有意义,否则将会导致管道破裂,向管道中写入数据的进程将收到内核传来的SIGPIPE信号,通常Broken Pipe错误
有名管道
特点
a.有名管道可以使互不相关的两个进程通信
b.有名管道可以通过路径名来指出,并且在文件系统中可见,但内容存放在内存中
c.进程通过IO来操作有名管道
d.有名管道遵循先进先出的规则
e.不支持如lseek()操作
函数接口
int mkfifo(const char * filename,mode_t mode);
//功能:创建有名管道
/*
参数:
filename:有名管道文件名
mode:权限
返回值:
成功:0
失败:-1并设置errno
*/
//注意:
//对错误的处理方式:如果错误时file_exist时,注意加判断,如:if(errno==EEXIT)
注:函数只是在路径下创建管道文件,往管道中写的数据依然写在内核空间
注意事项
a.只写方式,写阻塞,一直到另一个进程把读打开
b.只读方式,读阻塞,一直到另一个进程把写打开
c.可读可写,如果管道中没有数据,读阻塞
有名管道和无名管道的区别
使用场景 | 无名管道 | 有名管道 |
---|---|---|
具有亲缘关系的进程间通信 | 不相关的两个进程间可以使用 |
特点 | 无名管道 | 有名管道 |
---|---|---|
半双工的通信方式 | 在文件系统中会存在管道文件 | |
固定读端fd[0]和写端fd[1] | 通过文件IO进行操作 | |
可以看做一种特殊的文件,通过文件IO操作 | 不支持lseek操作,遵循先进先出 | |
数据存放在内核空间 |
信号
概念
1.信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式
2.信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件
3.如果该进程当前并未处于执行状态,该信号就由内核保存起来,直到该进程恢复执行,再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程
信号的响应方式:忽略信号,捕捉信号,执行缺省操作
信号种类
信号 | 中文 | 文字说明 |
---|---|---|
SIGKILL | 结束进程 | 不能被忽略,不能被捕捉 |
SIGSTOP | 结束进程 | 不能被忽略,不能被捕捉 |
SIGCHLD | 传递进程 | 子进程状态改变时给父进程发的信号 |
SIGINT | 结束进程 | ctrl+c |
SIGTSTP | 暂停信号 | ctrl+z |
SIGQUIT | 退出信号 | ctrl+\ |
SIGALRM | 闹钟信号 | alarm函数设置定时,当到设定的时间时,内核会向进程发送此信号结束进程 |
SIGTERM | 结束终端 | kill使用时不加数字默认是此信号 |
函数接口
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid,int sig);
//功能:信号发送
/*
参数:
pid:指定进程
sig:要发送的信号
返回值:
成功:0
失败:-1
*/
int raise(int sig);
//功能:进程向自己发送信号
/*
参数:
sig:信号
返回值:
成功:0
失败:-1
*/
int pause(void);
//功能:用于将调用进程挂起,直到收到信号为止
unsigned int alarm(unsigned int seconds);
//功能:在进程中设置一个定时器
/*
返回值:
如果调用此alarm()前,进程中已经设置了闹钟时间,则返回上一个闹钟时间的剩余时间,否则返回0
*/
//注意:一个进程只能有一个闹钟时间,如果在调用alarm时,已设置过闹钟时间,则之前的闹钟时间会被新值代替
#include <signal.h>
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
//修改形式
sighandler_t signal(int signum,void (*handler)(int));
//功能:信号处理函数
/*
参数:
signum:要处理的信号
handler:信号处理方式
SIG_IGN:忽略信号
SIG_DFL:执行默认操作
handler:捕捉信号
void handler(int sig){}//函数名可以自定义
返回值:
成功:设置之前的信号处理方式
失败:-1
*/
共享内存
特点
1.共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据拷贝
2.为了在多个进程之间交换信息,内核专门留出了一块内存区域,可以有需要访问的进程将其映射到自己的私有地址空间即可
3.进程就可以直接读写这一内存区域而不需要进行数据的拷贝,从而大大提高效率 由多个进程共享这一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等
步骤
a.创建key值
b.创建或打开共享内存
c.映射共享内存到用户空间
d.撤销映射
e.删除共享内存
函数接口
#include <sys/types.h>
#include <sys/ipc.h>
key_t ftok(const char *pathname,int proj_id);
//功能:产生一个独一无二的key值
/*
参数:
pathname:已经存在的可访问文件的名字
proj_id:一个字符(因为只用低8位)
返回值:
成功:key值
失败:-1
*/
int shmget(key_t key,size_t size,int shmflg);
//功能:创建或者打开共享内存
/*
参数:
key:键值
size:共享内存的大小
shmflg:IPC_CREAT|IPC_EXCL|0777
返回值:
成功:shmid
失败:-1
*/
int *shmat(int shmid,const void *shmaddr,int shmflg);
//功能:映射共享内存.即把指定的共享内存映射到进程的地址空间用于访问
/*
参数:
shmid:共享内存的id号
shmaddr:一般为NULL,表示由系统自动完成映射
shmflg:SHM_RDONLY就是对该共享内存只进行读操作 0:可读可写
返回值:
成功:完成映射后的地址
失败:-1的地址
使用方法:
if((p=(char*)shmat(shmid,NULL,0))==(char *)-1)
*/
int shmdt(const void *shmaddr);
//功能:取消映射
/*
参数:
shmaddr:要取消的地址
返回值:
成功:0
失败:-1
*/
int shmctl(int shmid,int cmd,struct shmid_ds *buf);
//功能:(删除共享内存),对共享内存进行各种操作
/*
参数:
shmid:共享内存id号
cmd:
IPC_STAT:获得shmid属性信息,存放在第三个参数
IPC_SET:设置shmid属性信息,要设置的属性存放在第三参数
IPC_RMID:删除共享内存,此时第三个参数为NULL即可
返回值:
成功:0
失败:-1
*/
关于共享内存的一些命令
ipcs -m:查看系统中的共享内存
ipcrm -m shmid:删除共享内存
信号灯集
信号灯:也叫信号量。它是不同进程之间或一个给定进程内部不同线程间同步的机制;System V信号灯是一个或者多个信号灯的集合。其中的每一个都是单独的计数信号灯。而Posix信号灯指的是单个计数信号灯
通过信号灯集实现共享内存的同步操作
步骤
1.创建key值
2.创建或打开信号灯集
3.初始化信号灯集
4.PV操作
5.删除信号灯集
关于信号灯集的一些命令
ipcs -s:查看信号灯集
ipcrm -s semid :删除信号灯集
函数接口
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key,int nsems,int semflg);
//功能:创建/打开信号灯
/*
参数:
key:ftok产生的key值
nsems:信号灯集中包含的信号灯数目
semflg:信号灯集的访问权限,通常为IPC_CREAT|0666
返回值:
成功:信号灯集ID
失败:-1
*/
int semop(int semid,struct sembuf *opsptr,size_t nops);
//功能:对信号灯集合中的信号量进行PV操作
/*
参数:
semid:信号灯集ID
opsptr:操作方式
nops:要操作的信号灯个数
返回值:
成功:0
失败:-1
*/
struct sembuf{
short sem_num; //要操作的信号灯编号
short sem_op; //0:等待,直到信号灯的值变成0
//1:释放资源,v操作
//-1:分配资源,p操作
short sem_flg; //0(阻塞),IPC_NOWAIT,SEM_UNDO
};
/*
使用方法:
申请资源P操作:
mysembuf.sem_num=0;
mysembuf.sem_op=-1;
mysembuf.sem_flg=0;
semop(semid,&mysembuf,1);
释放资源V操作:
mysembu.sem_num=0;
mysembuf.sem_op=1;
mysembuf.sem_flg=0;
semop(semid,&mysembuf,1);
*/
int semctl(int semid,int semnum,int cmd/*union semun arg*/);
//功能:信号灯集合的控制(初始化/删除)
/*
参数:
semid:信号灯id
semnum:要操作的集合中的信号灯编号
cmd:
GETVAL:获取信号灯的值,返回的是获得的值
SETVAL:设置信号灯的值,需要用到第四个参数共用体
返回值:
成功:0
失败:-1
*/
/*
用法:初始化
union semun{
int val;
}mysemun;
mysemun.val=10;
semctl(semid,0,SETVAL,mysemun);
获取信号灯值,函数semctl(semid,0,GETVAL)的返回值
删除信号灯集:semctl(semid,0,IPC_RMID);
*/
消息队列
特点
消息队列是IPC对象的一种
消息队列由消息队列ID来唯一标识
消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等
消息队列可以按照类型来发送/接收消息
步骤
1.产生key值 2.创建或打开消息队列 3.添加消息:按照类型吧消息添加到已经打开的消息队列末尾 4.读取消息:可以按照类型把消息从消息队列中取走 5.删除消息队列
关于消息队列的一些命令
ipcs -q:查看消息队列
ipcrm -q msgid:删除消息队列
函数接口
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key,int flag);
//功能:创建或打开一个消息队列
/*
参数:
key:key值
flag:创建消息队列的权限 IPC_CREAT|IPC_EXEC|0666
返回值:
成功:msgid
失败:-1
*/
int msgsnd(int msgid,const void *msgp,size_t size,int flag);
//功能:添加消息
/*
参数:
msqid:消息队列的id
msgp:指向消息的指针。常用消息结构msgbuf如下:
struct mgbuf{
long mtype; //消息类型
char mtext[N] //消息正文
};
size:发送的消息正文的字节数
flag:
IPC_NOWAIT:消息没有发送完成函数也会立刻返回
0:直到消息发送完成函数才返回
返回值:
成功:0
失败:-1
*/
/*
使用:msgsnd(msgid,&msg,sizeof(msg)-sizeof(long),0);
注意:消息结构除了第一个成员函数必须为long类型以外,其他成员函数可以根据应用的需求自行定义
*/
int msgrcv(int msgid,void *msgp,size_t size,long msgtype,int flag);
//功能:读取消息
/*
参数:
msgid:消息队列的ID
msgp:存放读取消息的空间
size:接受的消息正文的字节数
msgtype:
0:接收消息队列中第一个消息
>0:接收消息队列中第一个类型为msgtyp的消息
<0:接收消息队列中类型值不小于msgtyp的绝对值且类型值又最小的消息
flag:
0若无消息函数会一直阻塞
IPC_NOWAIT:若没有消息,进程会立刻返回ENOMSG
返回值:
成功:接收到的消息长度
失败:-1
*/
int msgctl(int msgqid,int cmd,struct msqid_ds *buf);
//功能:对消息队列的操作,删除消息队列
/*
参数:
msgqid:消息队列的队列ID
cmd:
IPC_STAT:读取消息队列的属性,并将其保存在buf指向的缓冲区中
IPC_SET:设置消息队列的属性,这个值取自buf参数
IPC_RMID:从系统中删除消息队列
buf:消息队列缓冲区
返回值:
成功:0
失败:-1
*/
//使用方法:msgctl(msgid,IPC_RMID,NULL);