操作系统进程线程
一、线程和进程
定义:
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程时进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的独立运行的基本单位。
二者区别:
- 进程有自己的独立地址空间,线程没有
- 进程是资源分配的基本单位,线程时CPU调度的最小单位
- 进程和线程通信方式不同(线程之间的通信比较方便,同一个进程下的线程共享资源,比如全局变量,静态变量,通过这些数据来通信不仅快捷而且方便,当然如何处理好这些访问的同步与互斥正式编写多线程程序的难点。而进程之间的通信只能通过进程通信的方式进行。)
- 进程上下文切换开销大,线程开销小;对进程进程操作一般开销都比较大,对线程开销就小了
- 一个进程挂掉了不会影响其他进程,而线程挂掉了会影响其他线程
二、上下文切换
对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换是一种将CPU资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个线程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。
CPU的上下文切换分为三种:进程上下文切换、线程上下文切换、中断上下文切换
系统调用
进程的运行空间分为内核空间和用户空间:
- 内核空间:具有最高权限,可以访问所有资源
- 用户空间:只能访问受限资源,不能直接访问内存等硬件设备(只能直接访问虚拟内存,再由操作系统将虚拟内存的虚拟地址映射到真实内存的真实地址),必须借助系统调用
进程可以在用户空间运行(叫做:进程用户态),也可以在内核空间运行(叫做:进程内核态)。从用户态到内核态需要系统调用完成。
系统调用过程中也会发生CPU上下文切换。CPU寄存器会先保存用户态的状态,然后加载内核态相关内容。系统调用结束之后,CPU寄存器要恢复原来保存的用户态,继续运行进程。所以,一次系统调用,要发生两次CPU上下文切换。
需要注意的是,系统调用过程中,不涉及虚拟内存等进程用户态的资源,也不会切换进程。与通常所说的进程上下文切换不同:
- 进程上下文切换时指,从一个进程切换到另一个进程;
- 系统调用过程中一直是同一个进程在运行。
虚拟内存的概念见:https://cloud.tencent.com/developer/article/1507583
进程上下文切换
进程是由内核管理和调度的,进程的切换只能发生在内核态(意思是只能由操作系统的程序或代码来做这件事,用户程序做不了)。当一个进程从内核中移出,另一个进程成为活动的,这些进程之间便发生了上下文切换。操作系统必须记录重启进程和启动新进程使之活动所需要的所有信息,这些信息被称作上下文,它描述了进程的现有状态。
进程上下文信息包括,指向可执行文件的指针,栈,内存(数据段和帧),进程状态,优先级,程序I/O的状态,授予权限,调度信息,审计信息,有关资源的信息(文件描述符和读/写指针),关时间和信号的信息,寄存器组(栈指针,指定计数器)等等,诸如此类。
进程的上下文不但包括虚拟内存、栈、全局变量等用户空间资源,还包括内核堆栈、寄存器等内核空间状态。所以,进程的上下文切换比系统调用多了一个步骤:保存当前进程的内核状态和CPU寄存器之前,先把该进程的虚拟内存、栈等保存起来;加载下一个进程的内核态后,还需要刷新进程的虚拟内存和用户栈(我的理解:也就是不仅要保存用户空间信息,还要保存内核空间信息;加载下一个进程的内核态后,还要刷新用户空间)。
进程切换时需要切换上下文,进程切换的场景有:
- 进程时间片耗尽
- 系统资源不足(如内存不足)
- 进程通过sleep把自己挂起来
- 当有优先级更高的进程运行时,为了去运行高优先级进程,当前进程会被挂起
- 发生硬中断,CPU上的今后才能会被挂起,然后去执行内核中的中断服务进程
线程上下文切换
线程时CPU调度的基本单位,而进程则是资源分配的基本单位。
内核中的任务调度实际是在调度线程,进程只是给线程提供虚拟内存、全局变量等资源。线程上下文切换时,共享相同虚拟内存和全局变量等资源不需要修改。而线程自己的私有数据,如栈和寄存器等,上下文切换时需要保存。
线程切换分两种情况:
- 前后两个线程属于不同进程;
- 前后两个线程属于同一进程(速度更快,消耗更少资源)。
进程切换与线程切换的一个最主要区别就在于进程切换涉及到虚拟地址空间的切换而线程切换则不会。https://blog.csdn.net/github_37382319/article/details/97273713
中断上下文切换
为了快速响应硬件的事件,中断处理会打断进程的正常调度和执行,然后调用中断处理程序,响应设备事件。在打断其他进程时,需要先将进程当前的状态保存下来,等中断结束后,进程仍然可以恢复回来。
跟进程上下文不同,中断上下文切换不涉及进程的用户态。所以,即使中断过程打断了一个正处在用户态的进程,也不需要保存和恢复这个进程的虚拟内存等用户态资源。中断上下文,只包括内核态中断服务程序执行所必需的状态,也就是CPU寄存器、内核对阵、硬件中断参数等(内核态信息)。
对同一个CPU来说,中断处理比进程拥有更高的优先级,所以中断上下文切换不会与进程上下文切换同时发生。并且,由于中断会打断正常进程的调度和执行,所以大部分中断处理程序都短小精悍,以便可以尽快完成。
三、僵尸进程和孤儿进程
在讲这章之前,先了解一下fork()
原文链接:https://blog.csdn.net/tong_xin2010/article/details/38469471
#include <unistd.h> #include <sys/types.h> main () { pid_t pid; pid=fork(); if (pid < 0) printf("error in fork!"); else if (pid == 0) printf("i am the child process, my process id is %d\n",getpid()); else printf("i am the parent process, my process id is %d\n",getpid()); }
结果是
[root@localhost c]# ./a.out
i am the child process, my process id is 4286
i am the parent process, my process id is 4285
不了解fork的人,应该不理解输出的结果为啥是这样的,为什么两个条件中的语句都能执行,整个执行的过程是怎样的呢?
其实是fork()函数虽然调用了一次,但却返回了两次。
pid = fork();
这条语句之后操作系统创建一个新的进程(子进程),新进程(子进程)和原有进程(父进程)的可执行程序是同一个程序;上下文和数据,绝大部分是父进程的拷贝,但是他们是两个相互独立的进程。此时程序计数器pc在父、子进程的上下文中都声称,这个进程目前执行到fork()调用即将返回(此时子进程不占有CPU,子进程的pc不是真正保存在寄存器中的,而是作为进程上下文保存在进程表中的对应表项中)。
父进程继续执行,操作系统对fork的实现,使这个调用在父进程中返回刚刚创建的子进程的pid(所以返回的pid > 0 执行大于0的分支);
子进程之后在某个时候得到调度,它的上下文被换入,占用CPU,操作系统对fork的实现,使得子进程中fork调用返回0。所以这个进程中的pid == 0,执行等于0的分支。
总结:在父进程fork出子进程后,两个进程在得到调度之后程序计数器都从fork函数返回开始执行,父进程中返回子进程pid,子进程中返回0. 但是哪个进程先执行,这个和操作系统的调度算法等等很多因素有关。
PID/PPID
- PID(process ID):是程序被操作系统加载到内存成为进程后动态分配的资源。每次程序执行的时候,操作系统会重新加载,PID在每次加载的时候都是不同的。
- PPID(parent process ID):是程序的父进程号。
- PID和PPID都是非零的整数。
特别说明:所有进程追溯其祖先最终都会落在进程号为1的进程身上,这个进程叫init进程,是linux内核启动后第一个执行的进程,引导系统,启动守护进程并且运行必要的程序。
僵尸进程
1. 定义:
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或者waitpid获取子进程的状态信息,那么子进程的进程描述符等一系列信息还会保存在系统中。这种进程称之为僵尸进程。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <errno.h> 4 #include <stdlib.h> 5 6 int main() 7 { 8 pid_t pid; 9 pid = fork(); 10 if (pid < 0) 11 { 12 perror("fork error:"); 13 exit(1); 14 } 15 else if (pid == 0) 16 { 17 printf("I am child process.I am exiting.\n"); 18 exit(0); 19 } 20 printf("I am father process.I will sleep two seconds\n"); 21 //等待子进程先退出 22 sleep(2); 23 //输出进程信息 24 system("ps -o pid,ppid,state,tty,command"); 25 printf("father process is exiting.\n"); 26 return 0; 27 }
测试结果:

