目录

1.冯诺依曼体系结构(硬件结构)

冯诺依曼结构计算机的基本工作原理是什么?

举例说明数据的流动过程

2.操作系统简述

总结:先描述,再组织

3.进程

4.通过代码创建子进程:fork

进程终止

进程等待

waitpid:

进程具有独立性,为什么父进程能拿到子进程退出码数据?

5.Linux操作系统进程状态

环境变量

批量化注释+去注释

6.进程替换

exec系列函数

自制shell


1.冯诺依曼体系结构(硬件结构

存储器指的是:物理内存

冯诺依曼体系结构中数据输入设备的有:键盘、摄像头、话筒、磁盘(读取)、网卡等

冯诺依曼体系结构中数据输出设备的有:显示器、磁盘、音响、网卡等

中央处理器(CPU)的运算器进行运算的两种情况:算术运算,逻辑运算;

控制器:CPU可以响应外部事件(拷贝数据到内存)


硬件层面上的数据流向

CPU读取数据(数据+代码),都是要从内存中读取。站在数据的角度,我们认为CPU不和外设直接交互。外设(输入或输出设备)要输入或者输出数据,也只能写入内存或者从内存中读取。

冯诺依曼体系结构最典型的特征是所有的外设(输入单元和输出单元)产生的数据,必须将数据写入存储器。存储器本身没有计算能力,CPU通过某种方式来访问存储器,将数据读取进CPU内部,CPU进行运算器运算,控制器控制,最终将计算完的结果写回到内存当中。最后将最终的结果显示到显示器当中。

总结:所有设备都只能直接和内存打交道


冯诺依曼结构计算机的基本工作原理是什么?

计算机就是为了完成指定的数据处理,而通过指令按指定流程完成指定功能,指令的合集就是一段程序。计算机就是按照指定的指令执行流程完成对指定数据的处理

简写为:存储程序、程序控制


举例说明数据的流动过程

由于程序已经在内存中运行,当我们发送消息时,通过键盘输入(输入设备),输入的数据放到内存,cpu经过计算回写到存储器,存储器将数据定期刷新出去,此时的输出设备叫做网卡。

接收消息的电脑接受数据,此时的输入设备是网卡,网卡收到的数据放到内存里,然后经过cpu运算,把处理完的结果再写回存储器里,存储器定期将数据刷新到输出设备,此时的输出设备是显示器。

总结:冯诺依曼结构规定了计算机硬件体系结构走向,如果数据流向不清楚,想象一下硬件体系结构是什么样子,程序的数据流走向也就清楚了(硬件决定了走向,例如胳膊只能向上弯)


2.操作系统简述

操作系统是“搞管理”的软件,与硬件交互,管理所有的软硬件资源,为用户程序(应用程序)提供一个良好的执行环境,操作系统包括:

1.内核,也就是操作系统(进程管理,内存管理,文件管理,驱动管理)

2.其他程序(例如函数库,shell程序,接口,库等等)

总结:先描述,再组织

如何管理软硬件?管理者(操作系统)拿到被管理者(硬件)的核心数据,来进行管理决策才最重要的,先对被管理对象进行描述,在根据描述类型,定义对象,可以把对象组织成数组或者其他数据结构。也就是对数据的管理工作,变成了对数组或者其他数据结构的增删改查

管理:是对被管理对象数据的管理

1. 描述起来,用struct结构体 2. 组织起来,用链表或其他高效的数据结构


系统调用和库函数

操作系统会展开部分接口,供开发使用,由操作系统提供的接口,叫做系统调用。

对系统调用进行适度封装,形成库,更加方便开发者或者用户使用


3.进程

管理大量进程:先描述,再组织(描述:Linux内核中操作系统会为每个进程申请一个结构体:PCB)

该结构体保存了对应进程所有的属性,方便操作系统管理进程。组织:对进程的管理,变成了对进程PCB结构体管理(增删查改),等于对数据结构管理(相当于学生管理系统,学生在学校不代表就是该校学生,配合管理系统信息才能确定)

总结:进程=对应的代码和数据+进程对应PCB结构体

PCB(进程控制块 process control block)

Linux系统中PCB名称:struct task_struct{};

查看进程状态:ps ajx | head -1 && ps axj | grep 'mytest'

写一个死循环,查看mytest状态

PID:进程ID,代表当前进程的ID值

grep --color =auto mytest:最后一个是grep进程,查找mytest,grep中包含该关键字,通常一起显示

ls /proc:把所有内存级进程数据以文件形式显示出来(目录文件是动态的)

死循环PID程序1006运行运行

获取当前进程pid的系统调用函数:getpid

其中pid_t是操作系统提供的属性类型,实际是一个无符号整数

知道pid,还可以使用kill命令终止指令:kill -9 pid

获取当前进程的父进程的系统调用函数:getppid

查看当前进程父进程可以发现:ppid是bash,就是shell命令行,外壳程序

在命令行中运行命令或者程序时,父进程永远都是bash(执行命令时,所有的命令都是以子进程的形式去运行,shell以创建子进程的方式运行一段命令或程序)


4.通过代码创建子进程:fork

fork有两个返回值:

1.失败的时候返回-1

2.成功时  a.给父进程返回子进程pid b.给子进程返回0

为什么fork有两个返回值?

1.因为fork是函数,return语句被其内部实现的创建子进程代码两个执行流各自执行,返回值就有两个

可以看到,printf只是一条语句,却被打印了两次

fork之后,变为两个进程,一个是父进程(自己),一个是子进程

父进程返回子进程pid;子进程返回0

fork用法

由于fork之后代码是父子共享,通常要用 if 进行分流(id值不同,父子进程执行不同的代码)

进程终止

进程终止时,OS做了什么?

释放进程申请的相关内核数据结构和对应的代码+数据

main函数返回值有什么意义?return 0的意义是什么

main函数的返回值,并不是总是0,可以是其他值,main函数的0指的是进程的退出码,用来表示进程是否正确返回(代码跑完结果是否正确),退出码为0代表运行结果正确,非0表示运行结果错误

获取最近一次进程执行完的退出码:echo $?

main函数返回值意义是返回给上一级进程,用来评判该进程执行结果用。如果关心返回值,可以对返回值做判断决定结果是否正确,可以方便程序运行不正确时定位错误原因

退出码转换成字符串错误信息:strerror

对应错误码2

程序崩溃

退出码无意义,进程只要跑起来,就是OS范畴,本质是OS杀掉了进程,本质是发送信号的方式

exit和_exit

exit直接退出进程并返回退出码,和return区别是return在main函数才是退出,函数return返回值。

_exit是系统调用函数,两者的区别是exit调用了_exit,并且执行了用户定义的清理函数和刷新缓冲区,关闭流(stdin,stdout,stderror),从侧面可以看出缓冲区是由C标准库维护的

进程等待

父进程创建了子进程,如果不管子进程变成僵尸进程会导致内存泄漏,kill -9也没办法杀掉一个已经死去的进程。父进程通过进程等待的方式,既可以回收子进程,也可以获取子进程退出时的信息

进程等待的方法

wait:

阻塞式等待一个进程直到状态发生变化(如果在fork子进程中继续fork子进程,由于wait回收了第一次fork的子进程,第二次fork子进程并不会僵尸而是被1号进程领养)

#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);

status参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

返回值: 成功返回被等待进程pid,失败返回-1。

waitpid:

pid_ t waitpid(pid_t pid, int *status, int options);

返回值pid_t:

当正常返回的时候waitpid返回收集到的子进程的进程ID;

如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;

如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

pid:

pid: Pid=-1,等待任一个子进程。与wait等效。

Pid>0.等待其进程ID与pid相等的子进程。

options:

默认为0,表示阻塞式等待(父进程在此期间只等待子进程退出,只有子进程先退出父进程才调waitpid)

WNOHANG,表示非阻塞式等待: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

阻塞等待:在内核中阻塞,等待被唤醒

非阻塞等待:父进程通过调用waitpid来进行等待,如果子进程没有退出,waitpid系统调用马上返回,继续执行waitpid之后的代码(非阻塞调用的轮询检测方案)

非阻塞调用的轮询检测方案

status输出型参数(传入变量取地址获得信息):

参数NULL表示不关心

status并不是按照整数整体使用,而是按照比特位方式将32个比特位进行划分

低16位的次低八位,表示子进程的退出码,用来表示进程结果是否正确

两个宏:

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出) WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

最低7个比特位,表示进程收到的信号,如果进程崩溃,是OS发送了信号导致进程挂掉,那我们可以通过查看OS是否有发送信号来判断进程是否崩溃/正常跑完

程序异常,可能不光是本身代码问题,有可能收到信号结束

进程具有独立性,为什么父进程能拿到子进程退出码数据?

子进程退出变成僵尸进程后,还会保留对应进程的PCB信息task_struct,里面保留了任何进程退出时的退出信息,一直保留直到wait\waitpid读取。

进程退出时退出码和退出信号会写入task_struct中等待别人读取

注意事项

如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,获得子进程退出信息。

如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。

如果不存在该子进程,则立即出错返回。

5.Linux操作系统进程状态

OS理论状态:

新建:初步具备进程相关数据结构,没有入队列中时叫做新建状态,字面意思

运行:进程的task_struct结构体在CPU运行队列中排队等待调度,就叫做运行态。

阻塞:等待非CPU资源就绪时(例如网卡硬盘传输,或者scanf时等待键盘输入),就叫做阻塞状态

挂起:和进程访问某种资源关系不大,当内存不足时,操作系统将长时间不执行的进程的代码和数据,通过适当的置换到磁盘中,此时进程的状态就叫做挂起

OS实际进程状态

一个进程可以有几个状态(在 Linux内核里,进程有时候也叫做任务)

