Linux的进程控制
研究如何建立,撤销,阻塞,或唤醒一个进程
一 创建进程
系统启动时处于内核状态
初始化最后,启动名为init的内核线程,然后保留在idle状态(空闲状态)
系统中没有进程可运行时,调度管理器运行idle进程。
idle进程详解
idle进程是空闲进程,是唯一不动态分配task_struct的进程,是task数组的0号元素,记作init_task
pid为0
注意,idle进程就是0号进程,始终位于linux系统的run_queue中,也就是系统可运行队列。在前面讲到了
使用指针*next_run和*pre_run连接形成双向循环队列RUN_QUEUE
这个双向循环队列的基准就是这个0号进程idle
run_queue的头部元素就是init_task之后的task_struct所代表的进程,
尾部元素是init_task之前的进程。
也就是说,每次建立新的进程,都会被插入到init_task之前。
init内核线程详解
init内核线程pid为1
负责完成一些初始化的任务
使用/etc/inittab作为脚本创建系统中的新的进程,这些新的进程又创建各自的新进程。
系统关机前还需要完成结束所有线程,是系统中所有进程的祖先。(而不是0号进程)
fork的使用
如pid=fork(),通过系统调用创建一个新的进程
返回值的意义:
0,只会在子进程中出现,表示当前的进程是子进程
>0,在父进程中,返回值是子进程的pid
-1,创建失败
内核在fork()时完成以下的操作
ubuntu中编写代码
CTRL+alt+T
ls显示已有文件
gedit xx.c 进入xx.c的编辑界面
gcc xx.c 编译并运行xx.c
./a.out 显示输出
touch xx.c 创建一个新文件
多个fork的代码
#include <unistd.h> //ppid指当前进程的父进程pid #include <stdio.h> //pid指当前进程的pid, int main(void) //fpid指fork返回给当前进程的值 { int i=0; printf("i son/pa ppid pid fpid/n"); for(i=0;i<2;i++){ pid_t fpid=fork(); if(fpid==0) printf("%d child %4d %4d %4d/n",i,getppid(),getpid(),fpid); else printf("%d parent %4d %4d %4d/n",i,getppid(),getpid(),fpid); } return 0; }
在我的机器上的运行结果为:
i son/fa ppid pid fpid
0 fa 2185 2214 2215
1 fa 2185 2214 2216
1 child 718 2216 0
0 child 718 2215 0
1 fa 718 2215 2217
1 child 718 2217 0
emmmmmm,可能出了点问题
在父进程代码块中加入sleep(1)试一试
0 child 2434 2435 0 //第一个进程的子进程,父进程2434,自己为2435
1 child 2435 2436 0 //2435创建的2436
0 fa 2185 2434 2435 //第一个进程,祖先进程2185,自己pid为2434 ,子进程2435
1 fa 2434 2435 2436 //2435创建2436
1 child 2434 2437 0 //2434第二轮创建的2437
1 fa 2185 2434 2437 //还是第一个进程,第二轮中作为父进程创建2437
最终实际上只创建了4个进程。
二 执行进程
为了使子进程和父进程完成不同的任务。
使用exec系统调用来装载新的进程映像,放弃从父进程中拷贝过来的内容。
只有execve是真正的系统调用,其他都是在此基础上经过包装的库函数。
execve对当前进程进行替换,为一个指定的程序,参数包括文件名,参数列表及环境变量。
#include <stdio.h> #include <stdlib.h> #include <string.h> #include<unistd.h> char command[256]; void main() { int rtn,errorno; /*子进程的返回数值*/ while(1) /* 从终端读取要执行的命令 */ { printf( ">" ); fgets(command, 256, stdin); command[strlen(command)-1] = 0; if (fork() == 0) /* 子进程执行此命令 */ { execl(“/bin/bash”,”bash”,”-c”,command); /*bash的-c选项表示命令从后续字符串中读取*/ /* 如exec返回,表明没有正常执行*/ perror(command); exit(errorno); } else /*父进程等待子进程结束 */ { wait(&rtn); printf("child process return %d\n", rtn); } } }
这份代码暂时没有看懂
三 等待进程
父进程可以使用系统调用wait()来等待它的一个子进程结束。
参数指定了父进程等待的子进程。
wait()
函数原型为: pid_t wait(*status);
进程一旦调用wait,就立即阻塞自己。然后通过wait函数,不断搜寻当前进程是否有结束退出
的子进程。如果当前进程有任意一个子进程结束并退出,那么wait就搜集该子进程的信息,参数保
存到参数status中,如果没有,那么当前进程阻塞直到有一个出现为止。
waitpid
其功能是等待指定的子进程结束
函数原型为pid_t waitpid(pid_t pid ,int *status,int options)
#include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> int main( void ) { pid_t childpid; int status; childpid = fork(); if (childpid == -1 ) { perror("fork()"); exit(EXIT_FAILURE); } else if (childpid ==0) { puts("In child process"); sleep(3); //让子进程睡眠3秒 printf("\t child pid = %d\n", getpid()); printf("\t child ppid = %d\n", getppid()); exit(EXIT_SUCCESS); } else { waitpid(childpid, &status, 0); puts("In parent process"); printf("\t parent pid = %d\n", getpid()); printf("\t parent ppid = %d\n", getppid()); printf("\t child process exited with status %d \n", status); } exit(EXIT_SUCCESS); }
运行结果,
in child process
child pid=1817
child ppid=1816
in parent process
fa pid=1816
fa ppid=1730
child process exit with status 0
可见尽管子进程wait了,由于父进程的阻塞,还是让子进程先完成。
四 终止进程
使用void exit(int status);
结束本进程,停止之后的所有操作,清除包括PCB在内的各种数据结构。
五 linux的进程调度
Linux的进程调度基于优先级
linux进程分为普通进程和实时进程,后者优先级更高。
普通进程一律采用基于动态优先级的轮转法。SCHED-OTHER
(感觉像是时间多的先运行,运行到时间少了优先级不如别人了再让出来,,不是的
运行到死,然后再RUN_queue挑选优先级最大的)
实时进程采用 时间片轮转 和 先进先出 策略。SCHED-RR SCHED-FIFO
进程类型用policy域表示,通过系统调用sys_sched_setscheduler()更改,确定linux进程的类型。
可以参考前面的task_struct中有关进程调度信息的数据结构。
进程的优先权
由priority和rt_priority确定。
普通进程由counter决定,实时进程取决于rt_priority。
priority域是调度管理器分配给进程的优先级,可以renice系统调用改变。
rt_priority同样可以系统调用改变,且优先级比priority高。
counter是进程运行允许的时间,开始被赋予priority(优先级)的值,,是的,这也符合运行到死的规则。
(猜测rt_priority是一个相对优先级,,也被赋予counter作为运行时间)
调度管理器的功能
六 Linux内核的调度算法
七 进程的虚拟内存
数据结构
包含指向进程页表的指针以及指向vm_area_struct链表的指针
vm_area_struct链表详解
可见这是个链表。
Linux中,进程虚拟内存空间管理的基本单位是虚拟存储区域。每个区域都用一
个vm_area_struct结构来描述。
可见描述了开始与结束区域。
还有存取权限,以及一组内存操作函数。
为了加快存取,Linux把vm_area_struct组合成AVL树。
当进程请求分配虚拟内存的时候,linux不直接分配物理内存,而是创建一个
vm_area_struct,来描述此虚拟内存区域。
八 进程访问的文件
fs_struct进程所在文件系统信息
files_struct进程打开的文件的信息
两个字段
最多包含256个file文件
每个file结构用来描述一个被当前进程所使用的文件。
每打开一个新文件,file一个就指向这个
标准文件描述符