1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 6 int main() 7 { 8 pid_t pid; 9 //循环创建子进程 10 while(1) 11 { 12 pid = fork(); 13 if (pid < 0) 14 { 15 perror("fork error:"); 16 exit(1); 17 } 18 else if (pid == 0) 19 { 20 printf("I am a child process.\nI am exiting.\n"); 21 //子进程退出,成为僵尸进程 22 exit(0); 23 } 24 else 25 { 26 //父进程休眠20s继续创建子进程 27 sleep(20); 28 continue; 29 } 30 } 31 return 0; 32 }
测试结果:

原文链接:https://www.cnblogs.com/Anker/p/3271773.html
waitpid()函数的用法见https://blog.csdn.net/Roland_Sun/article/details/32084825
2. 危害
在 Unix 系统管理中,当用 ps 命令观察进程的执行状态时,经常看到某些进程的状态栏为 defunct,这就是所谓的 “僵尸” 进程。“僵尸” 进程是一个早已死亡的进程,但在进程表(processs table)中仍占了一个位置(slot)。由于进程表的容量是有限的,所以,defunct 进程不仅占用系统的内存资源,影响系统的性能,而且如果其数目太多,还会导致系统瘫痪。
3. 处理方法:
- 改写父进程,捕获子进程退出的信号。具体做法是接管SIGCHLD信号。子进程死后,会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。这是基于这样的原理:就算父进程没有调用wait,内核也会向它发送SIGCHLD消息,尽管默认处理是忽略,如果想响应这个消息,可以设置一个处理函数。
//子进程退出时向父进程发送SIGCHLD信号,父进程处理这个信号,在信号处理函数中调用wait进行处理僵尸进程。 #include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h> #include <signal.h> static void sig_child(int signo); int main() { pid_t pid; //创建捕捉子进程退出信号 signal(SIGCHLD,sig_child); pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } else if (pid == 0) { printf("I am child process,pid id %d.I am exiting.\n",getpid()); exit(0); } printf("I am father process.I will sleep two seconds\n"); //等待子进程先退出 sleep(2); //输出进程信息 system("ps -o pid,ppid,state,tty,command"); printf("father process is exiting.\n"); return 0; } static void sig_child(int signo) { pid_t pid; int stat; //处理僵尸进程 while ((pid = waitpid(-1, &stat, WNOHANG)) >0) printf("child %d terminated.\n", pid); }

- 把父进程杀掉。父进程死后,僵尸进程成为“孤儿进程”,过继给1号init进程,init始终会负责清理僵尸进程。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> int main() { pid_t pid; //创建第一个子进程 pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } //第一个子进程 else if (pid == 0) { //子进程再创建子进程 printf("I am the first child process.pid:%d\tppid:%d\n",getpid(),getppid()); pid = fork(); if (pid < 0) { perror("fork error:"); exit(1); } //第一个子进程退出 else if (pid >0) { printf("first procee is exited.\n"); exit(0); } //第二个子进程 //睡眠3s保证第一个子进程退出,这样第二个子进程的父亲就是init进程里 sleep(3); printf("I am the second child process.pid: %d\tppid:%d\n",getpid(),getppid()); exit(0); } //父进程处理第一个子进程退出 if (waitpid(pid, NULL, 0) != pid) { perror("waitepid error:"); exit(1); } exit(0); return 0; }

孤儿进程
父进程运行结束,但子进程还在运行(未运行结束)的子进程就成为孤儿进程。
孤儿进程最终会被init进程(进程号为1)收养,因此init进程此时变成孤儿进程的父进程,并由init进程对它们完成状态收集工作。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <errno.h> 4 #include <unistd.h> 5 6 int main() 7 { 8 pid_t pid; 9 //创建一个进程 10 pid = fork(); 11 //创建失败 12 if (pid < 0) 13 { 14 perror("fork error:"); 15 exit(1); 16 } 17 //子进程 18 if (pid == 0) 19 { 20 printf("I am the child process.\n"); 21 //输出进程ID和父进程ID 22 printf("pid: %d\tppid:%d\n",getpid(),getppid()); 23 printf("I will sleep five seconds.\n"); 24 //睡眠5s,保证父进程先退出 25 sleep(5); 26 printf("pid: %d\tppid:%d\n",getpid(),getppid()); 27 printf("child process is exited.\n"); 28 } 29 //父进程 30 else 31 { 32 printf("I am father process.\n"); 33 //父进程睡眠1s,保证子进程输出进程id 34 sleep(1); 35 printf("father process is exited.\n"); 36 } 37 return 0; 38 }
测试结果:

四、进程的通信方式(IPC,Inter-Process Communication进程间通信)
详细见https://www.jianshu.com/p/c1015f5ffa74
1. 管道:
匿名管道(pipe)
- 半双工,单向的、先进先出的;需要双方通信时,需要建立起两个管道。
- 只能用于父子进程或兄弟进程之间(具有亲缘关系的进程)。
- 单独构成的一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,单独构成一种文件系统,并且只存在于内存中。管道的实质是内核缓冲区。
- 数据的读出和写入:写进程在管道的尾端写入数据,读进程在管道的道端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其他进程从管道移走数据之前,写进程将一直阻塞。
局限:
- 只支持单向数据流
- 只能用于有亲缘关系的进程之间
- 没有名字
- 管道的缓冲区有限的
- 无格式字节流,读出方和写入方必须实现约定好数据的格式,比如多少字节算作一个消息(或命令、或记录)等等。
有名管道(FIFO)
不同于匿名管道,它提供了一个路径名与之关联,以有名管道的文件形式存在于文件系统中,这样,即使与有名管道的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过有名管道相互通信。有名管道名字存在于文件系统中,内容存放在内存中。
2. 信号
- 信号是Linux系统中用于进程间互相通信或者操作的一种机制,信号可以在任何时候发给某一进程,而无需知道该进程的状态。
- 如果该进程当前并未处于执行状态,则该信号就由内核保存起来,直到该进程恢复执行并传递给它为止。
- 如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消是才被传递给进程。
3. 消息队列:
- 消息队列是存放在内核中的消息链表,每个消息队列由消息队列标识符表示。
- 与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。
- 另外与管道不同的是,消息队列在某个进程往一个队列写入消息之前,并不需要另外某个进程在该队列上等待消息的到达。
(1)消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识.
(2)消息队列允许一个或多个进程向它写入与读取消息.
(3)管道和消息队列的通信数据都是先进先出的原则。
(4)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比FIFO更有优势。
(5)消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。
(6)目前主要有两种类型的消息队列:POSIX消息队列以及System V消息队列,系统V消息队列目前被大量使用。系统V消息队列是随内核持续的,只有在内核重起或者人工删除时,该消息队列才会被删除。
4. 共享内存:
共享内存允许两个或多个进程访问同一个逻辑内存。这一段内存可以被两个或两个以上的进程映射至自身的地址空间中,一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取读出,从而实现了进程间的通信。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。共享内存是最快的IPC方式,它是针对其它进程间通信方式运行效率低而专门设计的。它往往与其它通信机制(如信号量)配合使用,来实现进程间的同步和通信。
5. 信号量:
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
(1)信号量是非负整型变量,除了初始化之外,它只能通过两个标准原子操作:wait(semap) , signal(semap) ; 来进行访问;
(2)操作也被成为PV原语(P来源于荷兰语proberen"测试",V来源于荷兰语verhogen"增加",P表示通过的意思,V表示释放的意思),而普通整型变量则可以在任何语句块中被访问;
(1)互斥量用于线程的互斥,信号量用于线程的同步。这是互斥量和信号量的根本区别,也就是互斥和同步之间的区别。
互斥:是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
同步:是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
在大多数情况下,同步已经实现了互斥,特别是所有写入资源的情况必定是互斥的。少数情况是指可以允许多个访问者同时访问资源
(2)互斥量值只能为0/1,信号量值可以为非负整数。
也就是说,一个互斥量只能用于一个资源的互斥访问,它不能实现多个资源的多线程互斥问题。信号量可以实现多个同类资源的多线程互斥和同步。当信号量为单值信号量是,也可以完成一个资源的互斥访问。
(3)互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到。
6. 套接字:
套接字是一种通信机制,凭借这种机制,客户/服务器(即要进行通信的进程)系统的开发工作既可以在本地单机上进行,也可以跨网络进行。也就是说它可以让不在同一台计算机但通过网络连接计算机上的进程进行通信。
五、进程调度
概念
- 非抢占式:分派程序一旦把处理机分配给某进程后便把它一直运行下去,直到进程完成或发生进程调度某事件而阻塞时,才把处理机分配给另一个进程。
- 抢占式:操作系统将正在运行的进程强行暂停,由调度程序将CPU分配给其他就绪进程的调度方式。
CPU任务可以分为交互式任务和批处理任务,调度最终的目标是合理的使用CPU,使得交互式任务的响应时间尽可能短,用户不至于感到延迟,同时使得批处理任务的周转时间尽可能短,减少用户等待时间。
- 响应时间(交互式任务):从用户输入到产生反应的时间
- 周转时间(批处理任务):从任务开始到任务结束的时间
进程调度算法
批处理任务:
- 先来先服务(FCFS)
- 短作业优先(SJF)
- 最短剩余时间优先(SRTN)
- 优先权调度
交互式任务
- 时间片轮转
- 多级调度队列
- 多级反馈队列
1. FIFO或First Come, First Served(FCFS)先来先服务
- 调度的顺序就是任务到达就绪队列的顺序
- 公平、简单(FIFO队列)、非抢占、不适合交互式
- 未考虑任务特性,平均等待时间可以缩短
2. Shortest Job First(SJF)最短作业优先
- 最短的作业(CPU区间长度最小)最先调度
- 由于作业的长短只是根据用户所提供的估计执行时间而定的,而用户又可能会有意或无意地缩短其作业的估计运行时间,致使该算法不一定能真正做到短作业优先调度
- SJF可以保证最小的平均等待时间
3. Shortest Remaining Job First(SRJF)最短剩余作业优先
- SJF的可抢占版本,比SJF更有优势
- SJF(SRJF):如何知道下一CPU区间大小?根据历史进行预测:指数平均法
4. 优先权调度
- 每个任务关联一个优先权,调度优先权最高的任务
- 优先权太低的任务一直就绪,得不到运行,出现“饥饿”现象
5. Round-Robin(RR)轮转调度算法
- 设置一个时间片,按时间片来轮转调度("轮转"算法)
- 优点:定时有响应,等待时间较短
- 缺点:上下文切换次数较多
- 时间片太大,响应时间太长;吞吐量变小,周转时间变长;当时间片过长时,退化为FCFS。
6. 多级队列调度
- 按照一定的规则建立多个进程队列
- 不同的队列有固定的优先级(高优先级有抢占权)
- 不同的队列可以给不同的时间片和采用不同的调度方法
- 存在问题1:没法区分I/O bound(I/O密集型)和CPU bound(CPU密集型)
- 存在问题2:也存在一定程度的“饥饿”现象
7. 多级反馈队列
设有N个队列(Q1,Q2….QN),一般来说,优先级Priority(Q1) > Priority(Q2) > … > Priority(QN)。各个队列中的作业(进程)的优先级也是不一样的。位于Q1中的任何一个作业(进程)都要比Q2中的任何一个作业(进程)相对于CPU的优先级要高(也就是说,Q1中的作业一定要比Q2中的作业先被处理机调度),依次类推其它的队列。对于某个特定的队列来说,里面是遵循时间片轮转法。各个队列的时间片也是不一样的,各个队列的时间片是随着优先级的增加而减少的,也就是说,优先级越高的队列中它的时间片就越短。
- 在多级队列的基础上,任务可以在队列之间移动,更细致的区分任务
- 可以根据“享用”CPU时间多少来移动队列,阻止“饥饿”
- 最通用的调度算法,多数OS都使用该方法或其变形,如UNIX 、Windows等
算法详解:
- 进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。
- 首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。
- 对于同一个队列中的各个进程,按照时间片轮转法调度。比如Q1队列的时间片为N,那么Q1中的作业在经历了N个时间片后若还没有完成,则进入Q2队列等待,若Q2的时间片用完后作业还不能完成,一直进入下一级队列,直至完成。
- 在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业(抢占式)。
我们来看一下该算法是如何运作的
假设系统中有3个反馈队列Q1,Q2,Q3,时间片分别为2,4,8。
现在有3个作业J1,J2,J3分别在时间 0 ,1,3时刻到达。而它们所需要的CPU时间分别是3,2,1个时间片。
1、时刻0 J1到达。于是进入到队列1 , 运行1个时间片 , 时间片还未到,此时J2到达。
2、时刻1 J2到达。 由于时间片仍然由J1掌控,于是等待。 J1在运行了1个时间片后,已经完成了在Q1中的
2个时间片的限制,于是J1置于Q2等待被调度。现在处理机分配给J2。
3、时刻2 J1进入Q2等待调度,J2获得CPU开始运行。
4、时刻3 J3到达,由于J2的时间片未到,故J3在Q1等待调度,J1也在Q2等待调度。
5、时刻4 J2处理完成,由于J3,J1都在等待调度,但是J3所在的队列比J1所在的队列的优先级要高,于是J3被调度,J1继续在Q2等待。
6、时刻5 J3经过1个时间片,完成。
7、时刻6 由于Q1已经空闲,于是开始调度Q2中的作业,则J1得到处理器开始运行。 J1再经过一个时间片,完成了任务。于是整个调度过程结束。
从上面的例子看,在多级反馈队列中,后进的作业不一定慢完成。
操作系统内存和中断
一、虚拟内存
虚拟内存实现原理见,讲的比较清楚https://www.bilibili.com/video/BV1yW411S7UL?from=search&seid=16344676726946735756
为什么采用分页内存管理,为了提高内存利用率,参考https://blog.csdn.net/qq_39755395/article/details/78380942
1. 定义:具有请求调入功能和置换功能(当内存耗尽时,电脑就会自动调用硬盘来充当内存,以缓解内存的紧张。当需要相应的数据并且不在内存中时,就会从硬盘中读出调入内存),能从逻辑上对内存容量加以扩充的一种存储器系统。其逻辑容量由内存之和和外存之和决定。
它使得应用程序认为它拥有连续的可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。使计算机能够运行比内存大的应用程序。
2. 与传统存储器比较虚拟存储器有以下三个主要特征:
- 多次性,是指无需在作业运行时一次性地全部装入内存,而是允许被分成多次调入内存运行。
- 对称性,是指无需在作业运行时一直常驻内存,而是允许在作业的运行过程中,进行换进和换出。
- 虚拟性,是指从逻辑上扩充内存容量,使得用户所看到的内存容量,远大于实际的内存容量。
3. 引入虚拟内存的原因
在没有虚拟内存概念的时候,程序寻址用的都是物理地址。程序能寻址的范围都是有限的,这取决于CPU的地址线条数。比如在32位平台下,寻址的范围是2^32也就是4G(2^10是1K,2^20是1M,2^30是1G)。并且是固定的。我们要运行一个程序,会把程序全部装入内存,然后运行,如果这个程序所需要的内存空间不超过物理内存空间的大小,就不会有问题。但是当同时运行多个程序时,计算机如何把有限的物理地址分配给多个程序使用呢,就会出现以下的问题:
1)进程地址空间不隔离,没有权限保护
由于指令都是直接访问物理内存的,那么我这个进程就可以修改其他进程的数据,甚至会修改内核地址空间(通常32位Linux内核地址空间划分0~3G为用户空间,3~4G为内核空间。注意这里是32位内核地址空间划分,64位内核地址空间划分是不同的。)的数据。
2)内存使用效率低
因为物理内存是有限的,当有多个进程要执行的时候,内存很快就会分配完了。在A,B都运行的情况下,如果用户又运行了程序C,程序C需要20M内存才能运行,而此时系统只剩下8M的空间可用,所以系统必须在已运行的程序中选择一个将其数据暂时拷贝到硬盘上,释放出部分空间来供C使用,然后再将C的数据全部装入内存中运行。在这个过程中,有大量的数据在装入装出,导致效率十分低下。
3)程序运行的地址不确定
当内存中的剩余空间可以满足C的要求后,操作系统会在剩余空间中随机分配一段连续的20M大小的内存供C使用,因为内存是随机分配的,所以程序运行的地址也是不确定的。但是某些汇编指令是要在固定的地址上开始执行的,如果这个地址后边被程序占有,那么执行完这条指令之后就会对这块内存进行修改,就可能导致程序瘫痪。
4. 虚拟地址和物理地址
物理地址:物理地址空间是实在的存在于计算机中的一个实体,在每一台计算机中保持唯一独立性。我们可以称它为物理内存;在32位机上,物理空间的大小理论上可以达到2^32字节(4GB),这是可以达到的最大内存。
虚拟地址:虚拟地址并不真实存在于计算机中(这个虚拟地址只是每个进程中自己的地址,并不是存储数据的真实地址)。每个进程都分配有自己的虚拟空间,而且只能访问自己被分配使用的空间。理论上,虚拟空间受物理内存大小的限制,如给4G内存,那么虚拟地址空间的地址范围就应该是0x00000000~0xFFFFFFFF。每个进程都有自己独立的虚拟地址空间,这样每个进程都能访问自己的地址空间,这样做到了有效隔离。
虚拟地址和物理地址之间的映射是通过MMU(内存管理单元)来完成的。我们平时操作的内存其实都是通过操作虚拟地址的内存单元,通过MMU的映射来间接操作物理地址。
5. 对虚拟内存的理解
1)每个进程都有自己独立的4G内存空间,各个进程的内存空间具有类似的结构
2)一个新进程建立的时候,将会建立起自己的内存空间,此进程的数据,代码等从磁盘拷贝到自己的进程空间,哪些数据在哪里,都由进程控制表中的task_struct记录,task_struct中记录一条链表,记录内存空间的分配情况,哪些地址有数据,哪些地址无数据,哪些可读,哪些可写。
3)每个进程已经分配的内存空间,都与对应的磁盘空间映射
4)每个进程的内存空间只是虚拟内存空间,每次访问内存空间的某个地址,都需要把地址翻译成实际物理内存地址
5)所有进程共享同一物理内存,每个进程只把自己目前需要的虚拟内存空间映射并存储到物理内存上。
6)进程要知道哪些内存地址上的数据再物理内存上,哪些不在。如果在,在物理内存上的哪里;如果不在,则存放在磁盘的哪个位置。需要用页表来记录;
7)页表的每一个表项分为两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)
8)当进程访问某个虚拟地址,去看页表,如果发现对应的数据不在物理内存中,则缺页异常
9)缺页异常的处理过程,就是把进程需要的数据从磁盘上拷贝到物理内存中,如果内存已经满了,那就找一个页覆盖,如果被覆盖的页修改过,需要将此页写回磁盘。
可以认为虚拟空间都被映射到了磁盘空间中,并且由页表记录映射位置,当访问到某个地址的时候,通过页表中的有效位,可以得知此数据是否在内存中。
事实上,在每个进程创建加载时,内核只是为进程“创建”了虚拟内存布局,具体就是初始化进程控制表中内存相关的链表,实际上并不立即把虚拟内存对应位置的程序数据和代码(比如.text .data段)拷贝到物理内存中,只是建立好虚拟内存和磁盘文件之间的映射,等到运行到对应的程序时,才会通过缺页异常,拷贝数据。还有进程运行过程中,要动态分配内存,比如malloc时,也只是分配了虚拟内存,即为这块虚拟内存对应的页表项做相应设置,当进程真正访问到此数据时,才引发缺页异常。


