1、进程的概念:
简单来说就是进行中的程序,操作系统通过PCB来控制一个进程的运行,这个PCB也叫进程描述符,描述了一个运行中的程序,在操作系统角度,进程就是PCB。
一个PCB相当于一个内存指针,存储着指定程序的位置信息
CPU的分时机制:切换调度进程,每个进程只运行很短的时间,这个时间被称为时间片
进程标识符:PID
进程优先级:交互式进程 > 批处理进程 让操作系统运行的更加合理
进程查看:
ps -ef aux
/proc 保存系统正在运行进程信息
getpid() 获取调用进程pid
进程创建:
创建一个pcb
fork() 通过【复制】调用进程(父)创建一个新的进程(子)
父子进程代码共享,【“数据独有”】
分辨父子进程:通过返回值判断
父进程:返回子进程PID
子进程:返回0
失败:返回-1
创建子进程的意义:1、分摊父进程压力,CPU资源进程足够的情况下, 父子进程同时处理,效率更高、
2、希望子进程完成其他任务
2、进程的O(1)调度算法.
该算法可以在恒定的时间内为每个进程重新分配好时间片,而且在恒定的时间内可以选取一个最高优先级的进程,重要的是这两个过程都与系统中可运行的进程数无关。
2.1、O(1)中时间片的计算
O(1)算法采用过期进程数组和活跃进程数组解决以往调度算法所带来的O(n)复杂度问题。过期数组中的进程都已经用完了时间片,而活跃数组的进程还拥有时间片。当一个进程用完自己的时间片后,它就被移动到过期进程数组中,同时这个过期进程在被移动之前就已经计算好了新的时间片。可以看到O(1)调度算法是采用分散计算时间片的方法,并不像以往算法中集中为所有可运行进程重新计算时间片。
当活跃进程数组中没有任何进程时,说明此时所有可运行的进程都用完了自己的时间片。那么此时只需要交换一下两个数组即可将过期进程切换为活跃进程,进而继续被调度程序所调度。两个数组之间的切换其实就是指针之间的交换,因此花费的时间是恒定的。下面的代码说明了两个数组之间的交换:
1
struct prop_array *array = rq->active;
2
if (array->nr_active != 0) {
3
rq->active = rq->expired;
4
rq->expired = array;
5
}
通过分散计算时间片、交换过期和活跃两个进程集合的方法可以使得O(1)算法在恒定的时间内为每个进程重新计算好时间片。
2.2 O(1)中进程的选择
进程调度的本质就是在当前可运行的进程集合中选择一个最佳的进程,这个最佳则是以进程的动态优先级为选取标准的。不管是过期进程集合还是活跃进程集合,都将每个优先级的进程组成一个链表,因此每个集合就有140个不同优先级的进程链表。同时,两个集合中还采用优先级位图来标记每个优先级链表中是否存在进程。
调度程序在选取最高优先级的进程时,首先利用优先级位图从高到低找到第一个被设置的位,该位对应着一条进程链表,这个链表中的进程是当前系统所有可运行进程中优先级最高的。在该优先级链表中选取头一个进程,它拥有最高的优先级,即为调度程序马上要执行的进程。上述进程的选取过程可用下述代码描述:
01
struct task_struct *prev, *next;
02
struct list_head *queue;
03
struct prio_array *array;
04
int idx;
05
06
prev = current;
07
array = rq->active;
08
idx = sehed_find_first_bit(array->bitmap);
09
queue = array->queue + idx;
10
next = list_entry(queue->next, struct task_struct, run_list);
11
if (prev != next)
12
context_switch();
sehed_find_first_bit()用于在位图中快速查找第一个被设置的位。如果prev和next不是一个进程,那么此时进程切换就开始执行。
通过上述的内容可以发现,在恒定的时间重新分配时间片和选择一个最佳进程是Q(1)算法的核心。
3、task_struct结构体中的各个字段的含义.
Linux中task_struct用来控制管理进程,结构如下:
struct task_struct
{
//说明了该进程是否可以执行,还是可中断等信息
volatile long state;
//Flage 是进程号,在调用fork()时给出
unsigned long flags;
//进程上是否有待处理的信号
int sigpending;
//进程地址空间,区分内核进程与普通进程在内存存放的位置不同
mm_segment_t addr_limit; //0-0xBFFFFFFF for user-thead
//0-0xFFFFFFFF for kernel-thread
//调度标志,表示该进程是否需要重新调度,若非0,则当从内核态返回到用户态,会发生调度
volatile long need_resched;
//锁深度
int lock_depth;
//进程的基本时间片
long nice;
//进程的调度策略,有三种,实时进程:SCHED_FIFO,SCHED_RR, 分时进程:SCHED_OTHER
unsigned long policy;
//进程内存管理信息
struct mm_struct *mm;
int processor;
//若进程不在任何CPU上运行, cpus_runnable 的值是0,否则是1 这个值在运行队列被锁时更新
unsigned long cpus_runnable, cpus_allowed;
//指向运行队列的指针
struct list_head run_list;
//进程的睡眠时间
unsigned long sleep_time;
//用于将系统中所有的进程连成一个双向循环链表, 其根是init_task
struct task_struct *next_task, *prev_task;
struct mm_struct *active_mm;
struct list_head local_pages; //指向本地页面
unsigned int allocation_order, nr_local_pages;
struct linux_binfmt *binfmt; //进程所运行的可执行文件的格式
int exit_code, exit_signal;
int pdeath_signal; //父进程终止是向子进程发送的信号
unsigned long personality;
//Linux可以运行由其他UNIX操作系统生成的符合iBCS2标准的程序
int did_exec:1;
pid_t pid; //进程标识符,用来代表一个进程
pid_t pgrp; //进程组标识,表示进程所属的进程组
pid_t tty_old_pgrp; //进程控制终端所在的组标识
pid_t session; //进程的会话标识
pid_t tgid;
int leader; //表示进程是否为会话主管
struct task_struct *p_opptr,*p_pptr,*p_cptr,*p_ysptr,*p_osptr;
struct list_head thread_group; //线程链表
struct task_struct *pidhash_next; //用于将进程链入HASH表
struct task_struct **pidhash_pprev;
wait_queue_head_t wait_chldexit; //供wait4()使用
struct completion *vfork_done; //供vfork() 使用
unsigned long rt_priority; //实时优先级,用它计算实时进程调度时的weight值
//it_real_value,it_real_incr用于REAL定时器,单位为jiffies, 系统根据it_real_value
//设置定时器的第一个终止时间. 在定时器到期时,向进程发送SIGALRM信号,同时根据
//it_real_incr重置终止时间,it_prof_value,it_prof_incr用于Profile定时器,单位为jiffies。
//当进程运行时,不管在何种状态下,每个tick都使it_prof_value值减一,当减到0时,向进程发送
//信号SIGPROF,并根据it_prof_incr重置时间.
//it_virt_value,it_virt_value用于Virtual定时器,单位为jiffies。当进程运行时,不管在何种
//状态下,每个tick都使it_virt_value值减一当减到0时,向进程发送信号SIGVTALRM,根据
//it_virt_incr重置初值。
unsigned long it_real_value, it_prof_value, it_virt_value;
unsigned long it_real_incr, it_prof_incr, it_virt_value;
struct timer_list real_timer; //指向实时定时器的指针
struct tms times; //记录进程消耗的时间
unsigned long start_time; //进程创建的时间
//记录进程在每个CPU上所消耗的用户态时间和核心态时间
long per_cpu_utime[NR_CPUS], per_cpu_stime[NR_CPUS];
//内存缺页和交换信息:
//min_flt, maj_flt累计进程的次缺页数(Copy on Write页和匿名页)和主缺页数(从映射文件或交换
//设备读入的页面数); nswap记录进程累计换出的页面数,即写到交换设备上的页面数。
//cmin_flt, cmaj_flt, cnswap记录本进程为祖先的所有子孙进程的累计次缺页数,主缺页数和换出页面数。
//在父进程回收终止的子进程时,父进程会将子进程的这些信息累计到自己结构的这些域中
unsigned long min_flt, maj_flt, nswap, cmin_flt, cmaj_flt, cnswap;
int swappable:1; //表示进程的虚拟地址空间是否允许换出
//进程认证信息
//uid,gid为运行该进程的用户的用户标识符和组标识符,通常是进程创建者的uid,gid
//euid,egid为有效uid,gid
//fsuid,fsgid为文件系统uid,gid,这两个ID号通常与有效uid,gid相等,在检查对于文件
//系统的访问权限时使用他们。
//suid,sgid为备份uid,gid
uid_t uid,euid,suid,fsuid;
gid_t gid,egid,sgid,fsgid;
int ngroups; //记录进程在多少个用户组中
gid_t groups[NGROUPS]; //记录进程所在的组
//进程的权能,分别是有效位集合,继承位集合,允许位集合
kernel_cap_t cap_effective, cap_inheritable, cap_permitted;
int keep_capabilities:1;
struct user_struct *user;
struct rlimit rlim[RLIM_NLIMITS]; //与进程相关的资源限制信息
unsigned short used_math; //是否使用FPU
char comm[16]; //进程正在运行的可执行文件名
//文件系统信息
int link_count, total_link_count;
//NULL if no tty 进程所在的控制终端,如果不需要控制终端,则该指针为空
struct tty_struct *tty;
unsigned int locks;
//进程间通信信息
struct sem_undo *semundo; //进程在信号灯上的所有undo操作
struct sem_queue *semsleeping; //当进程因为信号灯操作而挂起时,他在该队列中记录等待的操作
//进程的CPU状态,切换时,要保存到停止进程的task_struct中
struct thread_struct thread;
//文件系统信息
struct fs_struct *fs;
//打开文件信息
struct files_struct *files;
//信号处理函数
spinlock_t sigmask_lock;
struct signal_struct *sig; //信号处理函数
sigset_t blocked; //进程当前要阻塞的信号,每个信号对应一位
struct sigpending pending; //进程上是否有待处理的信号
unsigned long sas_ss_sp;
size_t sas_ss_size;
int (*notifier)(void *priv);
void *notifier_data;
sigset_t *notifier_mask;
u32 parent_exec_id;
u32 self_exec_id;
spinlock_t alloc_lock;
void *journal_info;
};
4、进程的几种状态及僵尸进程和孤儿进程的代码实现
进程状态: 就绪、运行、阻塞
Linux进程状态:
常见: 运行状态(R):包含就绪和运行
可中断睡眠态(S):
不可中断睡眠态(D):
停止态(T):nothing to do
僵尸态(Z):
不常见:死亡态(X):进程退出的瞬间
追踪态(t):
僵尸进程:处于僵死状态的进程(进程退出后资源没有完全释放,即未完全退出)
产生原因:子进程先于父进程退出,将自己的退出原因保存在pcb中,操作系统监测到子进程退出,因为父进程可能关注子进程退户原因,所以操作系统不能随意释放所有资源,但是父进程可能没有及时关注到子进程退出,导致子进程退出了但资源未完全释放,使子进程处于僵死状态,称为僵尸进程。
危害:导致资源泄露,一个用户可以创建的进程是有限的(僵尸进程达到一定数量时,可能导致新进程创建失败)
处理办法:强杀父进程 (不可取)
避免办法:进程等待
孤儿进程:父进程先于子进程退出,子进程成为孤儿进程,运行在后台,父进程成为1号进程
守护进程/精灵进程:特殊的孤儿进程 服务类程序都是孤儿进程
kill proc_pid 杀死进程
kill -9 强杀
// 僵尸进程的实现
// 僵尸进程概念:子进程退出,父进程还在运行,但父进程没有读取到子进程的状态信息,此时子进程进入僵尸状态
//
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(){
int pid = fork();
if(pid < 0) {
perror("fork perror");
return 1;
}
else if(pid > 0){
printf("farther[%d] is sleeping ...\n",getpid());
sleep(10);
}
else{
printf("child[%d] is begin Zombie ...\n",getpid());
sleep(5);
exit(0);
}
return 0;
}
// 孤儿进程的实现
// 概念:父进程先于子进程退出,此时子进程就会变为孤儿进程,孤儿进程会被init进程领养
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
int pid = fork();
if(pid < 0) {
perror("fork error");
return 1;
}
else if(pid == 0) {
printf("child[%d] \n",getpid());
sleep(10);
}
else {
printf("farther[%d] \n",getpid());
sleep(2);
exit(0);
}
return 0;
}
5、 虚拟内存 :
什么是虚拟内存?简单地说,是指程序员或CPU “需要”和直接“看到”的内存,这其实暗示了两点:1、虚拟内存单元不一定有实际的物理内存单元对应,即实际的物理内存单元可能不存在;2、如果虚拟内存单元对应有实际的物理内存单元,那二者的地址一般不是相等的。通过操作系统的某种内存管理和映射技术可建立虚拟内存与实际的物理内存的对应关系,使得程序员或CPU访问的虚拟内存地址会转换为另外一个物理内存地址。
使用虚拟内存的意义:
保持进程的独立性
充分利用内存
按需内存访问控制
内存管理的三种方式:段页式、页式、段式内存管理
6、setenv, export等环境变量相关的函数和命令.
6.1、setenv函数
作用:改变或增加环境变量
头文件:#include<stdlib.h>
函数定义:int setenv(const char *name,const char * value,int overwrite);
函数说明:setenv()用来改变或增加环境变量的内容。参数name为环境变量名称字符串。参数 value则为变量内容,参数overwrite用来决定是否要改变已存在的环境变量。如果没有此环境变量则无论overwrite为何值均添加此环境变量。若环境变量存在,当overwrite不为0时,原内容会被改为参数value所指的变量内容;当overwrite为0时,则参数value会被忽略。返回值 执行成功则返回0,有错误发生时返回-1。
相关函数:getenv,putenv,unsetenv
说明:通过此函数并不能添加或修改 shell 进程的环境变量,或者说通过setenv函数设置的环境变量只在本进程,而且是本次执行中有效。如果在某一次运行程序时执行了setenv函数,进程终止后再次运行该程序,上次的设置是无效的,上次设置的环境变量是不能读到的。
相关代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(){
char *env;
const char* envname = "vim";
// 获取变量vim的环境变量
env = getenv(envname);
printf("The first env %s = %s \n ",envname,env);
// 设置环境变量
setenv(envname, "I will get it !",1);
printf("The second env %s = %s \n",envname, env);
env = getenv(envname);
printf("The third env %s = %s ",envname, env);
// 删除环境变量
int ret = unsetenv(envname);
printf("ret %d \n",ret);
env = getenv(envname);
printf("The third env %s = %s \n",envname, env);
return 0;
}
运行结果:
6.2、export函数
作用:用于将shell变量输出为环境变量,或者将shell函数输出为环境变量
语法:export(选项)(参数)
常用选项:
-f:代表[变量名称]中为函数名称;
-n:删除指定的变量。变量实际上并未删除,只是不会输出到后续指令的执行环境中;
-p:列出所有的shell赋予程序的环境变量。
参数:指定要输出或者删除的环境变量