R 运行状态(running):

并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里

S 睡眠状态(sleeping):

意味着进程在等待事件(某种资源,类似阻塞状态)完成(这里的睡眠有时候也叫做可中断睡眠 (interruptible sleep))

while1循环时,我们看到死循环进程状态是R+;while1循环内部printf时,我们看到死循环进程状态是S+;和R状态不同的原因是:

在cpu看来运行printf时间只需要纳秒级别,其实有运行状态,只不过显示器在冯诺依曼体系结构中处于外设,进程向显示器打印时,printf的PCB放入CPU时间比运行队列速度快,导致看到的大部分是S状态;

当压力过大内存严重不足时,操作系统可能会通过一定手段干掉一些进程(S睡眠状态),起到节省空间的作用

可中断睡眠

改变sleep状态,可以发送信号来更改状态

状态后带+

带+:意味任务处于前台进程(前台进程一启动,执行其他命令失效,可以被ctrl+c终止,占用bash对话框)

不带+:需要后台运行时,在执行时加 &即可,随后显示后台进程pid,不可被ctrl+c终止,bash可以使用

D 磁盘休眠状态(Disk sleep)

有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程不可被被动唤醒,通常会等待IO的结束。

当压力过大内存严重不足时,操作系统无法干掉D状态

T暂停/调试状态(stopped/tracing stop):

可以通过发送 SIGSTOP 19号信号给进程来停止(T)进程。

被暂停的进程可以通过发送 SIGCONT 18号信号让进程继续运行。kill -18 pid

打断点停下调试==进程暂停

X死亡状态(dead):

这个状态瞬时性很强,通常只是一个返回状态,你不会在任务列表里看到这个状态。

Z(zombie)-僵尸进程

是什么:一个进程已经退出,但是还不允许被OS释放其所属资源,处于一个被检测的状态(进程状态检测一般是父进程或者操作系统检测)

当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程,僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。 父进程还在运行,但父进程没有读取子进程状态,子进程进入Z状态

为什么要维持该状态:为了让父进程和OS回收(最终Z-->X)

僵尸进程危害:不被任何进程读取,也不会和外设交互,但是其创建的PCB进程基本信息一直存在,造成内存资源浪费(内存泄漏)

循环监测mytest进程状态:

while :; do ps axj | head -1 && ps axj | grep mytest | grep -v grep; sleep 1; echo "#############################################"; done

#include <stdio.h>2 #include <stdlib.h>3 #include <unistd.h>                                        4 int main()5 {6    pid_t id = fork();7    if(id < 0)8    {9       perror("fork");10        return 1;11    }12     else if(id > 0)13     { //parent14         while(1)15         {16 17          printf("I am parent,pid%d,ppid%d\n", getpid(),getp    pid());18          sleep(3);19         }20     }21    else22    {23      //child24      while(1)25      {26        printf("I am child,pid :%d, ppid %d\n", getpid(),get    ppid());27        sleep(3);28        exit(-1);   29      }30    }31     return 0;32    }  

孤儿进程

父进程提前退出,子进程就称之为“孤儿进程”,孤儿进程此时被1号init进程(系统本身)领养,init进程进行回收

如果父进程退出,此时子进程从前台进程转化为后台进程,使用kill -9即可(照常输入)

杀掉子进程后进入Z状态,直接被操作系统回收


环境变量

main函数最多有三个参数(int argc,char* argv[],char* env[]),前两个是命令行参数(启动程序时,传入的参数选项,意义是同样一个参数,通过传入不同的选项,使用同一个程序的不同子功能),最后一个是环境变量参数(每一个进程启动时,启动该进程的进程传递给main函数的环境变量信息,都可以以该参数传导)

env保存环境变量字符串,以指针数组的形式维护起来