原文链接:https://blog.csdn.net/fengxinlinux/article/details/52071766
6. 虚拟也和物理页的关系
在缓存原理中,数据块是磁盘和主存之间的传输单元。在内存管理中,系统将虚拟内存分割为称为虚拟页(VP)的大小固定的块来处理这个问题。类似的,物理内存被分割为物理页(PP,也被称为页帧)。虚拟页和物理页的大小是一样的。
在任何时刻,虚拟页面的集合都分为三个不相交的子集:
- 未分配的:系统还未分配(或者创建)的页。未分配的块没有任何数据和它们相关联,因此也就不占用任何磁盘空间。
- 缓存的:当前已缓存在物理内存中的已分配页。
- 未缓存的:未缓存的物理内存中的已分配页。
如图展示了一个有8个虚拟页的小虚拟内存(存放在磁盘上):

DRAM缓存表示虚拟内存系统的缓存,它在主存中缓存虚拟页。
7. 虚拟地址的工作原理
页表就是一个页表条目(PTE)的数组,存放在物理内存中。虚拟地址空间中的每个页在页表中都有一个PTE。PTE由一个有效位和一个n位地址字段组成的。
- 如果设置了有效位为1,那么就表示已缓存到物理内存,地址字段是物理页的起始地址。
- 如果没设置有效位,表示这个虚拟页还未分配。
- 设置了有效位为0,这个地址就指向该虚拟页在磁盘上的起始位置。
页表结构如图:

