前言:这个系列是关于Linux系统学习的学习笔记总结!另外一位宝藏博主: 小林哥他的博客是真的好!
1.操作系统概念
操作系统是管理软硬件资源的 “管理” 软件!操作系统向下管理,管理各种硬件资源,向上为用户(用户软件)提供一个良好(稳定、快速、安全、简单) 使用环境!
如何理解"管理":
如果将校长(操作系统)通过辅导员(各种驱动)管理学生(设备),当然如果将院长引进来,那么院长相当于操作系统中的模块(进程管理、文件管理…)但是上面的图足够说明管理的原理!
校长并不和学生直接打交道,但是能管理好学生,是通过辅导员向校长报告学生的情况(数据)!管理的本质:是对被管理对象的数据的管理!再如:一个公司可以通过员工的业绩来发放年终奖!如果对象很多,那么在C语言中通过结构体来描述对象的属性(信息),再通过对应的数据结构进行组织!那么管理的本质是:先描述、再组织!
所以,再学习操作系统中会看到struct定义的结构体和大量的数据结构!在C++或Java中,要实现一些功能,也是通过的成员变量描述对象,在使用成员函数组织!
关于系统调用和库: 操作系统不相信任何人!就好比如银行假设的前提是不相信任何人,所以在办理业务的时候通过窗口来帮你办理,计算机科学是人为的科学!同样,操作系统也会提供系统调用接口(窗口)的返回来提供服务,Linux用C语言来编写,所以系统调用就是函数调用,后面fork函数就是一个系统调用;而库,是方便开发者对部分系统调用(当然不止是系统调用)进行适度封装,从而形成库,pthread库就是Linux系统为用户提供的线程操作的库(C标准库也是封装了系统调用接口)!
2.进程
2.1.进程基本的概念
我们使用编译器编写一个C程序,通过编译会形成可执行文件,在Windows下双击就是启动一个进程,在Linux下运行一条命令
,执行 ./可执行文件
在系统层面上创建一个进程。通常在书中:进程是程序的一个执行实例,或者说进程是运行着的程序 ,站在内核的角度出发,能更好理解进程,是分配系统资源(CPU时间,内存)的基本单位! 比如:一个家是资源分配的实体,每个家之间相互独立,每个家有自己的电视机、电冰箱、洗衣机等,在语言层面在栈上开辟空间,在堆上申请空间都是以进程为单位!
平常使用电脑时,都是使用多个进程,听歌的同时写代码!手不停地敲,歌不停地放,似乎多个程序在同时运行,造成并行的假象,对于单个CPU而言这是并发的现象,多个程序交替执行(多进程管理)!计算机当中进程的数量远多于CPU的数量[注],操作系统为每个进程分配时间片(毫秒),时间片结束就会切换进程,但CPU的效率极快,切换的时间极短人感觉不到!当然,一个进程在发生IO操作也会切换进程,因为IO效率很低,为了提升CPU的利用率!CPU不会阻塞等待IO操作执行完,而是当IO操作完成,CPU会收到中断继续执行该进程!如:一个人烧水不会在那里等着水烧开,而是在烧水的期间做点其它事情,等听到水"咕咕咕响"再去用烧开的水泡茶!
注: 进程的竞争性:系统进程数目众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为了高效完成任务,更合理竞争相关资源,便具有了优先级
并发: 多个进程在一个CPU下采用进程切换的方式,在一段时间之内,让多个进程都得以推进
并行:多个进程在多个CPU下分别,同时进行运行
2.2.查看和描述进程
在Windows下可以通过任务管理器查看进程信息
Linux下查看进程:
[xiyan@hecs-34711 ~]$ ls /proc # 系统文件
[xiyan@hecs-34711 ~]$ ps -l # 用户级工具
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1000 22006 22005 0 80 0 - 28920 do_wai pts/3 00:00:00 bash
0 R 1000 22231 22006 0 80 0 - 38332 - pts/3 00:00:00 ps
[xiyan@hecs-34711 ~]$ top # 用户级工具
上面我们先要关注的是PID(进程标识符),本进程的唯一标示符,用来区别其他进程;PPID
通过上面的基础进程查看,会发现系统中大量的进程需要被管理(先描述,再组织)!
进程信息被放在一个叫做进程控制块(PCB:process control block)的数据结构中,在Linux下PCB是task_struck
! task_struct
是Linux内核的一种数据结构是描述进程的结构体!在创建线程时会创建对应的task_struck
,被加载到RAM(内存)中,记录进程的信息!
如果我们查看对应的内核源码,可以发现task_struck
有大量的描述进程的信息:
struct task_struct {
volatile long state;/* -1 unrunnable, 0 runnable, >0 stopped */
void *stack;
atomic_t usage;
unsigned int flags; /* per process flags, defined below */
unsigned int ptrace;
int lock_depth; /* BKL lock depth */
...
/* open file information */
struct files_struct *files; //文件系统
/* namespaces */
struct nsproxy *nsproxy;
/* signal handlers */
struct signal_struct *signal; //信号
struct sighand_struct *sighand;
...
};
注意:所有的进程信息都可以在PCB中找到,如:
- 标示符: 描述本进程的唯一标示符,用来区别其他进程
- 状态: 任务状态,退出代码,退出信号等
- 优先级: 相对于其他进程的优先级
- 上下文数据: 进程执行时处理器的寄存器中的数据
- 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等
- 其他信息
Linux中通过task_struck
结构体对进程进行描述,再通过链表来对结构体进行组织!
2.3.fock创建进程
pid_t fork(void);
功能:创建子进程
返回值:
失败:返回-1
成功:a.给父进程返回子进程的pid;
b.给子进程返回0
pid_t getpid(void);
功能:获取当前进程pid
pid_t getppid(void);
功能:获取当前进程的父进程的pid
代码示例:
#include<iostream>
#include<unistd.h>
using namespace std;
int g_val = 5;
void create_proc()
{
pid_t ret = fork();
if(ret < 0){
cout << "fork create process fail !"<< endl;
}
else if(ret == 0){
printf("this is child process : ret = %d,ret addr = %p\n",ret,&ret);
printf("this is child process : ppid = %d,pid = %d\n",getppid(),getpid());
printf("in child process print g_val = %d,g_val addr = %p\n",g_val,&g_val);
g_val = 10;
printf("after change g_val,in child process print g_val = %d,g_val addr = %p\n",g_val,&g_val);
}
else{
sleep(2); // 让子进程先运行
cout << "--------------------------------" << endl;
printf("this is father process : ret = %d,ret addr = %p\n",ret,&ret);
printf("this is father process : ppid = %d,pid = %d\n",getppid(),getpid());
printf("in father process print g_val = %d,g_val addr = %p\n",g_val,&g_val);
}
printf("create_proc is create once child process\n");
sleep(5);
}
int main()
{
create_proc();
return 0;
}
输出结果:
this is child process : ret = 0,ret addr = 0x7ffde8ec12ac
this is child process : ppid = 27841,pid = 27842
in child process print g_val = 5,g_val addr = 0x60208c
after change g_val,in child process print g_val = 10,g_val addr = 0x60208c
create_proc is create once child process
--------------------------------
this is father process : ret = 27842,ret addr = 0x7ffde8ec12ac
this is father process : ppid = 22006,pid = 27841
in father process print g_val = 5,g_val addr = 0x60208c
create_proc is create once child process
子进程是按照父进程的模板创建出来的,对于代码,父子将共享,数据父子间独有一份,当进程并不是在创建的时候直接区拷贝对应的数据,而是在修改的时候才会发生拷贝(写时拷贝),这样能保证即使是父子进程,也是相互独立的!做到多进程交替运行不相互干扰!
fork函数为什么能有两个返回值
pid_t fork()
{
// 创建子进程的核心代码,子进程已经被创建出来,所有父子进程都会返回,return两次所以有两个返回值
return pid;
}
为什么定义一个变量ret 能接收到fork函数两个返回值
从上面的输出可以看出,一个地址有两个不同的数据,说明该地址不是真是地址!上面的地址时,虚拟地址,通过对应的页表映射到不同的物理地址。上面的虚拟地址相同,数据通过对应映射会存放不同的物理地址处,所以能接收两个返回值!
总结:
- 操作系统是纯正的管理的软件,而管理的本质先描述,在组织
- 进程是运行着的程序,从内核出发进程是资源分配的基本单位;创建一个进程,就是创建对应的数据结构,磁盘中将可执行程序加载到内存中
- fork是Linux创建子进程的系统调用,父子进程谁先调度,有调度器决定;父子进程通过写时拷贝(先这样理解)的技术保证进程的独立性!!!