获得环境变量方式

环境变量的参数一般是由父进程继承导入,默认所有的环境变量都会被子进程继承

批量化注释+去注释

注释:ctrl+v进入视图模式,shift+g批量化选中所有内容,大写I+双//+esc即可

去注释:ctrl+v,使用hjkl键上下左右选中,然后d即可

6.进程替换

fork创建子进程后如果想执行不同的代码(不是父子执行不同的代码段),就需要用到程序替换。

注意事项:

1.该进程的用户空间代码和数据被新程序替换。

2.进程替换没有创建新进程(内核数据结构+代码+数据)

3.通过特定的接口:exec系列函数,物理内存加载磁盘上全新的代码和数据,重新建立页表映射,放到调用进程的地址空间

exec系列函数

exec函数程序替换后,调用成功后,加载新的程序。此操作会将当前进程的所有代码和数据进行替换(包括已执行和未执行)

调用出错返回值-1

没有成功的返回值,因为成功后exec本身也被替换。

l(list) :传递形式像

v(vector) :传递形式像

p(path) : 自动搜索环境变量,从环境变量中寻找PATH

e(env) : 表示自己维护环境变量

使用

const char* path表示文件名路径

系统命令其实也是文件路径下的可执行程序

...表示可变参数列表,可以传入多个不定参数。命令行如何执行,参数列表对应填入命令(ls -a -l)

argv[0]代表ls,argv[1]表示-a,是一个个的字符串。最后必须以NULL结尾

execve

execve才是系统调用,以上的其他exec系列函数由系统封装,以满足不同场景

execl(list,像链表结点一样传参)

execlp(path不用带路径,直接从环境变量中寻找)

第一个ls代表要执行谁,目的为了找到程序,第二个ls表示如何执行,传递选项

execv(vector,先把需要执行的命令构建成数组)

char *const agrv[]是指针数组,数组内容是char*指向一个个字符串

execle

环境变量是K/V模型

获取环境变量getenv

如何执行自己编写的程序

绝对路径和相对路径都可以实现

mycmd.c

execlp可以执行其他语言程序,功能是加载器的底层接口,可以执行任何程序

自制shell

shell运行原理:父进程解析命令+等待子进程执行,子进程执行命令

思路:

1.首先一定是死循环(常驻进程)

2.打印一行命令行提示符

3.获取用户输入的指令和选项(ls -a),需要用到fgets,从特定的文件流中获取字符串数据(自动添加\0)放入到缓冲区中,大小是size

4.命令行解析,把指令拆分成子串:strtok (把空格设置为\0,每个子串起始位置用指针标识)

代码实现

次模式删除需要ctrl+del

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/types.h>#define SIZE 32
#define NUM 1024
char _buffer[NUM];//保存完整的命令行
char* g_argv[SIZE];//保存打散之后的命令行字符串,把子串的起始地址保存起来int main(){while(1){printf("[root@testshell]# ");fflush(stdout);memset(_buffer,'\0',sizeof(_buffer));if(fgets(_buffer,sizeof _buffer,stdin) == NULL ){continue;}_buffer[strlen(_buffer)-1] = '\0';
//读取字符串时把\n也读取到了,此时字符串内容是xxxx\n\0,找到\n位置-1下标置为\0;g_argv[0] = strtok(_buffer," ");//strtok要求必须字符串,得是双引号//  g_argv[1] = "--color=auto";int i = 1;if(strcmp(g_argv[0],"ls") == 0){g_argv[i++] = "--color=auto"; //增加颜色}while( g_argv[i++] = strtok(NULL," "));
//strtok的用法,第一次调用要传入要传入原始字符串,第二次如果继续解析相同字符串,传入NULL//  int j = 0;//测试是否成功// for(j = 0;g_argv[j];++j)//g_argv[i]最后肯定以NULL结尾//     printf("g_argv[%s]\n",g_argv[j]);pid_t id = fork();if(id == 0){execvp(g_argv[0],g_argv);//带p后就不用带路径exit(1);}int status = 0;pid_t ret = waitpid(id,&status,0);if(ret > 0){printf("exit code:%d",WEXITSTATUS(status));}}return 0;}           

退回命令

自制的shell无法完成返回上级目录操作,本质是子进程返回而父进程没有返回,只会影响当前子进程

chdir修改工作目录

