1.进程
运行着的程序就是进程
进程的特性:1.独立性 2.动态性 3.并发性
(1)进程的状态
进程一共有五种状态分别为:创建态,就绪态,运行态,阻塞态(挂起态),退出态(终止态)其中创建态和退出态维持的时间是非常短的,稍纵即逝。我们主要是需要将就绪态, 运行态, 挂起态,三者之间的状态切换搞明白
就绪态: 万事俱备,只欠东风(CPU资源)
- 进程被创建出来了,有运行的资格但是还没有运行,需要抢CPU时间片
- 得到CPU时间片,进程开始运行,从就绪态转换为运行态。
- 进程的CPU时间片用完了, 再次失去CPU, 从运行态转换为就绪态。
运行态:获取到CPU资源的进程,进程只有在这种状态下才能运行
- 运行态不会一直持续,进程的CPU时间片用完之后, 再次失去CPU,从运行态转换为就绪态
- 只要进程还没有退出,就会在就绪态和运行态之间不停的切换。
阻塞态:进程被强制放弃CPU,并且没有抢夺CPU时间片的资格
- 比如: 在程序中调用了某些函数(比如: sleep()),进程又运行态转换为阻塞态(挂起态)
- 当某些条件被满足了(比如:slee() 睡醒了),进程的阻塞状态也就被解除了,进程从阻塞态转换为就绪态。
退出态: 进程被销毁, 占用的系统资源被释放了
- 任何状态的进程都可以直接转换为退出态。
创建子进程的过程就是cow(copy-on-write)写时拷贝
写时拷贝(COW):copy-on-write:多个调用者指向同一块地址空间,如果其中有一个要对地址空间的内容进行修改的话,就会拷贝一个内容、大小和原来地址空间一模一样的地址,然后再让要修改的变量指向拷贝的那一块地址空间,然后再进行修改。写时拷贝的优点:减少资源的占用,缺点:只是将资源的占用延后了
(2)特殊进程
1.僵尸进程
在一个启动的进程中创建子进程,这时候就有了父子两个进程,父进程正常运行, 子进程先与父进程结束, 子进程无法释放自己的PCB资源, 需要父进程来做这个件事儿, 但是如果父进程也不管, 这时候子进程就变成了僵尸进程。
2.孤儿进程
在一个启动的进程中创建子进程,这时候父子进程同时运行,但是父进程由于某种原因先退出了,子进程还在运行,这时候这个子进程就可以被称之为孤儿进程(跟现实是一样的)。
3.守护进程
守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。(守护进程是一种特殊的孤儿进程)
1.Linux下查看系统进程信息的命令
ps aux
- a: 查看所有终端的信息
- u: 查看用户相关的信息
- x: 显示和终端无关的进程信息ps ajx
- a: 查看所有终端的信息
-j: 采用工作控制的格式显示进程状况
- x: 显示和终端无关的进程信息
2.进程函数操作中使用的一些常用函数
(1)创建子进程函数fork、vfork
#include<sys/type.h>
#include<unistd.h>
pid_t fork(void);
功能:
在已有的进程基础上又创建一个子进程
参数:
无
返回值:
成功:
>0 子进程的进程号,标识父进程的代码区
=0 子进程的代码区
失败:
-1 返回给父进程,子进程不会创建
#include<sys/type.h>
#include<unistd.h>
pid_t vfork(void);
功能:
在已有的进程基础上又创建一个子进程
参数:
无
返回值:
成功:
>0 子进程的进程号,标识父进程的代码区
=0 子进程的代码区
失败:
-1 返回给父进程,子进程不会创建
[1]fork与vfork的区别:
1)fork(): 子进程拷贝父进程的堆栈段、数据段,代码段
vfork(): 子进程与父进程共享资源
2)fork(): 父子进程执行次序不确定
vfork(): 保证子进程先运行,在调用exec()或exit()之前,与父进程数据共享,在exec()或exit()调用之后,父进程才能运行
(2)获取进程号函数 :getpid、getppid
#include<unistd.h> #include<sys/types.h> pid_t getpid(void); 功能: 用来取得执行目前进程的pid号 参数: 无 返回值: 目前进程的pid号
#include<unistd.h>
#include<sys/types.h>
pid_t getppid(void);
功能:
用来取得执行目前进程的父进程的pid号
参数:
无
返回值:
当前进程的父进程的pid号
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int num = 10;
pid_t pid = fork();
//pid_t pid = vfork();
if(pid > 0){
num += 10;
printf("in parent child pid %d, %d\n",pid, num);
exit(0); //退出进程
}else if(0 == pid){
num += 5;
printf("in child pid %d, num %d\n",getpid(), num);
exit(0); //退出进程
}else{
printf("failed\n");
}
return 0;
}
运行结果:
(3)进程回收函数wait、waitpid
为了避免僵尸进程的产生,一般我们会在父进程中进行子进程的资源回收,回收方式有两种,一种是阻塞方式wait(),一种是非阻塞方式waitpid()
3.进程间通信的方法
进程间的通信方式有:(一共7种方式)
1.管道:无名管道、有名管道
2.信号:信号 信号量
3.消息队列
4.共享内存
5.套接字
(1)进程间通信——无名管道pipe
1.创建无名管道函数pipe
#include <fcntl.h>
#include <unistd.h>
int pipe(int pipefd[2])
作用:创建一个匿名管道,用来进程间通信;
参数:
int pipefd[2]这个数组是一个传出参数;
pipefd[0] 对应管道的读端;
pipefd[1] 对应管道的写端;
返回值:
成功 0;
失败-1;
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(){
int fd[2] = {-1, -1}; //fd[0]是读端 fd[1]是写端
int res = pipe(fd);
if(res == -1){
perror("create pipe failed");
return -1;
}
pid_t pid = fork();
if(pid > 0){
wait(NULL); //等待子进程退出
printf("in parent\n");
char buf[100] = {0};
read(fd[0], buf,100);
printf("recv %s",buf);
exit(0);
}else if(pid == 0){
printf("in child\n");
write(fd[1], "hello", 5);
exit(0); //系统退出,退出进程
}
return 0; //函数的返回
}
运行结果:
(2)进程间通信——有名管道mkfifo
1.创建无名管道函数(mkfifo——创建管道文件)
#include<sys/types.h>
#include<sys/stat.h>int mkfifo(const char *filename,mode_t mode);
功能:
创建一个管道文件
参数:
filename:管道文件名
mode:管道文件权限
O_RDONLY:读管道
O_WRONLY:写管道
O_RDWR:读写管道
O_NONBLOCK:非阻塞
返回值:
若函数成功执行则返回值为0
失败返回-1
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<sys/types.h>
#include<sys/stat.h>
int main(){
int ret = mkfifo("myfifo",0666); //创建管道文件
if(ret == -1){
perror("mkfifo");
return -1;
}
int fd = open("myfifo", O_RDWR);
pid_t pid = fork();
if(pid > 0){
wait(NULL);
printf("in parent\n");
char buf[100] = {0};
read(fd, buf,100);
printf("recv %s",buf);
exit(0);
}else if(pid == 0){
printf("in child\n");
write(fd, "hello", 5);
exit(0); //系统退出,退出进程
}
return 0; //函数的返回
}
运行结果:
(3)进程间通信——信号signal
Linux中的信号:可以用kill -l命令查看
#include<signal>
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
功能:
设置某个信号的捕捉行为
参数:
signum:要捕捉的信号
handler:捕捉到信号要如何处理
SIG_IGN:忽略信号
SIG_DFL:使用信号默认的行为回调函数:
这个函数是内核调用,程序员只负责写,捕捉到信号后如何去处理信号。
回调函数:
需要程序员实现,提前准备好的,函数的类型根据实际需求,看函数指针的定义不是程序员调用,而是当信号产生,由内核调用函数指针是实现回调的手段,函数实现之后,将函数名放到函数指针的位置返回值:
成功返回上一次注册的信号处理函数的地址。第一次调用返回NULL
失败返回SIG_ERR,设置错误号
9)SIGKILL 和 19)SIGSTOP 不能被捕捉和忽略
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
功能:
发送一个信号给进程号为pid的进程
参数:
pid:进程的id
sig:要发送信号
返回值:
成功返回0
错误返回-1
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<fcntl.h>
#include<signal.h>
void sighandle(int signum){
printf("signum is %d\n",signum);
}
int main(){
pid_t pid = fork();
if(pid > 0){
//signal(SIGQUIT,SIG_IGN); //SIG_IGN忽视信号
//signal(SIGQUIT,SIG_DFL); //SIG_DFL默认的方式去处理
signal(SIGQUIT,sighandle); //自定义处理
wait(NULL);
printf("in parent\n");
exit(0);
}else if(pid == 0){
printf("in child\n");
kill(getppid(),3);//SIGQUIT == 3 //getppid()获取进程的父进程pid号
exit(0); //系统退出,退出进程
}
return 0; //函数的返回
}
运行结果:
(4)消息队列——msgget、msgrcv、msgsnd
在Linux中查看消息队列命令:ipcs -q
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg);
功能:
创建或打开消息队列
参数:
key:和消息队列关联的key值
msgflg:
0:取消息队列标识符,若不存在则函数会报错
IPC_CREAT:当msgflg&IPC_CREAT为真时,如果内核中不存在键值与key相等的消息队列,则新建一个消息队列;如果存在这样的消息队列,返回此消息队列的标识符
IPC_CREAT|IPC_EXCL:如果内核中