?操作系统
是个软件系统,协调计算机硬件进行工作,为上层应用提供支持和用户操作。对编程开发来说,它提供API编程接口(文件操作,socket网络编程等)在计算机中,操作系统的定位是一个管理者,对上管理在系统中运行的进程,对下通过驱动程序管理各种硬件。
1、与硬件交互,管理所有的软件和硬件资源
2、为应用程序提供一个执行环境
常用的操作系统:
终端的应用:windows、IOS、Android等
服务器应用:windows server、linux(重点)、unix等
?Linux内核
内核是操作系统的核心,具有很多最基本功能,它负责管理系统的进程、内存、设备驱动程序、文件和网络系统,决定着系统的性能和稳定性 。Linux 内核由如下几部分组成:内存管理、进程管理、设备驱动程序、文件系统和网络管理等
?虚拟内存
虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。目前,大多数操作系统都使用了虚拟内存,如Windows家族的“虚拟内存”;Linux的“交换空间”等。
进程
运行的程序就称为进程 ,进程是操作系统分配资源和调度的基本单元 ,其实可以理解为程序的运行实例
?进程的特征:
动态性:有生命周期
并发性:同时可以有多个程序,在内存中执行。常常代表的是同时启动多个程序,注意和并行的区别,并行强调的是多个进程同一时间在执行。
独立性:进程有自己的地址空间,运行时在这个地址空间中运行代码,各个进程之间互不影响。
异步性:指的是独立运行,以不可预知的速度推进
?进程的组成:
Linux系统中进程由以下三部分组成: ①进程控制块PCB;②数据段;③正文段
PID:进程ID号
PCB:进程控制块,
linux下叫具体为task_struct 进程的地址空间(虚拟地址空间:地址连续) 代码段、数据段、堆、栈等等(栈地址增长方向:从高到低。堆地址增长方向:从低到高)
假设32系统,共4G的空间
1G:Linux系统内核使用,3G:用户可以使用。
其中0-3G是用户空间,
3G-4G是内核空间 较低3G字节0x00000000 ~ 0xBFFFFFFF称为“用户空间” 最高的1G字节0xC0000000 ~ 0xFFFFFFFF称为“内核空间”
?进程的关系 父子关系、兄弟关系、亲缘关系
运行:占据CPU执行 就绪:进程进入就绪队列等待获取CPU执行,优先级高的先处理,当优先级相同时,轮询处理
阻塞:当有I/0请求时,释放CPU的使用权,因为IO操作比较耗时,此时进入阻塞状态,如果IO操作结束后,状态切换为就绪态(进入就绪队列等待获取CPU执行)
?Linux进程常用的五种状态
运行状态(TASK_RUNNING)
可中断睡眠状态(TASK_INTERRUPTIBLE)
不可中断睡眠状态(TASK_UNINTERRUPTIBLE)
暂停状态(TASK_STOPPED)
僵死状态(TASK_ZOMBIE)—僵尸进程
僵尸进程:子进程已经结束,但是父进程并没有回收其资源,此时的子进程就是一个僵尸进程。使用wait和waitpid函数可以解决这个问题
孤儿进程:父进程已经结束,但是子进程仍在运行。这时候子进程就是孤儿进程
守护进程:(相当于windows的服务程序)一种脱离终端,在后台运行的一种进程。终端的开关与他无关
?进程标识符pid 每个进程都有一个独一无二的进程标识符,标志符大于等于0,0号进程是内核进程,它创建1号进程,1号进程创建了2号进程 进程id的范围如下所示 ?创建进程
分配id,分配PCB 分配地址空间 拷贝父进程的进程空间 将子进程置为就绪状态 放到就绪队列
?程序转换为进程
内核将程序读入内存,为程序分配内存空间 内核为该进程分配进程标识符pid和其他所需资源 内核为该进程保存PID及相应的状态信息,把进程放到运行队列中等待执行.程序转化为进程后就可以被操作系统的调度程序调度执行了
?销毁进程
回收各种资源 、记录系统日志、将进程换出内存,置为僵尸状态、转存储调度
?进程的终止方式 有5种方式使进程终止
1、从main返回
2、调用exit
3、调用_exit
4、调用abort
5、由一个信号终止
进程在终止的时候,系统会释放进程所拥有的资源,例如内存、文件描述符,内核结构等。
exit:退出时清理资源和IO缓冲区中的内容
_exit:不清理IO缓冲区中的内容
案例1:
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
int n = 10; pid_t pid = getpid();
pid_t ppid = getppid();
while(1)
{
printf("pid=%d,ppid=%d\n",pid,ppid);
printf("&n=0x%x\n",&n);
sleep(5);
}
return 0;
}
fork
进程复制,创建子进程
fork() 函数不需要参数,但返回两次,返回值有三种情况:
(1)对于父进程,fork函数返回新的子进程的ID。
(2)对于子进程,fork函数返回0。
(3)如果出错,fork函数返回-1。
fork()的特点是执行一次,返回两次。在父进程和子进程中返回的是不同的值,父进程返回的是子进程的ID号,而子进程中则返回0。
案例2:
#include<stdio.h>
#include<sys/types.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int n = 10;
//进程复制
int pid = fork();
//fork失败
if(-1 == pid)
{
perror("fork");
exit(-1);
}
if(0 == pid)
{
//子进程
printf("child pid=%d,ppid=%d\n",getpid(),getppid());
printf("&n=0x%x\n",&n);
exit(0);
}
else if(pid > 0)
{
//父进程
//让当前进程休眠1秒
//sleep(1);
//等待进程结束
//wait(NULL);
printf("father pid=%d,ppid=%d\n",getpid(),getppid());
printf("&n=0x%x\n",&n);
wait(NULL);
}
return 0;
}
情况1:产生孤儿进程
情况2:sleep(1)后产生的结果
情况3:使用wait()
1.wait在前
2.wait在后
案例3:
子进程会拷贝父进程的所有资源,变量。
注意:子进程拷贝了父进程数据空间、堆、栈等资源的副本,
父子进程间不共享这些存储空间,共享的空间只有代码段,
子进程修改一个全局变量,父进程的这个全局变量不会改变,因为是一个副本。
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
int main()
{
int n = 1;
//如果fork成功,则返回两次
pid_t pid = fork();
if(-1 == pid)
{
perror("fork:");
exit(-1);
}
if(pid == 0)
{
//子进程
while(1)
{
n++;
printf("child pid=%d,ppid=%d\n",getpid(),getppid());
sleep(1);
}
exit(0);
}
else
{
//父进程
while(1)
{
printf("father pid=%d,n=%d\n",getpid(),n);
sleep(1);
}
}
printf("main ending!\n");
return 0;
}
强制结束父进程,则1号接管子进程
?子进程从父进程拷贝的内容主要有以下
用户号UIDS和用户组号GIDS
环境Environment
堆栈
共享内存
打开文件的描述符
执行时关闭(Close-on-exec)标志
信号(Signal)控制设定
进程组号
当前工作目录
根目录
文件方式创建屏蔽字
资源限制
控制终端
?子进程独有
进程号PID
不同的父进程号
自己的文件描述符和目录流的拷贝
子进程不继承父进程的进程正文(text),数据和其他锁定内存(memory locks)
不继承异步输入和输出
父进程和子进程拥有独立的地址空间
案例4:
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>
/*全局变量*/
char *msg = "shenlan";
int g_value = 0;
int main()
{
printf("%s\n", msg);
int val = 0;
printf("uid:%d,euid:%d,gid:%d,egid:%d\n", getuid(), geteuid(), getgid(), getegid());
pid_t id = fork();
if (id < 0)
{
printf("fork error\n");
exit(1);
}
else if (id == 0)
{
//子进程执行代码
val++;
g_value++;
printf("child:pid:%d,ppid:%d,val:%d,&val:0x%x,g_value:%d,&g_value:0x%x\n",
getpid(), getppid(), val, &val, g_value, &g_value);
}
else
{
//父进程执行代码
sleep(1);
printf("father:pid:%d,ppid:%d,val:%d,&val:0x%x,g_value:%d,&g_value:0x%x\n",
getpid(), getppid(), val, &val, g_value, &g_value);
sleep(3);
}
return 0;
}
案例5:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
int i;
for(i=0; i<2; i++){
fork();
printf("-");
}
return 0;
}
```![在这里插入图片描述](https://img-blog.csdnimg.cn/20191011174459698.png)
案例6:
```cpp
/*
父子进程的文件描述符可以共享,但是要在fork之前打开,并在公有代码中关闭文件
*/
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *args[])
{
// 打开文件描述符:在fork之前打开才能让父进程和子进程共享
int fd = open("data.txt", O_RDONLY);
// 判断文件打开是否成功
if (fd == -1)
{
printf("File open failed : %s\n", strerror(errno));
}
// 定义读取文件的缓冲区
char buf[1024];
//读取到的字节数
int nread;
// 执行fork函数
pid_t pd = fork();
// 判断fork是否成功
if (pd == -1)
{
printf("fork failed : %s\n", strerror(errno));
}
// 通过if...else...执行父子进程的特有代码
if (pd > 0)
{
sleep(5);
//清空缓冲区内存
memset(buf, 0, sizeof(buf));
// 读取文件内容
nread = read(fd, buf, 7);
buf[nread] = '\0';
//将读取到的内容显示到屏幕上
printf("%s\n", buf);
printf("father fd = %d\n", fd);
//关闭文件描述符
close(fd);
}
else
{
//清空缓冲区内存
memset(buf, 0, sizeof(buf));
// 读取文件内容
nread = read(fd, buf, 5);
buf[nread] = '\0';
//将读取到的内容显示到屏幕上
printf("%s\n", buf);
printf("son fd = %d\n", fd);
//关闭文件描述符
close(fd);
}
printf("process [%d] ending!\n", getpid());
return 0;
}
vfork() 函数和 fork() 函数(fork()如何使用,请点此链接)一样都是在已有的进程中创建一个新的进程,但它们创建的子进程是有区别的。
fork(): 父子进程的执行次序不确定。
vfork():保证子进程先运行,在它调用 exec(进程替换) 或 exit(退出进程)之后父进程才可能被调度运行。
fork(): 子进程拷贝父进程的地址空间,子进程是父进程的一个复制品。
vfork():子进程共享父进程的地址空间(准确来说,在调用 exec(进程替换) 或 exit(退出进程) 之前与父进程数据是共享的)