Linux 【进程】相关推荐

  1. Linux 进程及进程之间的通信机制——管道

    参考: LInux C编程从初学到精通 电子工业出版社 Linux 进程 Linux 进程简介 Linux是一个多用户多任务的操作系统,多用户是指多个用户可以在同一时间使用同一台计算机系统:多用户是指 ...

  2. Linux进程与线程的区别 详细总结(面试经验总结)

    首先,简要了解一下进程和线程.对于操作系统而言,进程是核心之核心,整个现代操作系统的根本,就是以进程为单位在执行任务.系统的管理架构也是基于进程层面的.在按下电源键之后,计算机就开始了复杂的启动过程, ...

  3. Linux进程编程基础介绍

    Linux系统是一个多进程的系统,它的进程之间具有并行性.互不干扰等特点.也就是说,每个进程都是一个独立的运行单位,拥有各自的权利和责任.其中,各个进程都运行在独立的虚拟地址空间,因此,即使一个进程发 ...

  4. Linux进程ID号--Linux进程的管理与调度(三)

    进程ID概述 进程ID类型 要想了解内核如何来组织和管理进程ID,先要知道进程ID的类型: 内核中进程ID的类型用pid_type来描述,它被定义在include/linux/pid.h中 enum ...

  5. Linux 进程必知必会

    点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 只是简单的描述了一下 Linux 基本概念,通过几个例子来 ...

  6. Linux进程管理 (7)实时调度

    关键词:RT.preempt_count.RT patch. 除了CFS调度器之外,还包括重要的实时调度器,有两种RR和FIFO调度策略.本章只是一个简单的介绍. 更详细的介绍参考<Linux进 ...

  7. Linux进程及进程管理命令

    Linux进程基础 1进程概念 1.1程序与进程 1.2进程的状态 1.3线程 1.4进程生命周期 1.5虚拟内存 2进程管理工具 2.1pstree 2.2ps 2.3pgrep 2.4kill.p ...

  8. Linux进程查看与管理

    Linux进程查看与管理(2) htop: 交互式命令: u: 过滤 s:跟踪选用的进程所发起系统调用 l:显示选定进程所打开的文件 t:显示进程的层次数 a:设定进程的cpu亲缘性:(将选定的进程绑 ...

  9. Linux进程的Uninterruptible sleep(D)状态

    Linux系统进程状态: PROCESS STATE CODES Here are the different values that the s, stat and state output spe ...

  10. linux进程及作业管理实验,Linux 进程及作业管理(示例代码)

    Linux 进程及作业管理 概述: 一.进程的相关概念: 1.相关定义:内核的功用:   进程管理.文件系统.网络功能.内存管理.驱动程序.安全功能等: 通俗的来说进程是运行起来的程序.唯一标识进程的 ...

最新文章

  1. 上传图片被防火墙拦截_Web安全:文件上传漏洞
  2. Python @函数装饰器用法
  3. 北极约200万个甲烷点被发现,人类的反思该提上日程了,因为只有一个地球
  4. Python编程快速上手-字典
  5. Remarkable启动遇到Spellchecking not enabled(没有解决)
  6. 信息: 开始协议处理句柄[http-nio-8080]_你必须要知道的HTTP协议原理
  7. AttributeError: module ‘tensorflow‘ has no attribute ‘InteractiveSession‘或 ‘placeholder‘的解决
  8. android 支付宝手势密码锁,Android仿支付宝手势密码解锁功能
  9. 高德地图API(使用浏览器ip实现精确定位)
  10. RealSense 图形识别之路 1.0
  11. Python爬取堆糖网图片的代码
  12. 杭电复试2018年笔试第三题--西瓜地(最小生成树)
  13. 清华大学生计算机系学生就业方向,这5所大学计算机专业全球顶尖,就业前景广阔,毕业就能拿高薪!...
  14. Habor 入门指南
  15. calloc()函数
  16. 记录 vue-admin-template 中 npm install 错误解决办法
  17. AlignedReID: Surpassing Human-Level Performance in Person Re-Identification
  18. Grapher--寂寞无名的神器
  19. Ubuntu下pip安装CPU版PaddlePaddle
  20. 基于qiankun的微前端最佳实践 -(同时加载多个微应用)

热门文章

  1. 数据结构(54) B树、B+树
  2. Maven配置阿里云中央仓库
  3. 学习笔记-----浅谈汇编指令CMP运行机制
  4. J-flash工具使用教程
  5. Lua 5.0 参考手册
  6. Java String字符串长度不足指定位数补0
  7. Python+Opencv实现图像匹配——模板匹配
  8. 【MySQL-设置定时任务】
  9. sqlserver日期推算(年,季度,月,星期推算)
  10. Mendix与JEECG对比