进程基本概念
1.进程:
程序:存放在外存中的一段数据组成的文件
进程:是一个程序动态执行的过程,包括进程的创建、进程的调度、进程的消亡
进程相关命令
1.top
动态查看当前系统中的所有进程信息(根据CPU占用率排序)
PID:唯一识别进程的ID号(>0)
NI优先级:Linux系统中数值低,优先级高(-20 - 19) Windows系统中数值高,优先级高
进程状态:
R 运行态/就绪态
S 睡眠态/可唤醒等待态
D 不可唤醒等待态
T 暂停态
Z 僵尸态
X 结束态
q退出
2.nice
以指定优先级来运行进程
示例:
nice -n 优先级 要执行的集成任务
renice
重新设定一个正在运行的进程的优先级
示例:
renice -n 优先级 进程PID
3.kill
杀死指定的进程任务
示例:
kill -9 进程PID
killall
杀死进程名对应的所有进程任务
示例:
killall -9 进程名
4.ps -ef
查看当前时刻所有进程的信息
PPID:父进程的ID号
ps -ef | grep a.out 通过管道将前面命令的输出作为面命令的输入
5.pstree
查看进程树关系
6.ps -aux
查看当前时刻的进程信息
7. ./a.out &
将a.out任务放在后台执行
8.jobs
查看一个终端下后台执行的所有任务
9.fg 编号
将后台任务放到前台执
进程的创建
32bits:
一个进程在运行时,操作系统会为该进程分配 0 - 4G 虚拟内存空间,分为文本段、数据段、系统数据段
文本段:
也称为文本区,存放代码和指令
数据段:
也称为数据区,可以细分为:
1.字符串常量区
2.未初始化全局变量/静态变量
3.已初始化全局变量/静态变量
系统数据段:
包含 堆区(大概几G):1,程序员手动管理,空间要自己malloc和free
2,堆的增长方向是自低向高
栈区(基本就是8MB):1,局部变量存放在栈区
2,未经初始化的随机值
3,代码执行到变量定义的时候开辟空间
4,超过变量作用域回收空间
5,栈的增长方向自高向低
4.进程中虚拟地址和物理地址的关系
1. 0 - 4G虚拟内存空间只有一个
2. 实际物理地址中每个进程空间独立
3. 通过MMU内存映射单元,单一个进程执行时,将物理地址中的数据加载到虚拟地址中运行
进程的调度
1.常见的调度算法:
1.先来先执行,后来后执行
2.高优先级调度算法
3.时间片轮转调度算法
4.多级队列反馈调度算法
5.负载均衡调度算法
时间片:
1.CPU在一个任务中的运行时间称为一个时间片
2.宏观并行,微观串行
3.进程的状态:
R 运行态、就绪态
S 睡眠态/可唤醒等待态
D 不可唤醒等待态
T 暂停态
Z 僵尸态
X 结束态
进程相关函数接口
1.进程的创建
fork
pid_t fork(void);
功能:
创建一个子进程,新创建的进程称为原来进程的子进程,原来的进程称为新进程的父进程
参数:
void 缺省
返回值:
成功子进程返回0
父进程返回子进程的PID
失败返回-1
父进程调用fork创建子进程,子进程拷贝父进程的文本段、数据段、系统数据段 ,除了内核不拷贝。
宏观并行,微观是切换进行,具体根据情况。
getpid
pid_t getpid(void);
功能:
获得调用进程的PID号
getppid
pid_t getppid(void);
功能:
获得调用进程的PPID,获得父进程的PID
2.exit
void exit(int status);
功能:
让进程结束
参数:
status:进程结束的状态
返回值:
缺省
exit在主函数中使用和return效果一致
exit会刷新缓存区
_exit
void _exit(int status);
功能:
让进程直接结束,不会刷新缓存区。
参数:
status:进程结束的状态
返回值:
缺省
进程的消亡
1.僵尸进程:
进程代码执行结束,空间没有被回收,称为僵尸进程
2.如何避免产生僵尸进程?
1.让父进程先结束
2.让父进程回收子进程空间
3.孤儿进程:
进程的父进程先结束,此时该进程称为孤儿进程,被系统进程收养,进程再结束时,会被系统进程回收进程空间
8.wait
pid_t wait(int *wstatus);
功能:
回收子进程空间
参数:
wstatus:存放子进程结束状态空间的首地址
返回值:
成功返回回收到的子进程PID
失败返回-1
1.wait函数具有阻塞功能
2.wait函数具有同步功能
WIFEXITED(wstatus)
进程是否正常退出
WEXITSTATUS(wstatus)
进程结束状态值
WIFSIGNALED(wstatus)
进程是否被信号杀死
WTERMSIG(wstatus)
获得杀死进程的信号编号
实例:创建九个子进程
exec函数族
extern char **environ;
int execl(const char *path, const char *arg, ...
/* (char *) NULL */); :
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *path, const char *arg, ...
/*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
执行结束子程序整个代码也会结束。
功能:
利用进程空间执行另外一份代码
l:参数以列表形式传递
v:参数以指针数组形式传递
e:更新环境变量
p:在系统指定目录下查找文件
getenv
char *getenv(const char *name);
功能:
获得环境变量名对应的值,也就是平时的PATH
setenv
int setenv(const char *name, const char *value, int overwrite);
功能:
设置环境变量的值
参数:
name:环境变量名
value:环境变量的值
overwrite:非0 覆盖
0 不覆盖
返回值:
成功返回0
失败返回-1
进程间通讯
1.进程间的通信:
1.管道
2.信号
3.消息队列
4.共享内存
5.信号灯
6.套接字
管道
1.无名管道
无名管道只能用于具有亲缘关系的进程间通信,先创建管道,在创建子进程,子进程就会复制父进程的管道。
pipe
int pipe(int pipefd[2]);
功能:
创建一个无名管道
参数:
pipefd[0]:读管道文件描述符
pipefd[1]:写管道文件描述符
返回值:
成功返回0
失败返回-1
搭配:write(写管道,数组,数组大小);
read(读管道,数组,数组大小);
无名管道特性:
1.管道中至少有一个写端:
读取数据时,如果管道中有数据直接读取,管道中没有数据阻塞等待直到有数据写入读出,继续向后执行
2.管道中没有写端:
读取数据时,如果管道中有数据直接读取,管道中没有数据不阻塞等待直接向下执行
3.管道中至少有一个读端:
写入数据时,如果管道中没有存满(64k),则直接写入,管道中如果存满,则阻塞等待直到有数据读出,才能继续写入
4.管道中没有读端:
写入数据时,会产生管道破裂错误,导致程序崩溃
2.有名管道
打开管道文件 -> 读写管道文件 -> 关闭管道文件
注意:有名管道必须读写两端同时加入才能继续向下执行
1.mkfifo
int mkfifo(const char *pathname, mode_t mode);
功能:
创建一个管道文件
参数:
pathname:管道文件路径
mode:权限
返回值:
成功返回0
失败返回-1
双管道通讯
信号
信号用来实现内核层和用户层信息的交互,也可以用来实现进程间通信
1.信号的种类:
kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD(有子进程结束,就会收到该信号) 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
2.信号处理方式:
1.缺省:
按照系统默认的方式处理
2.忽略:
不响应信号
3.捕捉:
按照自定义方式处理信号
9号信号SIGKILL
19号信号SIGSTOP
这两个信号不能被忽略和捕捉
以下三个信号可以从键盘输入:
SIGINT:ctrl + c 让进程结束
SIGQUIT:ctrl + \ 让进程退出
SIGTSTP:ctrl + z 让进程挂起
4.signal
typedef void (*sighandler_t)(int);
就等于 typedef void (*)(int) sighandler_t;因为c语言中函数必须和指针在一起写,所以写了过去
意思就是用sighandler_t代替前面那个函数指针。
sighandler_t signal(int signum, sighandler_t handler);
功能:
改变信号的处理方式
参数:
signum:信号的编号
handler:信号的处理方式
SIG_IGN 忽略处理
SIG_DFL 缺省处理
函数首地址 捕捉处理
返回值:
成功返回之前处理函数的首地址
失败返回SIG_ERR
在handler函数中int signo代表的是信号的编号,所以就可以把这些西到一个函数里面。
收到信号,不去捕捉,信号大多就会杀死进程。
1.信号:
1.pause
int pause(void);
功能:
让进程睡眠,直到接收到信号(捕捉)才能继续向下执行
2.alarm
unsigned int alarm(unsigned int seconds);
功能:
定时seconds秒后给调用进程发送SIGALRM信号
参数:
seconds:定时的秒数
返回值:
成功返回之前设定剩余的秒数
3.kill
int kill(pid_t pid, int sig);
功能:
给PID对应的进程发送sig信号
参数:
pid:进程ID号
sig:信号的编号
返回值:
成功返回0
失败返回-1
消息队列
1.IPC对象:
内存文件
1.ipcs
查看系统重的消息队列、共享内存、信号灯的信息
2.ipcrm
删除消息队列、共享内存、信号灯
ipcrm -Q/-M/-S key
ipcrm -q/-m/-s 消息队列ID/共享内存ID/信号灯ID
3.操作流程:
创建消息队列 -> 发送消息 -> 接收消息
4.函数接口:
1.ftok
key_t ftok(const char *pathname, int proj_id);
功能:
根据pathname和proj_id生成一个key_t类型的key值,将来可以用来创建消息队列、共享内存、信号灯
参数:
pathname:文件路径
proj_id:8位非0值
返回值:
成功返回key_t类型的IPC对象的key值
失败返回-1
2.msgget
int msgget(key_t key, int msgflg);
功能:
根据key值对象的IPC对象创建一个消息队列
参数:
key:IPC对象名字
msgflg:IPC_CREAT 对象不存在就创建
IPC_EXCL 对象存在报错
IPC_CREAT | 0664
返回值:
成功返回消息队列ID
失败返回-1
3.msgsnd
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:
向消息队列中发送消息
参数:
msqid:消息队列的ID号
msgp:发送消息空间的首地址
struct msgbuf {
long mtype; /* message type, must be > 0 */
char mtext[1]; /* message data */
};
msgz:发送消息内容的大小(不包含发送消息类型)
msgflg:属性,默认为0
返回值:
成功返回0
失败返回-1
4.msgrcv
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:
从消息队列中接收消息
参数:
msqid:消息队列的ID号
msgp:存放接收到消息空间的首地址
msgsz:最多接收消息的空间的大小
msgtyp:想要接收消息的类型,就是 long mtype;写哪个就可以接收那个消息有两个相同的,谁先发就先接谁
msgflg:属性,默认为0
返回值:
成功返回实际接收的字节数
失败返回-1
5.msgctl
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:
向消息队列发送一条cmd命令
参数:
msqid:消息队列的ID号
cmd:IPC_RMID 删除消息队列 还有其他消息队列命令,具体可以查手册。配合buf使用
buf:默认传NULL
返回值:
成功返回0
失败返回-1
共享内存
进程间通信最高效的形式
1.操作方式:
创建共享内存 -> 映射到共享内存中 -> 共享内存操作 -> 解除映射 -> 删除共享内存
2.函数接口:
1.ftok
2.shmget
int shmget(key_t key, size_t size, int shmflg);
功能:
创建一个共享内存,大小都是4k分配,所以尺寸大小都是4096的整数倍。
参数:
key:IPC对象名称
size:共享内存的大小
shmflg:
IPC_CREAT
IPC_EXCL
返回值:
成功返回共享内存ID
失败返回-1
3.shmat
void *shmat(int shmid, const void *shmaddr, int shmflg);
功能:
将一个地址映射到共享内存中 内存不能直接访问,所以需要映射访问。
参数:
shmid:共享内存ID号
shmaddr:NULL 让系统选择一个合适的地址映射
不为NULL shmflg 设定为SHM_RND 选择离给定地址最近的能够映射的地址进行映射
否则传递地址为4k的整数倍
返回值:
成功返回映射到共享内存空间中的地址
失败返回NULL
4.shmdt
int shmdt(const void *shmaddr);
功能:
解除映射
参数:
shmaddr:映射的地址
返回值:
成功返回0
失败返回-1
5.shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
功能:
向共享内存发送命令
参数:
shmid:共享内存ID号
cmd:IPC_RMID 删除共享内存,看手册产看其他命令
buf:NULL
返回值:
成功返回0
失败返回-1
利用消息队列实现clientA和clientB两个进程任务的全双工聊天功能
其实可以用一个队列就行了,因为他的数据类型可以设置不同,所以接收不会进行资源竞争。发送也是thread_safety,所以不会出现发送资源竞争。
信号量(有名信号量)
例子
head.h
#ifndef __HEAD_H__
#define __HEAD_H__
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <pthread.h>
#include <semaphore.h>
#include <signal.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/shm.h>
#include <sys/sem.h>
union semun {
int val; /* Value for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
unsigned short *array; /* Array for GETALL, SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO
(Linux-specific) */
};
extern int init_sem(int semid, int *parray, int len);
extern int sem_p(int semid, int num);
extern int sem_v(int semid, int num);
#endif
sem.c
#include "head.h"
int init_sem(int semid, int *parray, int len)
{
union semun myun;
int i = 0;
int ret = 0;
for (i = 0; i < len; i++)
{
myun.val = parray[i];
ret = semctl(semid, i, SETVAL, myun);
if (-1 == ret)
{
perror("fail to semctl");
return -1;
}
}
return 0;
}
int sem_p(int semid, int num)
{
int ret = 0;
struct sembuf mybuf;
mybuf.sem_num = num;
mybuf.sem_op = -1;
mybuf.sem_flg = SEM_UNDO;
ret = semop(semid, &mybuf, 1);
if (-1 == ret)
{
perror("fail to semop");
return -1;
}
return 0;
}
int sem_v(int semid, int num)
{
int ret = 0;
struct sembuf mybuf;
mybuf.sem_num = num;
mybuf.sem_op = +1;
mybuf.sem_flg = SEM_UNDO;
ret = semop(semid, &mybuf, 1);
if (-1 == ret)
{
perror("fail to semop");
return -1;
}
return 0;
}
write.c
#include "head.h"
int main(void)
{
key_t key;
int shmid = 0;
int semid = 0;
char *pshmaddr = NULL;
int val[2] = {0, 1};
key = ftok(".", 'a');
if (-1 == key)
{
perror("fail to ftok");
return -1;
}
semid = semget(key, 2, IPC_CREAT | 0664);
if (-1 == semid)
{
perror("fail to semget");
return -1;
}
init_sem(semid, val, 2);
shmid = shmget(key, 4096, IPC_CREAT | 0664);
if (-1 == shmid)
{
perror("fail to shmget");
return -1;
}
pshmaddr = shmat(shmid, NULL, 0);
if (NULL == pshmaddr)
{
perror("fail to shmat");
return -1;
}
while (1)
{
sem_p(semid, 1);
gets(pshmaddr);
sem_v(semid, 0);
if (!strcmp(pshmaddr, ".quit"))
{
break;
}
}
shmdt(pshmaddr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
read.c
#include "head.h"
int main(void)
{
key_t key;
int shmid = 0;
int semid = 0;
char *pshmaddr = NULL;
int val[2] = {0, 1};
key = ftok(".", 'a');
if (-1 == key)
{
perror("fail to ftok");
return -1;
}
semid = semget(key, 2, IPC_CREAT | 0664);
if (-1 == semid)
{
perror("fail to semget");
return -1;
}
init_sem(semid, val, 2);
shmid = shmget(key, 4096, IPC_CREAT | 0664);
if (-1 == shmid)
{
perror("fail to shmget");
return -1;
}
pshmaddr = shmat(shmid, NULL, 0);
if (NULL == pshmaddr)
{
perror("fail to shmat");
return -1;
}
while (1)
{
sem_p(semid, 0);
printf("SHMADDR:%s\n", pshmaddr);
if (!strcmp(pshmaddr, ".quit"))
{
break;
}
sem_v(semid, 1);
}
shmdt(pshmaddr);
shmctl(shmid, IPC_RMID, NULL);
return 0;
}
makefile
all:read write
read:read.c sem.c
gcc $^ -o $@
write:write.c sem.c
gcc $^ -o $@