前言:
程序是指储存在外部存储(如硬盘)的一个可执行文件, 而进程是指处于执行期间的程序, 进程包括 代码段(text section) 和 数据段(data section), 除了代码段和数据段外, 进程一般还包含打开的文件, 要处理的信号和CPU上下文等等.下面让我们开始对Linux进程有个初步的了解吧。
目录
一、进程的概念:
1.程序与进程区别:
2.并发:
3.单道程序设计:
4.多道程序设计:
5.CPU和MMU:
6.进程控制块PCB:
7.进程状态:
二、环境变量:
1.PATH:
2.SHELL:
3.TERM:
4.LANG.
5.HOME
三、进程控制
1.fork(重点):
(1) fork函数原理:
(2)创建子进程
2.getpid和getppid:
四、进程共享
一、进程的概念:
1.程序与进程区别:
程序:死的。只占用磁盘空间。 ——剧本。
进程;活的。运行起来的程序。占用内存、cpu等系统资源。 ——戏。
2.并发:
并发,在操作系统中,一个时间段中有多个进程都处于已启动运行到运行完毕之间的状态。但,任一个时刻点上仍只有一个进程在运行。
例如,当下,我们使用计算机时可以边听音乐边聊天边上网。若笼统的将他们均看做一个进程的话,为什么可以同时运行呢,因为并发。
3.单道程序设计:
所有进程一个一个排对执行。若A阻塞,B只能等待,即使CPu处于空闲状态。而在人机交互时阻塞的出现时必然的。所有这种模型在系统资源利用上及其不合理,在计算机发展历史上存在不久,大部分便被淘汰了。
4.多道程序设计:
在计算机内存中同时存放几道相互独立的程序,它们在管理程序控制之下,相互穿插的运行。多道程序设计必须有硬件基础作为保证。
时钟中断即为多道程序设计模型的理论基础。并发时,任意进程在执行期间都不希望放弃cpu。因此系统需要一种强制让进程让出 cpu资源的手段。时钟中断有硬件基础作为保障,对进程而言不可抗拒。操作系统中的中断处理函数,来负责调度程序执行。
在多道程序设计模型中,多个进程轮流使用CPU(分时复用CPu资源)。而当下常见CPu为纳秒级,1秒可以执行大约10亿条指令。由于人眼的反应速度是毫秒级,所以看似同时在运行。
1s = 1000ms, 1ms = 100Ous,1us = 1000hs 1000000000
实质上,并发是宏观并行,微观串行! ----推动了计算机蓬勃发展,将人类引入了多媒体时代。
*5.CPU和MMU:
虚拟内存与物理内存映射关系
6.进程控制块PCB:
我们知道,每个进程在内核中都有一个进程控制块(PCB)来维护进程相关的信息,Linux内核的进程控制块是task_struct结构体。
/usr/src/linux-headers-3.16.0-30/include/linux/sched.h.文件中可以查看struct task_struct结构体定义。其内部成员有很多,我们重点掌握以下部分即可:·
本质:结构体
进程id,系统中每个进程有唯一的id,在C语言类型中用pid_t类型表示,其实就是一个非负整数
文件描述符表
进程状态: 初始态、就绪态、运行态、挂起态、终止态。
进程工作目录位置
*umask掩码 (不是最重要的)
信号相关信息资源。
用户id和组id
7.进程状态:
进程基本的状态有5种。分别为初始态,就绪态,运行态,挂起态与终止态。其中初始态为进程准备阶段,常与就绪态结合来看。
二、环境变量:
1.PATH:
可执行文件的搜索路径。ls命令也是一个程序,执行它不需要提供完整的路径名/bin/ls,然而通常我们执行当前目录下的程序a.out却需要提供完整的路径名.l/a.out,这是因为PATH环境变量的值里面包含了ls命令所在的目录/bin,却不包含a.out所在的目录。PATH环境变量的值可以包含多个目录,用:号隔开。在shell 中用echo命令可以查看这个环境变量的值:
$echo $PATH
2.SHELL:
当前Shell,它的值通常是: /bin/bash
3.TERM:
当前终端类型,在图形界面终端下的值通常是xterm。终端类型决定了一些程序的输出显示方式,比如图形界面终端可以显示汉字,而字符终端一般不行。
4.LANG.
语言和 locale,决定了字符编码以及时间、货币等信息的显示格式。
5.HOME
当前用户主目录的路径,很多程序需要在主目录下保存配置文件,使得每个用户在运行该程序时都有自己的一套配置。
三、进程控制
*1.fork(重点):
pid_t fork(void)
创建子进程。父子进程各自返回。父进程返回子进程pid。 子进程返回 0.
getpid();getppid();
循环创建N个子进程模型。 每个子进程标识自己的身份。
(1) fork函数原理:
(2)创建子进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(int argc,char *argv[])
{printf("before fork -1-\n");printf("before fork -2-\n");printf("before fork -3-\n");printf("before fork -4-\n");pid_t pid = fork();if(pid == -1){perror("fork error !\n");exit(1);}else if(pid == 0){printf("child is created\n");}else if(pid > 0){printf("parent process : my child is %d\n",pid);}printf("end of file\n");return 0;
}
循环创建n个子进程
一次fork函数调用可以创建一个子进程。那么创建N个子进程应该怎样实现呢?简单想
for(i= 0; i< n; i++)fork()}即可。但这样创建的是N个子进程吗?
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(int argc,char *argv[])
{int i;pid_t pid;for(i = 0;i < 5;i++){if(fork() == 0)break;}if(i == 5)printf("I'm parent \n");elseprintf("I'm %dth child\n",i+1);return 0;
}
CPU抢夺现象:
如果出现3这种情况,说明子进程没有抢过bash进程,没有争过bash的CPU
避免父进程超越子进程的办法:
增加一个sleep进行延时打印父进程
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(int argc,char *argv[])
{int i;pid_t pid;for(i = 0;i < 5;i++){if(fork() == 0)break;}if(i == 5){sleep(2);printf("I'm parent \n");}else{sheep(i);printf("I'm %dth child\n",i+1);}return 0;
}
2.getpid和getppid:
getpid():获取当前进程ID
pid_t getpid(void);
getppid 函数:获取当前进程的父进程 ID
pid_t getppid(void);
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>int main(int argc,char *argv[])
{printf("before fork -1-\n");printf("before fork -2-\n");printf("before fork -3-\n");printf("before fork -4-\n");pid_t pid = fork();if(pid == -1){perror("fork error !\n");exit(1);}else if(pid == 0){printf("child is created,pid = %d,parent-pid:%d\n",getpid(),getppid());}else if(pid > 0){printf("parent process : my child is %d,my pid :%d,my parent_pid:%d\n",pid,getpid(),getppid());}printf("end of file\n");return 0;
}
四、进程共享
父子进程之间在 fork后。有哪些相同,那些相异之处呢?
刚fork 之后:
父子相同处:全局变量、.data、.text、栈、堆、环境变量、用户ID、宿主目录、进程工作目录、信号处理方式...
父子不同处:
1.进程ID 2.fork返回值 3.父进程ID
4.进程运行时间 5.闹钟(定时器) 6.未决信号集
l
似乎,子进程复制了父进程 0-3G 用户空间内容,以及父进程的 PCB,但 pid 不同,真的每fork一个子进程都要将父进程的 0-3G 地址空间完全拷贝一份,然后在映射至物理内存吗?
当然不是! 父子进程间遵循读时共享写时复制的原则。这样设计,无论子进程执行父进程的逻辑还是执行自己的逻辑都能节省内存开销。
重点注意! 躲避父子进程共享全局变量的知识误区
【重点】:父子进程共享:
1.文件描述符(打开文件的结构体)
2. mmap建立的映射(进程间通信详解)
特别的,fork之后父进程先执行还是子进程先执行不确定。取决于内核所使用的调度算
法。
五、父子进程gdb调试
gdb调试:
设置父进程调试路径:set follow-fork-mode parent (默认)
设置子进程调试路径:set follow-fork-mode child
遇到gdb运行时No symbol table is loaded. Use the “file“ command.的解决方法
- 首先,GDB已经报错file找不到了,那可以运行
file test
检测一下
(gdb) file test
Reading symbols from test...(no debugging symbols found)...done.
2.发现找不到可执行文件,所以问题应该在于编译时候没有加上ggdb3
,重新编译一下
ty@ubuntu:~$ gcc -ggdb3 -o main main.c
3.编译完成再次执行就没有问题了,问题解决