进程执行时,当需要访问虚拟地址中存放的值时,步骤如下:
1)CPU会先找到虚拟地址所在的虚拟页(VP3),根据页表,找出页表中第3条的值。
判断有效位,为1,DRMA缓存命中,获根据物理页号,找到物理页中的内容,返回。
2)若有效位为0,产生缺页异常,调用内核缺页异常处理程序。
它会选择一个物理页(如PP4),作为牺牲页,将该页的内容刷新到磁盘文件。然后,把VP3映射的磁盘文件,缓存到该物理页。
页表中的第3条,有效位变1,同时,物理页号表号变为PP4。
3)缺页异常处理完毕后,返回中断前的指令,重新执行,此时缓存命中,执行1)
4)将找到的内容映射到高速缓存,CPU从高速缓存中获取该值,结束。
二、页面置换算法
1. 最优页面置换算法(Optimal)
最理想的状态下,我们给页面做个标记,挑选一个最远才会被再次用到的页面调出。当然,这样的算法不可能实现,因为不确定一个页面在何时会被用到。
2. 先进先出页面置换算法(FIFO)
操作系统维护一个所有当前在内存中的页面的链表,最新进入的页面放在表尾,最久进入的页面放在表头。当发生缺页中断时,淘汰表头的页面并把新调入的页面加在表尾。
缺点:对于有些经常被访问的页面如含有全局变量、常用函数、例程等的页面,不能保证这些不被淘汰。
FIFO 算法还会产生当所分配的物理块数增大而页故障数不减反增的异常现象,称为 Belady 异常。
3. 最近最少使用页面置换算法(LRU)
根据页面调入内存后的使用情况做出决策。LRU 置换算法是选择最近最久未使用的页面进行淘汰。
利用局部性原理,根据一个作业在执行过程中过去的页面访问历史来推测未来的行为。它认为过去一段时间里不曾被访问过的页面,在最近的将来可能也不会再被访问。所以,这种算法的实质是:当需要淘汰一个页面时,总是选择在最近一段时间内最久不用的页面予以淘汰。 即淘汰最近最长时间未访问过的页面。
三、中断
所谓的中断就是在计算机执行程序的过程中,由于出现了某些特殊的事情,使得CPU暂停对程序执行,转而去执行处理这一事件的程序。等这些特殊事情处理完之后再回去执行之前的程序。操作系统是“中断驱动”的,换言之,中断是激活操作系统的唯一方式。
中断一般分为三类:
- 由计算机硬件异常或故障引起的中断,称为内部异常中断;
- 由程序中执行了引起中断的指令而造成的中断,称为软中断(与系统调用相关的中断);
- 由外部设备请求引起的中断,称为外部中断。
四、系统调用
进程的执行在系统上有两个级别:用户级和核心级,也称为用户态和内核态。这两个概念在上下文切换也提到过。内核态下的进程能够存取内核和用户地址,某些机器指令是特权指令,在用户态下执行特权指令会引起错误。
进程可以在用户空间运行,也可以在内核空间运行。从用户态到内核态需要系统调用完成。
用户态切换到内核态的方式如下:
- 系统调用:操作系统提供的函数就被称为系统调用(system call)。程序的执行一般是在用户态下执行的,但当程序需要使用操作系统提供的服务时,比如打开某一设备、创建文件、读写文件(这些均属于系统调用)等,就需要向操作系统发出调用服务的请求,这就是系统调用。
- 异常:当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。
- 外围设备的中断:当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序,如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态向内核态的切换。比如硬盘读写操作,系统会切换到硬盘读写的中断处理程序中执行后续操作等。
补充:陷阱、异常、中断
- 这里的中断指的是外部中断。通常国内教材只有在特指外部中断时才说是中断,而内部异常通常指的是陷阱、故障、终止。
- 陷阱,是有意的异常,重要的用途就是在用户程序和内核之间提供像过程调用一样的接口,称为系统调用。所以系统调用是陷阱的一种,同时陷阱只是内部异常的一种。
- 故障:故障由错误引起,常见的比如segment fault,缺页异常,如果系统能够处理,那就会回到当前指令继续执行,否则终止程序。
- 终止:不可恢复。
操作系统的互斥和同步
一、基本概念
临界区:对共享内存进行访问的程序片段。
互斥:当一个进程在使用一个共享变量或文件时,其他进程不能做同样的操作。
同步:在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。例如生产者消费者问题。
互斥量和信号量的区别:
互斥量:一个互斥量在某个时刻只能被一个线程占用,如果其他线程想要获得这个互斥量的使用权就需要等待,然后执行需要保护的代码,最后释放互斥量,下一个等待的线程获得使用权,这样就可以保证某些内存的原子性访问。
信号量:本质上和互斥量一样,都是用来表示对资源的访问权,但是互斥量表示资源某个时刻最多只被一个线程占用,也就是资源计数最多是1,而信号量的资源计数可以超过1,即同时被多个线程占用。
不管是信号量还是互斥量,线程在等待的时候会从用户态进入内核态,从而不再占用CPU时间,当获得该信号量或互斥量的访问权时,再由OS调度回用户态,由于上下文切换以及调度算法等原因,总得来说开销是比较大的,对性能会有损耗。
死锁:两个或多个进程被无限期地阻塞、相互等待的一种状态。
四个必要条件:互斥、请求与保持、非抢占、循环等待
二、信号量
信号量:用于进程间或线程间传递信号的一个整数。在信号量上只可以进行三种操作,即初始化、递增、递减。这三种操作分别都是原子操作。递减作用于阻塞一个进程,递增作用于解除一个进程的阻塞或释放一个资源。
信号量是不要求忙等的同步互斥工具。一种信号量表示一种资源。信号量的值表示可用资源的个数。当资源不可用时,进程将阻塞(避免忙等,加入阻塞队列),直到另一个进程释放资源时才被唤醒。
信号量S只能被下面的两个原语访问:
- senWait(s)也称作P(s)操作,wait(s)-----本进程请求分配一个资源
- senSignal(s)也称作V(s)操作,signal(s)------本进程释放一个资源
注意:
-
semWaits (s)、semSignal (s) 操作必须成对出现。
-
用于互斥时,位于同一进程内,临界区前后。
-
用于同步时,交错出现于两个合作进程内。
-
多个 semWait () 的次数不能颠倒,否则可能导致死锁。用于同步的 semWait (s1) 应出现在用于互斥的 semWait (s2) 之前。
-
多个 semSignal () 操作的次序可任意。
总结:
- P(s):如果s是非零的,那么P将s减1,并且立即返回。如果s为零,那么就挂起这个线程,知道s变为非零,而一个V操作会重启这个线程。在重启之后,P操作将s减1,并将控制返回给调用者。
- V(s):V操作将s加1。如果有任何线程阻塞在P操作等待s变为非零,那么V操作会重启这些线程中的一个,然后该线程将s减1,完成它的P操作。
Linux的IO模式
介绍
1. 文件描述符fd
Linux将所有设备都当做文件来处理,文件描述符来标识每个文件对象。
当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
2. 缓存IO(可以结合之前的虚拟内存分页一起看)
操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核,保证内核的安全,操作系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。
Linux的缓存IO机制中,操作系统会将IO的数据缓存在文件系统的页缓存中, 也就是说,数据会先被拷贝到操作系统内核的缓冲区,然后才会从内核的缓冲区拷贝到用户缓冲区中。
中断驱动IO和使用DMA的IO:
中断驱动IO的一个明显缺点是中断发生在每个字符上。中断要花费时间,所以这一方法将浪费一定数量的CPU时间。解决这一问题的方法是使用DMA。
DMA控制器一次给打印机提供一个字符,而不必打扰CPU。DMA重大的成功是将中断的次数从打印每个字符一次到打印每个缓冲区一次。
使用场景:
- 有许多字符并且中断十分缓慢,用DMA。
- 由于DMA控制器通常比主CPU要慢得多。如果DMA控制器不能全速驱动设备,或者CPU在等待DMA中断的同时没有其他事情要做,采用中断驱动IO。
文件IO的内核缓冲,复制了两次:
read和write系统调用在操作磁盘文件时不会直接发起磁盘访问,而仅是在用户空间缓冲区和内核缓冲区之间复制数据。
write调用后会立即返回,在后续某个时刻内核才会将缓冲区中的数据写入磁盘,即系统调用和磁盘操作并不同步。如果在此期间,另一进程试图读取文件的这几个字节,那么内核将自动从缓冲区中提供这些数据,而不是从文件中读取。
同理,对read而言,内核从磁盘中读取数据并存储到内核缓冲区中。read调用将从该缓冲区中读取数据,直至把缓冲区读完,这时内核会将文件的下一段内容读入缓冲区(内核通常会执行预读)。
其中第一次是通过DMA的方式将数据从磁盘复制到内核缓冲区,本次过程只需要CPU在一开始的时候让出总线、结束之后处理DMA中断即可,中间不需要CPU直接干预,CPU可以去做别的事情;第二次将数据从内核缓冲区复制到用户缓冲区,这个过程需要CPU的全程干预,浪费CPU的时间和额外的物理内存空间。
IO模式
以网络IO为例,在IO操作过程会涉及到两个对象:
- 一个是调用这个IO的process (or thread);
- 另外一个就是系统内核(kernel)。
在一个IO操作过程中,以read为例,会涉及到两个过程:
- 等待数据准备好(Waiting for the data to be ready);
- 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
常见的IO模型:
- 同步阻塞IO(blocking IO)
- 同步非阻塞IO(nonblocking IO)
- IO多路复用(IO multiplexing)
- 异步IO(asynchronous IO)
1. 同步阻塞IO(BIO)

