程序与进程
程序:是一种静态的概念,表示有序指令的集合.
进程:是一种动态的概念,表示程序运行的过程(运行过程包含:创建、调度、消亡)
进程状态
D 不间断睡眠(通常为IO) 软件不可中断,但是硬件可以,硬件交互
I 属于不可中断等待态D的内核线程,但是该内核线程可能实际上没有任何负载,该线程就是空闲状态
R 运行或可运行(运行队列)
S 可中断睡眠(等待事件完成)软件可中断
X 死(不应该被看到)
Z (“僵尸”)进程,终止但未被其父进程收割
< 高优先级(对其他用户不友好)
N 低优先级(对其他用户来说很好)
L 将页面锁定在内存中(用于实时和定制IO)
s 是一个会议领导者
l 是多线程的(使用CLONE_THREAD,像NPTL的pthreads那样)
+ 在前台进程组中
进程的系统调用
虚拟内存
- 每一个进程都有自己的虚拟内存空间(32位系统 4g)
- 虚拟内存与物理内存有映射关系,但是虚拟内存的大小和物理内存大小没有关系. 与操作系统以及CPU有关系
- 与内核内存 、物理内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FBt84v2Y-1658999320376)(.\IO进程小笔记.assets\123-1656860936411.png)]
fork函数
pid_t fork(void): 进程创建函数
{
宏观角度:
该函数正确调用的话有两个返回值(区分父子进程): 一个>0的值 一个 == 0的值.
错误返回 -1
pid_t p = fork();
if(p == -1): 错误发生,
常见错误原因: 资源不足开辟不了进程.
else if(p == 0): 表示子进程(由于该子进程没有子进程,所以返回值为0)
pid = getpid();//当前进程的pid
pid = getppid();//当前进程的父进程pid
else if(p > 0): 表示父进程(其实这儿的p就是子进程ID号,便于父进程管理子进程)
pid = getpid();//当前进程的pid
pid = getppid();//当前进程的父进程pid
pid = p;//当前fork()函数分裂的子进程pid
}
- fork()一旦被调用,则子进程会拷贝所有父进程的资源(正文段(fork之后的)、数据段、代码段、 堆、栈…)
pid_t vfork(void)函数一个已经被淘汰的函数
{
与fork相比,这个创建的进程数据直接会直接交互、干扰;
当子进程未被杀死时,父进程不会结束,会造成死锁的后果;
}
进程资源回收
进程退出函数:—exit() / _exit()
void exit(int status)
{
status: 结束状态.
EXIT_SUCCESS 或 0: 代表正常结束
EXIT_FAILURE 或 1 -1:代表错误结束
要处理缓存区内容(比如:把缓存剩下的内容写入文件).
}
void _exit(int status)
{
status: 结束状态.
EXIT_SUCCESS 或 0: 代表正常结束
EXIT_FAILURE 或 1 -1:代表错误结束
销毁缓存区的所有东西,直接退出进程.
}
资源回收函数:(子进程资源需要被父进程回收…) --wait
wait() 与 waitpid()
{
等待子进程退出.并且回收子进程资源.
pid_t wait(int *status)
{
status: 获取进程退出状态. 不需要获取的话 就为 NULL
pid_t: 返回子进程的进程号
为了避免僵尸进程的出现,通常会用wait来回收子进程的资源.
}
pid_t waitpid(pid_t pid, int *status, int options)
{
pid: pid > 0 只等待进程ID等于pid的子进程,不管其它已经有多少子进程运行结束退出了,只要指定的子进程 还没有结束,waitpid就会一直等下去
pid == -1 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用 一模一样
pid == 0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组, waitpid不会对它做任 何理睬
ps axj 可以查看组进程.
pid < -1 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对 值
status: 与wait一致
options: 为 0 与wait函数一样,需要一直等待进程退出(阻塞).
为WNOHANG 不需要等待(不阻塞).
返回值: 正常返回子进程的进程号 使用选项WNOHANG且没有子进程结束时:0 错误: -1;
}
exec函数族
- exec函数族提供了一种在进程中启动另一个程序执行的方法。
int execl(const char *path, const char *arg, ...)
int execv(const char *path, char *const argv[]);
int execle(const char *path, const char *arg, ..., char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, char *const argv[])
l: 代表后面的参数的传入是 list 形式(依次传入).
v: 代表后面的参数的传入是以指针数组传入的.
例如: char *arv[] = {"ls", "-l", NULL};
p: 第一个参数不需要指明路径,只需要传入可执行文件名就可以了. (在系统默认PATH路径下寻 找)
e: 最后一个参数可以传入环境变量.
除了第一个参数以外后面参数所以形式都需要以NULL结尾!!
例如:char *arg[] = {"ls", "-l", NULL}
execvp("ls", arg);
-
新的可执行程序把子进程原来的数据段、代码段和堆栈段,全部替换,除了进程号外
守护进程
pid = fork() if(pid > 0) exit(0); ==== 孤儿进程-->守护进程 ====
- 让进程摆脱原会话的控制
- 让进程摆脱原进程组的控制
- 让进程摆脱原终端的控制
会话–setsid()函数
pid_t setsid(void)
{
作用:创建一个新会话并担任该会话的组长
返回值:-1失败
成功返回进程组ID
}
改变当前目录–chdir()函数
int chdir(const char *path);
{
path:改换地址的绝对路径
返回值: 0成功
-1失败
}
重设文件掩码–umask()函数
mode_t umask(mode_t mask);
{
mask:掩码,当mask = 0700时,表示关闭该进程的该用户的读、写、执行的权限||通常设置为0
}
关闭文件描述符–close()
close(i) //i = 0,1,2
原因:占用资源,可能导致所在文件无法卸载
守护进程创建完毕
进程间的通信
-
传统通信方式:无名管道、有名管道、信号
-
IPC通信:共享内存、消息队列、信号灯
-
BSD通信: 套接字通信(网络编程篇)
传统通信方式
无名管道
1、是"半双工"工作方式 "半双工"同时只能从一端写入,另一端读取.
2、无名管道不属于文件系统. 数据交互在"内核内存"完成.
3、无名管道只能用于亲缘进程间的通信.
函数接口: pipe
{
int pipe(int pipefd[2]);
用法: int fd[2];
pipe(fd);
fd[0] 表示读端
fd[1] 表示写端
}
ret = read()在读取管道时(与读取文件返回0有区别).
如果写端关闭 且当中没有内容可读则会返回0. ret == 0
如果写端没有关闭 且当中没有内容可读,read会阻塞.
读端如果关闭,写端的存在是没有意义的,所以内核会发送一个管道破裂信号(SIGPIPE),
该信号会 默认结束进程
当写端关闭,如果管道中还有内容则读端会继续读取,直到读取完成返回0.
有名管道
1、是"半双工"工作方式 "半双工"同时只能从一端写入,另一端读取.
2、有名管道属于文件系统. 数据交互在"内核内存"完成.
3、有名管道可以用于非亲缘进程间的通信.
指令mkfifo 可以直接创建管道文件.
接口函数: mkfifo()
{
创建管道文件.
int mkfifo(const char *pathname, mode_t mode);
pathname : 管道名
mode:权限
创建好后直接用open打开,就可以向管道里读写数据了
值得一提的是:open打开管道是,文件路径为绝对路径,打开方式要么只读要么只写,如果读写都打开的话,则该进程无法与其他连接该管道的进程通信
信号
常见信号
发送信号–kill/raise
int kill(pid_t pid, int sig)
{
pid ; <0 发送信号给进程号为pid的进程
= 0 发送信号给单曲进程组中的所有进程
=-1 发送信号给所有信号表中的进程
<-1 发送信号给-pid进程组中的所以进程
sig:信号类型
返回值:0成功
-1错误
}
int raise(int sig)
{
这个是向进程自身发送信号
sig:信号类型
返回值:0成功
-1错误
}
信号设置–signal
typedef void (*sighander_t)(int)
int signal(int signum, sighander_t hander_t)
{
signum : 指定信号
hander: SIG_IGN 忽略该信号
SIG_DFL 默认处理
自定义处理函数
返回值:失败返回SIG_ERR
}
IPC通信
共享内存
创建–shmget
int shmget(key_t key ,int size, int shmflg)
{
key:健值,同上可用ftok或者IPC_PRIVATE
size:共享内存大小
shmflg:权限位,同上可加IPC_CREAT:创建新的共享内存
IPC_EXCL:和IPC_CREAT同用,表创建唯一共享内存
返回值:-1失败
成功返回标识符
}
映射–shmat
char *shmat(int shmid, const void * shmaddr, int shmflg)
{
shmid:由shmget返回的标识符
shmaddr:映射的指定地址,一般设为NULL,自动分配
shmflg:0 内存可读可写
SHM_RDONLY:只读
返回值:失败-1;
成功:被映射地址
}
解除映射–shmdt
int shmdt(const void * shmaddr)
{
shmaddr:shmat的返回值
返回值:0成功
-1失败
}
控制共享内存—shmctl
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
{
我们这儿用来删除
shmid:shmget返回值
cmd:IPC_RMID
buf:NULL
返回值:-1失败
0成功
}
信号量
创建函数–semget()
int semget(key_t key,int nsems, int semflg);
{
key:可以是IPC_PRIVATE,也可以用ftok()
nsems:创建信号灯个数
semflag:同open中一样听见权限位,
IPC_CREAT:创建新的信号量
IPC_EXCL:和IPC_CREAT同用,表创建唯一信号量
返回值:成功返回标识符
失败 -1
}
信号灯操作函数–semop()
int semop(int semid, struct sembuf * sem, size_t n)
{
semid:信号灯集的标识符
sem:结构体指针
struct sembuf{
short sem_num; //信号灯的编号
short sem_op; //信号灯操作方式
0:等待
-1:P操作
1:V操作
(V+ ,,, P-)
short sem_flg; //0:阻塞等待
n:操作个数 1
返回值:
成功信号量标识符
失败-1
}
}
信号灯控制函数–semctl()
int semctl(int semid, int semnum, int cmd, ....)
{
semid:信号灯集id
semnum:要修改的信号灯的编号
cmd:
SETVAL:设置信号灯值
GETVAL:获取信号灯值
IPC_RMID :删除信号灯
返回值:失败返回-1;
值得注意的是该函数的第四个参数
cmd = SETVAL||SETGAT时
union semun{
int val; //SETVAL的值
struct semid_ds *buf; //IPC_SET,IPC_STAT的缓存区
unsigned short *array;//SETALL,GETALL数组
struct seminfo *__buf;//IPC_INOF缓存区
}!!!需要自己定义
初始化
semctl(semid, 0 ,SETVAL, 0);//给编号0赋初值0
删除
semctl(semid, 0, IPC_RMID);//semnum随便取,直接删除的是id的信号灯集
}
消息队列
创建–msgget
int msgget(key_t key, int msgflg)
{
key:同上
msgflg:同open中一样听见权限位,
IPC_CREAT:创建新的信号量
IPC_EXCL:和IPC_CREAT同用,表创建唯一信号量
返回值:成功返回id、
失败返回-1
}
发送–msgsnd()
int msgsnd(int msqid, const void * msgp, size_t msgsz, int msgflg)
{
msqid:msgget返回的id
msgp:指向消息的结构体指针,需要减去 long mtype
ex:struct msgbuf {
long mtype; //消息类型
char buf[N]; //正文
int data; //正文
}
msgsz:正文大小 上述的sizeof(msgbuf)-sizeof(long)
msgflg : 通常设置为0,阻塞状态
设置为IPC_NOWAIT。非阻塞
返回值:成功0
错误-1
}
接受–msgrcv
int msgrcv(int msqid, void * msgp, size_t msgsz, long int msgtyp,int msgflg)
{
msqid:msgget返回的id
msgp:同上
msgtyp:0 :接收队列中的第一个消息
>0:接收队列中第一个msgtyp的消息
<0:接收队列中第一个类型值不小于|msgtyp|且这个类型值是消息队列中相对最小的
msgflg: 同上
返回值:成功0
失败-1
}
控制–msgrcv
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
{
msqid:同上
cmd:IPC_STAT 读取消息队列中的的数据结构msqid_ds存入buf
IPC_SET设置消息队列中的数据msqid_ds,取用buf
IPC_RMID 删除消息队列
buf: struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
cmd为IPC_RMID时 buf设置为NULL
}