1. 了解冯.诺依曼体系结构
2. 了解操作系统,并且知道操作系统如何进行管理?
3. 进程相关概念,进程和程序的区别,父进程和子进程
3. 进程的状态及其如何修改进程的状态
冯.诺依曼体系结构
我们常见的计算机:笔记本,台式计算机。而不常见的如服务器,大部分都遵循冯诺依曼体系
组成:
7. 输入单元:包括键盘,鼠标,扫描仪,写板等
8. 输出单元:显示器,打印机等
另外,输入设备和输出设备都属于外设,基本上所有的外设都和IO有关
9. 中央处理器(CPU):含有运算器和控制器等
10. 存储器:这里指的是内存
所有的设备都只能直接和内存打交道:
1. 外设(输入设备或者输出设备) 只能直接和 存储器(内存) 打交道
1. CPU 只能直接和 存储器(内存)打交道
更好的了解一下冯诺依曼体系结构:
两个人通过计算机聊天的例子:
发送者:
通过输入设备(键盘)发送消息,然后通过(内存)对数据进行封装,发送到输出设备(网卡)
接受者:
通过输入设备(网卡)接受消息,然后通过(内存)对数据进行解包,发送到输出设备(显示)
如果是发送文件的话,接受者对应的输入设备是硬盘,这样关掉计算机,也会收到消息,内容还在。
操作系统(Operator System)
操作系统:任何计算机系统都包含一个基本的程序集合。
其作用就是:在整个计算机软硬件架构中,它是用来搞“管理”的软件
操作系统包括:
1. 内核:
内存管理 进程管理 文件管理 驱动管理
2. 其他程序
比如:函数库 shell程序
设计操作系统的目的:
1. 与硬件交互,管理所有的软硬件资源 (对系统软硬件部分)
2. 为用户程序(应用程序) 提供一个良好的执行环境 (对用户)
如何进行管理?
1. 管理是通过数据进行管理的
2. 管理者和被管理者不进行直接沟通
进程
基本概念:
进程:(每个进程都有自己的状态和独立的地址空间。)
进程由操作系统管理
从用户角度看: 进程是程序的一次动态执行过程。
分时系统: 时间片轮转。
从操作系统: 进程是操作系统分配资源的基本单位(最小单位)
描述进程—PCB(进程控制块)
- 如何描述一个进程?
1.描述一个进程需要知道进程信息,而进程信息放在一个叫做进程控制块的数据
构中,这个进程控制块就是PCB。
2.PCB也可以理解为一个进程属性的集合。
- Linux操作系统下具体的PCB是:task_struct
task_struct
1. task_struct: 是Linux中描述进程的结构体
2. tsk_struct: 是Linux内核中的一个数据结构,它被装载在RAM(内存)中且包含了进程的信息
如何管理一个进程?
1. 将进程描述起来
而具体描述的方法是结构体task_struct(我们把描述进程的结构体叫做PCB)
2. 将结构体组织起来
用链表 (所有运行在系统里的进程都以task_struct链表的形式存在内核里。)
所以如果管理一个进程,只需要管理好它的PCB
查看进程
第一种方法:通过 ls /proc 文件夹查看
前面的第一列便是进程id
[root@localhost day01_6.12]# ls /proc
1 15 1653 1833 2098 2210 2339 2385 2405 2611 2667 30 365 716 cmdline interrupts kpageflags net sys
10 151 1678 1846 21 2216 2344 2386 2407 2612 2669 3070 37 8 cpuinfo iomem loadavg pagetypeinfo sysrq-trigger
11 152 1687 1883 2100 2258 2347 2387 2420 2615 2670 31 38 892 crypto ioports locks partitions sysvipc
12 1521 1688 19 2102 2269 2352 2388 2422 2617 2685 3126 4 893 devices ipmi mdstat sched_debug timer_list
1250 153 17 2 2108 2275 2357 2389 2470 2619 2693 3130 40 9 diskstats irq meminfo schedstat timer_stats
1271 1546 1720 20 2109 2290 2362 2392 25 2627 27 3131 41 941 dma kallsyms misc scsi tty
1299 16 1730 2018 2110 23 2370 2393 26 2631 271 3150 5 acpi driver kcore modules self uptime
13 1617 1735 2066 2121 2301 2371 2399 2601 2632 274 3172 6 asound execdomains keys mounts slabinfo version
14 1628 1755 2077 2124 2309 2373 24 2604 2633 28 32 662 buddyinfo fb key-users mpt softirqs vmallocinfo
144 1634 1771 2091 2140 2310 2382 2401 2607 2659 29 3201 7 bus filesystems kmsg mtd stat vmstat
145 1647 18 2096 22 2330 2383 2402 2609 2661 3 3218 71 cgroups fs kpagecount mtrr swaps zoneinfo
第二种方法:更详细的查看进程信息
[root@localhost day01_6.12]# ps aux
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.1 2900 1440 ? Ss Jun11 0:01 /sbin/init
root 2 0.0 0.0 0 0 ? S Jun11 0:00 [kthreadd]
root 3 0.0 0.0 0 0 ? S Jun11 0:00 [migration/0]
root 4 0.0 0.0 0 0 ? S Jun11 0:00 [ksoftirqd/0]
root 5 0.0 0.0 0 0 ? S Jun11 0:00 [migration/0]
root 6 0.1 0.0 0 0 ? S Jun11 0:26 [watchdog/0]
root 7 0.0 0.0 0 0 ? S Jun11 0:01 [events/0]
进程和程序的区别
程序:完成特定任务的一系列指令集合。
进程和程序的区别:
程序: 数据+代码
进程: 数据+代码+堆栈+PCB
PCB: 进程控制块 (Process.Control.Block)
(每个进程有一个PCB表示)———用链表存储
并发:多个进程在一个cpu下采用进程切换的方式,在一段时间之内让多个进程得以推进。
并行:多个进程在多个CPU下分别.同时进行。
1. 进程是动态的,程序是静态的;
2. 进程的生命周期是短暂的,而程序相对永久;
3. 进程有重要的数据结构PCB;
4. 一个进程只能对应一个程序
而一个程序可以对应多个进程。
父进程和子进程
通过系统调用获取进程标识符:
子进程id: PID 通过getpid()函数获取
父进程id: PPID 通过getppid()函数获取
这两个函数都在 <sys/types.h> <unistd.h>中
获取父子进程id:
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 printf("pid:%d ppid:%d \n%",getpid(),getppid());
8 return 0;
9 }
结果:
[root@localhost day01_6.12]# gcc test.c
[root@localhost day01_6.12]# ./a.out
pid:3311 ppid:3201
[root@localhost day01_6.12]#
[root@localhost day01_6.12]# ./a.out
pid:3312 ppid:3201
[root@localhost day01_6.12]# ./a.out
pid:3313 ppid:3201
[root@localhost day01_6.12]# ./a.out
pid:3314 ppid:3201
[root@localhost day01_6.12]# ./a.out
pid:3315 ppid:3201
[root@localhost day01_6.12]# ./a.out
pid:3316 ppid:3201
结果发现,每一次运行之后,父进程是不会变的,子进程会变。
而如果让代码死循环起来,这个就是一个进程,始终不能退出,所以效果就是:
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 while(1){
8 printf("pid:%d ppid:%d \n%",getpid(),getppid());
9 sleep(1);
10 }
11
12 return 0;
13 }
[root@localhost day01_6.12]# gcc test.c
[root@localhost day01_6.12]# ./a.out
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
pid:3351 ppid:3201
^C
[root@localhost day01_6.12]#
通过系统调用创建进程—fork()
1. 运行 man fork 认识fork
[root@localhost day01_6.12]# man fork
2. 发现fork() 有两个返回值
3. 父子进程代码共享,数据各自开辟空间,私有一份采用(写时拷贝)
一个简单的代码来了解fork():
1 #include <stdio.h>
2 #include <sys/types.h>
3 #include <unistd.h>
4
5 int main()
6 {
7 fork();
8 printf(" hello world!,pid: %d,ppid: %d\n ",getpid(),getppid());
9 sleep(1);
10 return 0;
11 }
看似只有一个printf,只会输出一个hello world.而其实调用了fork()函数,它的作用是创建进程,类似于一个叉子的结构,一分为二(简单认为叉子的另外一头只有两个分支)。
结果为:
[root@localhost day01_6.12]# ./test
hello world!,pid: 3498,ppid: 3395
hello world!,pid: 3499,ppid: 3498
[root@localhost day01_6.12]# gcc test.c
[root@localhost day01_6.12]# ./test
hello world!,pid: 3505,ppid: 3395
hello world!,pid: 3506,ppid: 3505
结果表示,先创建父进程,后创建子进程。也证明了:3.父子进程代码共享,数据各自开辟空间,私有一份采用(写时拷贝)——代码共享,数据私有
代码实现父子进程:
在fork()之前先处理父进程,在fork()之后,父子进程的处理顺序不一定,这个和调度有关。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
pid_t id = fork();
if( id == 0 )// 子进程
{
while(1)
{
printf(" child do thing 1..... pid:%d ,ppid : %d \n",getpid(),getppid());
sleep(1);
}
}
else if ( id > 0 )// 父进程
{
while(1)
{
printf(" father do thing 2.... pid : %d ,ppid : %d \n ",getpid(),getppid());
sleep(3);
}
}
else
{
perror("fork");
}
return 0;
}
结果:
[root@localhost day01_6.12]# ./test
father do thing 2.... pid : 3570 ,ppid : 3395
child do thing 1..... pid:3571 ,ppid : 3570
child do thing 1..... pid:3571 ,ppid : 3570
child do thing 1..... pid:3571 ,ppid : 3570
father do thing 2.... pid : 3570 ,ppid : 3395
child do thing 1..... pid:3571 ,ppid : 3570
child do thing 1..... pid:3571 ,ppid : 3570
child do thing 1..... pid:3571 ,ppid : 3570
father do thing 2.... pid : 3570 ,ppid : 3395
child do thing 1..... pid:3571 ,ppid : 3570
child do thing 1..... pid:3571 ,ppid : 3570
child do thing 1..... pid:3571 ,ppid : 3570
father do thing 2.... pid : 3570 ,ppid : 3395
child do thing 1..... pid:3571 ,ppid : 3570
^C
[root@localhost day01_6.12]#
进程状态
僵尸状态:
也有可能出现内存泄漏(父进程对子进程僵尸状态不处理)
所以僵尸状态:1. 肯定会维护 2. 必须处理
这里我们可以看出来 ./a.out & 的作用就是a.out文件放在后台运行
test1.c:
#include <unistd.h>
#include <sys/types.h>
int main()
{
while(1);
return 0;
}
[root@localhost day01_6.12]# gcc test1.c
[root@localhost day01_6.12]# ./a.out
^C
[root@localhost day01_6.12]# ps aux | grep a.out
root 3614 0.0 0.0 5980 764 pts/1 S+ 04:48 0:00 grep a.out
[root@localhost day01_6.12]# ./a.out &
[1] 3615
[root@localhost day01_6.12]# ./a.out
^C
[root@localhost day01_6.12]# ps aux | grep a.out
root 3615 91.2 0.0 1864 276 pts/1 R 04:48 0:17 ./a.out
root 3631 0.0 0.0 5980 744 pts/1 S+ 04:48 0:00 grep a.out
[root@localhost day01_6.12]#
修改进程状态
如果要恢复的话,18 SIGCONT 命令表示继续 即:kill -18 3615