当用户进程调用了 recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据(对于网络 IO 来说,很多时候数据在一开始还没有到达。比如,还没有收到一个完整的 UDP 包。这个时候 kernel 就要等待足够的数据到来)。这个过程需要等待,也就是说数据被拷贝到操作系统内核的缓冲区中是需要一个过程的。而在用户进程这边,整个进程会被阻塞(当然,是进程自己选择的阻塞)。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。
所以,blocking IO 的特点就是在:
- 在1/2阶段都发生阻塞;
- 调用阻塞IO会一直阻塞进程,直到操作完成。
2. 同步非阻塞IO(NIO)
Linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时:

当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度讲 ,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回。
所以,nonblocking IO 的特点是:
- 在第一阶段没有阻塞,在第二阶段发生阻塞
- 调用非阻塞IO会在kernel准备数组的情况下立即返回
- 非阻塞IO需要不断轮训,查看数据时候已经准备好了
3. 异步IO

用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel 的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。
4. IO多路复用https://zhuanlan.zhihu.com/p/36764771
多路复用与阻塞IO相比,优势在于一个进程能处理多个连接,将多个连接使用一次系统调用一起交给内核处理,对于select来说,是内核遍历所有文件描述符,当其中有的数据已经准备好,即已经拷贝到内核缓冲区中,就返回状态。select函数返回后,就遍历文件描述符,来找到就绪的文件描述符。对于epoll来说,是事件驱动的,内核不需要遍历所有的文件描述符,当有文件描述符就绪时,就会产生系统中断,来把就绪的文件描述符放在一起并返回。
多路复用与非阻塞IO相比,优势在于,如果没有多路复用,如果有10万个IO,就会发生10万次系统调用,看有没有读写。如果是IO多路复用,比如用select,就只需select一次系统调用,然后这个哪个文件描述符可读写这个轮询的任务就交给系统内核去做了,少了非常多次的系统调用,所以叫多路复用。但是select这种方式内核需要遍历所有文件描述符,有可以读写的就返回用户程序,但是可能10万个文件描述符就只有两个可读写,浪费了大量的资源。所以就出现了epoll。
此模型用到select和poll函数,两个函数会使进程阻塞,select先阻塞,有活动套接字才返回,但是和阻塞IO不同的是,这两个函数可以同时阻塞多个IO操作,而且可以同时对多个读操作,多个写操作的IO进行检测,直到有数据可读或可写(就是监听多个socket)。select被调用后,进程会被阻塞,内核监听所有select负责的socket,当有任何一个socket的数据准备好了,select就会返回套接字可读,我们就可以调用recvfrom处理数据。
正因为阻塞IO只能阻塞一个IO操作,而IO复用模型能够阻塞多个IO操作,也就是一次性可以把多个文件描述符放在一起进行一次系统调用,即一次处理多个连接,也即在准备数据阶段,一次阻塞多个IO操作,所以才叫做多路复用。

