到目前为止,我们所知道的计算机,如笔记本、服务器、台式机等。。都遵守冯诺依曼体系。这些都是由一个个硬件组成。
输入单元:磁盘、网卡、键盘, 鼠标,扫描仪, 写板等
输出单元:磁盘、网卡、显示器,打印机等
中央处理器(CPU):包含运算器和控制器等
我们需要强调几点:
存储器就是指内存。
不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)。
外设要输入或输出数据,也只能写入内存或从内存中读取。
总之就是所有外设都只能与内存打交道,不能直接与CPU打交道。
对冯诺依曼的理解,不能停留在概念上,要深入到对软件数据流理解上,请解释,从你登录上qq开始和某位朋友聊天开始,数据的流动过程。 从你打开窗口,开始给他发消息,到他收到消息之后的数据流动过程。如果是在qq上发送文件呢?
我输入的消息通过输入设备——>存储器——>CUP(处理消息:将消息封装成报文)——>存储器
——>网卡(输出设备)——>网络(省略细节)——>我的朋友的网卡(输入设备)——存储器——>CUP(处理消息:将报文解包)——>存储器——>显示器(输出设备)
任何计算机系统都包含一个基本的程序集合,称为操作系统(OS)。笼统的理解,操作系统包括:
内核(进程管理、内存管理、文件管理、驱动管理)
其他程序(函数库、shell程序等)
与硬件交互,管理所有的软硬件资源
为用户程序(应用程序)提供一个良好的执行环境
管理的例子
描述被管理的对象
组织被管理的对象
先描述,再组织
计算机管理硬件:
描述起来:用struct结构体
组织起来:用链表或其他的数据结构
系统调用:
在开发角度,操作系统对外会表现一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口。
库函数:
系统调用在使用上,功能比较基础,对用户要求相对比较高,所以,有心的开发者可以对部分系统调用进行适当的封装,从而形成库,有了库,就很有利于更上层用户或者开发者进行二次开发。
课本概念:程序的一个执行实体,正在执行的程序等
(管理该进程的结构体+程序的数据和代码)
内核观点:担当分配系统资源的(CPU时间、内存)的实体
进程信息被放在一个叫做程序控制块的
数据结构
中,可以理解为进程属性的集合。
课本上称为PCB(process control block)
,Linux操作系统下的PCB是task_struct。
在Linux中描述进程的结构体叫做task_struct。
task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。
标示符:描述本进程的唯一标示符,用来区别其他进程
状态:任务状态、退出代码、退出信号等
优先级:相对于其他进程的优先级
程序计数器:程序中即将被执行的下一条指令的地址
内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享内存块的指针
上下文数据:
进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]
I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表
记账信息:可能包括处理器的时间总和,使用的时钟数总和,时间记账,记帐号等
其他信息
Linux进程信息可以通过查看叫/proc
文件系统。
Linux提供一种叫/proc文件系统的机制,允许用户模式访问进程内核数据结构的内容。/proc文件系统将许多内核数据结构的内容输出为一个用户程序可以读的文件文本的层次结构。
其实对于每一个进程会用进程的pid号生成一个文件信息,将进程对应的信息都存储在这个文件信息中。
//test.c
#include
#include
int main(){
while(1){
sleep(1);
}
return 0;
}
查看进程状态(stat)/进程系统调用(syscall):
Linux进程信息也可以通过ps和top这些用户级工具来获取
ps aux |grep 进程名|grep -v grep (过滤掉用文本中有grep的信息)
ps aux | head -1 && ps aux |grep 进程名 #打印进程头
ps aux | grep 进程号
每个进程都有一个唯一的正数(非零)进程ID(PID)。getpid 函数返回调用进程的PID。getppid函数返回它的父进程的PID(创建调用进程的进程)。
#include
#include
pid_t getpid(void);
pid_t getppid(void);
//getpid和getppid 函数返回一个类型为pid_t的整数值,在Linux系统上它在types.h中被定义为int.
父进程通过调用fork函数创建一个新的运行的子进程
#include
#include
pid_t fork(void);
//返回:子进程返回0,父进程返回子进程的PID,如果出错,则返回-1.
#include
#include
int main(){
pid_t pid;
int x=1;
pid=fork();
if(pid==0){
//child
printf("child: x=%d\n",++x);
exit(0);
}
printf("parent: x=%d\n",--x);
exit(0);
}
■ 调用一次,返回两次
fork函数被父进程
调用一次,返回两次
—— 一次是返回到父进程,一次是返回到新创建的子进程
■ 并发执行
父子进程
并发运行
的独立进程,内核能够以任意方式交替执行它们的逻辑控制流中的指令。谁先运行由OS中的调度器决定。
■ 相同但是独立的地址空间(父子进程代码共享,数据各自开辟空间,私有一份(采用写时拷贝))
父子进程中每个进程有相同的用户栈、相同的本地变量、相同的堆、相同的全局变量值、以及相同的代码,由于父子进程是独立的进程,所以父子间
数据是各自私有的,但是其中进程代码是共享的。
采用写时拷贝。
■ 共享文件
子进程能够继承父进程所有的打开文件。
创建进程,是系统中多了一个进程,多一个进程系统就要多一组管理该
进程的数据结构+该程序对应的代码和数据
//伪代码
pid_t fork(void){
//创建子进程的逻辑
//给子进程创建task_struct
struct task_struct *ts=malloc(sizeof(struct task_struct));
//拷贝数据
ts.XXX=father.XXX;
....
ts.status=running;//子进程的状态
ts.link=task_queue;//挂载到调度队列
...
//进程创建完毕
return ts->id;//父进程要执行,子进程也要执行,函数的返回值是数据,父子进程各自私有一份,所以会有两个返回值
}
进程的数据=代码+数据。
父进程创建子进程的时候,代码是共享的,数据是各自私有一份(写时拷贝)。
代码是逻辑,只读不可写。数据是各自私有一份可读可写。
当fork完毕之后,父子进程谁先运行是不确定的,是由OS的调度器决定的。
结论:进程是具有独立性的。
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};
//在Linux中进程有时也叫任务
R运行状态(running):
并不意味着进程一定在运行中,它表明进程要么在运行中要么在运行队列里。
S睡眠状态(sleeping):
意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D磁盘休眠状态(disk sleep):
有时候叫不可中断睡眠(uninterruptible sleep),在这个状态的进程通常会等待IO结束。
T停止状态(stopped):
可以通过发送SIGSTOP信号给进程来停止(T)进程。这个被暂停的进程可以通过发送SIGCONT信号让进程继续运行。
X死亡状态(dead):
这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
ps aux | pa axj 命令
1、僵死进程是一个比较特殊的状态。当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵死(尸)进程。
2、僵死进程会以终止状态保持在进程表中,并且会一直等待父进程读取退出状态代码。
3、只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,子进程就会进入Z状态。
#include
#include
#include
#include
int main(){
pid_t pid;
pid=fork();
if(pid>0){
while(1){
printf("I am parent = %d!!\n",getpid());
sleep(2);
}
}else if(pid==0){
int count=5;
while(count--){
printf("I am child = %d!!\n",getpid());
sleep(1);
}
exit(0);
}else{
printf("fork Error!\n");
exit(0);
}
return 0;
}
监测shell:
while : ; do ps axj | head -1 && ps axj |grep zombie|grep -v grep ;sleep 1 ;echo "################" ;done
1、进程的退出状态必须被维持下去,因为他要告诉关心它的进程(父进程),你交给我的任务,我办的怎么样了。可父进程如果一直不读取,
那子进程就一直处于Z状态。
2、维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,换句话说,Z状态一直不退出, PCB一直都要维护。
3、一个父进程创建了很多子进程,就是不回收,那么就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间!如果不会回收那么就要造成内存泄漏。
1、父进程如果提前退出,那么子进程后退出,进入Z状态,那该怎么处理?
2、 父进程先退出,子进程就称为孤儿进程。
3、如果一个父进程终止了,内核会安排init进程成为它的养父,init进程是PID为1的进程,是在系统启动时由内核创建的,它不会终止,是所有进程的祖先。如果父进程没有回收它的僵死子进程就终止了,那么内核会安排init进程去回收它们。
#include
#include
#include
#include
int main(){
pid_t pid;
pid=fork();
if(pid>0){
int count=5;
while(count--){
printf("I am parent = %d!!\n",getpid());
sleep(1);
}
}else if(pid==0){
while(1){
printf("I am child = %d!!\n",getpid());
sleep(2);
}
exit(0);
}else{
printf("fork Error!\n");
exit(0);
}
return 0;
}
监测shell:
while : ; do ps axj | head -1 && ps axj |grep guer|grep -v grep ;sleep 1 ;echo "################" ;done
检测结果: