linux中的进程
1.进程原理是一个程序的一次执行过程。它是操作系统进行资源分配的和调度的基本单元。它占用内存、CPU等系统资源。
他拥有唯一的进程id。可以通过命令ps axj查看一些进程。
2.进程的特性:
1.并发性(多个进程可以同时并发执行,相互间不受干扰)得益于CUP执行速度快。宏观上CUP某一时间只能执行一个进程。他会给每个进程分配一个时间片。循环依次执行。在微观上就感觉在同时处理多个进程。
2.动态性(就是指每个进程独有完整的生命周期,而且进程状态都是可以不停变化的)
3.交互性(进程与进程之间可以发生直接与间接的通信)
4.独立性(每个进程都有独立的地址空间)
进程的最重要的就是ID与状态。ID是唯一的。状态有不同的
可以根据man ps查看(位置在475页)。根据图上下两部分一起来看。
比如R+就是进程运行在前台。没有标记的自己可以查。
3进程大小:每个进程都有独立的4GB地址空间。分为用户区(0G~3G)与内核区(3G ~ 4G)。如图下。每个进程的用户区通过地址映射到不同的物理内存,而每个进程的内核区映射到相同的物理内存。但是每个进程中的PCB控制进程块不是一样的内存。他们在内核中是以双向循环链表存放的。
4.创建进程
创建子进程要用到fork函数,其实就是克隆了一个一模一样的主进程。相当于两份一模一样的程序。但是子进程是从fork下面开始执行的。当父进程执行到fork时返回值就是子进程的ID。而子进程执行到fork时返回值是0。
主进程与子进程到底谁先执行取决于调度算法。
/******************************
函数功能:创建一个子进程
函数参数:无
函数返回值:成功对于**父进程中返回值是子进程的ID,而子进程返回值是0**
失败返回-1
函数定义的头文件:#include<sys/types.h> #include<unistd.h>
***************************/
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t ID=fork(); //创建一个子进程
if(ID==0) //子进程在运作
{
printf("子进程的ID(ID):%d\n",getpid());
printf("子进程的父进程ID:%d\n",getppid());
}
else if(ID>0) //父进程在运作
{
printf("父进程的ID(main):%d\n",getpid());
printf("父进程的父进程ID:%d\n",getppid());
printf("%d\n",ID);
}
sleep(2); //给子进程一些执行时间,让主进程慢点结束
return 0;
}
父子进程相同:
刚fork后。 data段、text段、堆、栈、环境变量、宿主目录位置、进程工作目录位置、信号处理方式
父子进程不同:
进程id、fork()返回值、各自的父进程、进程创建时间、闹钟、未决信号集
父子进程共享:**(切记切记)读时共享、写时不共享------全局变量 (会重新申请一份)。**1. 文件描述符 2. mmap映射区。举个列子。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int count=10; //全局变量
int main()
{
pid_t id;
id=fork();
if(id==0)
{
printf("子进程:%d\n",count); //子进程 依然是10
}
if(id>0)
{
count=20; //当写count=20时
printf("主进程:%d\n",count);
}
wait(NULL);
return 0;
}
当在父进程把count改为20,但是子进程中并没有改还是10。所以写是不共享的。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int count=10;
int main()
{
pid_t id;
id=fork();
if(id==0)
{
printf("子进程:%d\n",count);
}
if(id>0)
{
printf("主进程:%d\n",count);
}
wait(NULL);
return 0;
}
当父子进程都去读时。读到都是一样的。所以读是共享的
5.子进程的循环创建
只有当父进程的时候才创建。子进程的时候不创建。所以exit(0)正常退出子进程
#include<stdio.h>
#include<unistd.h>
#include<sys/wait.h>
#include<stdlib.h>
int main()
{
for(int i=0;i<5;i++) //循环创建5个进程
{
if(fork()==0)
{
printf("%d %d %d\n",getpid(),getppid(),i);
exit(1);
}
}
sleep(5); //让父进程延时退出。给子进程一些创建时间
return 0;
}
父进程都是一样的。
6.进程的退出
exit函数
/******************************
函数功能:退出一个进程
函数参数:一个整形如果为0(表示正常退出)
函数返回值:无
函数定义的头文件:#include<stdlib.h>
/
7.等待子进程先退出函数 wait
1.具有阻塞功能 2.回收子进程残留 3.获取子进程结束状态退出的原因
如果是多个子进程就循环退出。
/***
函数功能:等待子进程先退出
函数参数:是一个int* 用来保存子进程结束的状态。结束状态结果要用宏来测定。
函数返回值:成功返回已回收的子进程的ID 失败返回-1
函数定义的头文件:#include<sys/types.h> #include<sys/wait.h>
/
wiatpid也是等待子进程先退出函数。不够他可以指定等哪个子进程。可以设置不阻塞。
/***
函数功能:等待子进程先退出
函数参数:
参数1:指定回收子进程的ID (如果-1可以是任意子进程)
参数2:是一个int* 用来保存子进程结束的状态。结束状态结果要用宏来测定。
参数3:提供了一些额外的选项来控制waitpid,目前在Linux中只支持WNOHANG和WUNTRACED两个选项
函数返回值:成功>0返回已回收的子进程的ID
等于0:不阻塞没有子进程 退出
失败返回-1
函数定义的头文件:#include<sys/types.h> #include<sys/wait.h>
/
8.execl (函数簇)
为什么要用execl呢?因为有时我们希望子进程去执行另外的程序,exec函数族就提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新程序的内容替换了。可以是linux中所有的指令可以不用ls。换个其他指令。
/***
函数功能:使子进程执行某一指令
函数参数:参数1:路径 参数2:变长参数 (可以任意)
就是一条指令组成字符串。但是最后一定要加个NULL。
函数返回值:成功无 失败-1
函数定义的头文件:#include<unistd.h>
***************************/
int main(void)
{
pid_t pid = -1;
pid = fork();
if (pid > 0)
{
// 父进程
printf("parent, 子进程id = %d.\n", pid);
}
else if (pid == 0)
{
// 子进程
execl("/bin/ls", "ls", "-l", "-a", NULL); // ls -l -a
}
sleep(2);
return 0;
}
9.守护进程
守护进程是一种运行在后台的特殊进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。不受用户登录注销的影响。在服务器中用的比较多。
创建守护进程的步骤:
- fork子进程,退出父进程(这样才能创建会话)
- 子进程调用setsid()函数创建会话。
- 改变工作目录(提供一种安全)用chdir来改
- 设置umask文件权限掩码。
- 关闭/重定向文件描述符(因为守护进程已经脱离控制终端)文件描述符0,1,2。
- 守护进程的逻辑
#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
#include<stdlib.h>
void err(const char *arr)
{
perror(arr);
exit(0);
}
int main()
{
pid_t id;
id=fork();
if(id<0)
{
err("创建进程失败");
}
if(id>0) //让父进程退出 因为session leader调用setsid不会创建新session
{
exit(0);
}
int ret=setsid(); //setsid会新建一个会话 ,使当前进程成为新session的leader,并且不再关联之前session的controlling terminal1i
if(ret<-1)
{
err("创建会话失败");
}
ret=chdir("/home/student"); //改变工作目录
if(ret<-1)
{
err("改变工作目录失败");
}
umask(0022); //改变文件权限掩码
printf("%d\n",getpid());
close(STDIN_FILENO); //关闭为0的文件描述符 然后让为给其它的用
int fd=open("/dev/null",O_RDWR);
if(fd<0)
{
err("打开文件失败");
}
dup2(fd,STDOUT_FILENO); //改变文件描述符重定向到fd
dup2(fd,STDERR_FILENO);
while(1); //守护进程 业务逻辑
return 0;
}
10.进程的通信(IPC)
方法:管道、信号、共享映射区、本地套接字。
下面介绍管道通信
原理:内核借助环形队列机制,使用内核缓冲区实现。
特质:
- 伪文件(不占磁盘空间)
- 管道中的数据只能读一次。只能一端写,一端读。要么一端读,一端写。
不能同时一端又读又写。(因为是队列)
局限性: - 自己写,不能自己读。
- 数据不可以反复读。
- 是一种半双工
- 两个进程要有血缘关系(父子、兄弟)
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
char buff[1024];
int arr[2];
int ret= pipe(arr); //创建一个管道
if(ret<0)
{
perror("管道创建失败\n");
}
pid_t id=fork(); //创建一个子进程
if(id<0)
{
printf("子进程创建失败\n");
}
if(id==0)
{
close(arr[1]); //关闭写通道
int len=read(arr[0],buff,sizeof(buff));
printf("%d\n",len);
write(STDOUT_FILENO,buff,len); //写到屏幕
close(arr[0]);
}
if(id>0)
{
close(arr[0]); //关闭读通道
write(arr[1],"hello hello",sizeof("hello hello"));
close(arr[1]);
}
wait(NULL);
return 0;
}
运行结果
11.总结
其余进程通信方法也还在学习中所以就只介绍一种。里面有什么错误希望大家提出来。一起相互学习、相互进步。
12.习题
/*
ls | wc -l
父进程中把ls输出重定向到父进程写端
子进程wc -l的输入重定向到子进程的读端
整个程序执行 ls | wc -l
*/
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int arr[2];
int gd=pipe(arr);//创建管道 arr[0]:读管道 arr[1]:写管道
if(gd<0)
{
perror("创建管道失败");
return -1;
}
fflush(NULL); //清空缓存区
pid_t id=fork(); //创建进程(克隆父进程)
if(id<0)
{
perror("创建进程失败");
return -1;
}
if(id>0) //执行父进程
{
close(arr[0]); //关闭读通道
dup2(arr[1],STDOUT_FILENO); //重定位到arr[1] 从arr[1]中输出
fflush(NULL); //清空缓存区
execlp("ls","ls",NULL); //执行指令 不会往下进行
}
if(id==0) //执行子进程
{
close(arr[1]); //关闭写通道
dup2(arr[0],STDIN_FILENO); //重定位到arr[0] 从arr[0]中输入
fflush(NULL); //清空缓存区
execlp("wc","wc","-l",NULL); //执行指令 不会往下进行
}
return 0;
}