原文和更多理解见:https://segmentfault.com/a/1190000003063859
select:
select 函数监视文件描述符,调用后 select 函数会阻塞,直到有描述符就绪,或者超时,函数返回,当 select 函数返回后,就可以遍历描述符,找到就绪的描述符。
select 的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,可以通过修改宏定义甚至重新编译内核的方式提升这一限制。但是这样也会造成效率的降低(因为在有返回时需要遍历描述符,需要遍历的越多,效率越低)。
poll:
没有最大限制(但是数量过大后性能也是会下降)。和 select 函数一样,poll 返回后,需要轮询来获取就绪的描述符。
select 和 poll 都需要在返回后,通过遍历文件描述符来获取已经就绪的 socket。事实上,同时连接的大量客户端在同一时刻可能只有很少的就绪状态,因此随着监视的描述符数量的增长,其效率也会线性下降。
epoll:
epoll 是 Linux 内核为处理大批量文件描述符而作了改进的 poll,是 Linux 下多路复用 IO 接口 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统 CPU 利用率。原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核 IO 事件异步唤醒而加入 Ready 队列的描述符集合就行了。
Linux的常用命令
1. 如何动态查看服务器日志文件?
命令 tail -f 文件可以对某个文件进行动态监控,例如 tomcat 的日志文件, 会随着程序的运行,日志会变化,可以使用 tail -f catalina-2016-11-11.log 监控文件的变化
2. 如何打包压缩文件?
打包压缩:
Linux 中的打包文件一般是以.tar 结尾的,压缩的命令一般是以.gz 结尾的。而一般情况下打包和压缩是一起进行的,打包并压缩后的文件的后缀名一般.tar.gz。
命令:tar -zcvf 打包压缩后的文件名 要打包压缩的文件 其中:
-
z:调用 gzip 压缩命令进行压缩
-
c:打包文件
-
v:显示运行过程
-
f:指定文件名
比如:假如 test 目录下有三个文件分别是:aaa.txt bbb.txt ccc.txt,如果我们要打包 test 目录并指定压缩后的压缩包名称为 test.tar.gz 可以使用命令:tar -zcvf test.tar.gz aaa.txt bbb.txt ccc.txt 或:tar -zcvf test.tar.gz /test/
解压缩:
命令:tar [-xvf] 压缩文件,其中:x:代表解压
示例:
-
将 /test 下的 test.tar.gz 解压到当前目录下可以使用命令:tar -xvf test.tar.gz
-
将 /test 下的 test.tar.gz 解压到根目录 /usr 下:tar -xvf test.tar.gz -C /usr(- C 代表指定解压的位置)
3. 修改 /test 下的 aaa.txt 的权限为属主有全部权限,属主所在的组有读写权限, 其他用户只有读的权限?
操作系统中每个文件都拥有特定的权限、所属用户和所属组。权限是操作系统用来限制资源访问的机制,在 Linux 中权限一般分为读 (readable)、写 (writable) 和执行 (excutable),分为三组。分别对应文件的属主(该文件的所有者,owner),属组 (所有者的同组用户,group) 和其他用户 (other),通过这样的机制来限制哪些用户、哪些组可以对特定的文件进行什么样的操作。通过 ls -l 命令我们可以 查看某个目录下的文件或目录的权限

1).文件类型
-
d:代表目录
-
-:代表文件
-
l:代表软链接(可以认为是 window 中的快捷方式)
2).权限类别
-
r:代表权限是可读,r 也可以用数字 4 表示
-
w:代表权限是可写,w 也可以用数字 2 表示
-
x:代表权限是可执行,x 也可以用数字 1 表示
示例:修改 /test 下的 aaa.txt 的权限为属主有全部权限,属主所在的组有读写权限, 其他用户只有读的权限:chmod u=rwx,g=rw,o=r aaa.txt或者chmod 764 aaa.txt
4. 查找text.txt文件中的abc的位置?
grep: 在指定文件中查找符合要求的字符串
grep 要搜索的字符串 要搜索的文件 --color(代表高亮显示)

------------恢复内容结束------------
浙公网安备 33010602011771号