工具 - webdancer's Blog

UNIX v6 的fork系统调用实现分析

 

最近一直在阅读《莱昂氏unix代码分析》,硬件的基础有点差,PDP11结构也是不太清楚,汇编语言也不行。有很多的疑问,现在还是看一看unix v6 中的fork系统调用如何实现的呢?

/*
*/
#include "../param.h"
#include "../user.h"
#include "../proc.h"
#include "../text.h"
#include "../systm.h"
#include "../file.h"
#include "../inode.h"
#include "../buf.h"
/* -------------------------
*/
/*
* Create a new process-- the internal version of
* sys fork.
* It returns 1 in the new process.
* How this happens is rather hard to understand.
* The essential fact is that the new process is created
* in such a way that it appears to have started executing
* in the same call to newproc as the parent;
* but in fact the code that runs is that of swtch.
* The subtle implication of the return value of swtch
* (see above) is that this is the value that newproc’s
* caller in the new process sees.
*/
newproc()//无参数
{
int a1, a2;
struct proc *p, *up;
register struct proc *rpp;
register *rip, n;
p = NULL;
/*
* First, just locate a slot for a process               
* and copy the useful info from this process into it.//为新建的进程寻找存储槽,并复制有用的信息。
* The panic "cannot happen" because fork already
* checked for the existence of a slot.
*/
retry:
mpid++;//mpid,整形值,0-32767之间递增,用作进程号。
if(mpid < 0) {
mpid = 0;
goto retry;
}
for(rpp = &proc[0]; rpp < &proc[NPROC]; rpp++) {//在proc数组中,寻找空元素。
if(rpp->p_stat == NULL && p==NULL)//由p_stat来判断,是否为空。
p = rpp;
if (rpp->p_pid==mpid)
goto retry;
}
if ((rpp = p)==NULL)
panic("no procs");
/*
* make proc entry for new proc
*/
rip = u.u_procp;//当前进程proc项的地址。
up = rip;

rpp->p_stat = SRUN;
rpp->p_flag = SLOAD;
rpp->p_uid = rip->p_uid;
rpp->p_ttyp = rip->p_ttyp;
rpp->p_nice = rip->p_nice;
rpp->p_textp = rip->p_textp;
rpp->p_pid = mpid;
rpp->p_ppid = rip->p_ppid;
rpp->p_time = 0;

/*
* make duplicate entries
* where needed//复制打开文件。
*/
for(rip = &u.u_ofile[0]; rip < &u.u_ofile[NOFILE];)
if((rpp = *rip++) != NULL)
rpp->f_count++;
if((rpp=up->p_textp) != NULL) {
rpp->x_count++;
rpp->x_ccount++;
}
u.u_cdir->i_count++;
/*
* Partially simulate the environment
* of the new process so that when it is actually
* created (by copying) it will look right.
*/
savu(u.u_rsav);//保存环境和栈指针。
rpp = p;
u.u_procp = rpp;
rip = up;
n = rip->p_size;
a1 = rip->p_addr;
rpp->p_size = n;
a2 = malloc(coremap, n);//申请内存。
/*
* If there is not enough core for the
* new process, swap put the current process to
* generate the copy.
*/
if(a2 == NULL) {//申请不到内存。
rip->p_stat = SIDL;
rpp->p_addr = a1;
savu(u.u_ssav);
xswap(rpp, 0, 0);
rpp->p_flag =| SSWAP;
rip->p_stat = SRUN;
} else {
/*
* There is core, so just copy.
*/
rpp->p_addr = a2;
while(n--)
copyseg(a1++, a2++);
}
u.u_procp = rip;
return(0);
}

总结一下过程:

1.首先,应该为进程取得一个惟一的进程号。

2.将当前进程的proc复制到新进程的proc结构。

3.将当前进程的打开文件复制到新进程,同时增加文件引用。

4.进行内存分配,如果内存不足,则放入交换区,否则,分配内存。

UNIX的proc数组,当前LINUX是一个task_struct 结构的双向链表,结构更加复杂。但是,实现的基本思想应该变化不大。

阅读《莱昂氏UNIX源代码分析》

malloc.c源代码:
/*
* Structure of the coremap and swapmap   %两类资源:主存(64B); 盘交换区(512B)。
* arrays. Consists of non-zero count            %coremap   swapmap 维护可用资源的列表。
* and base address of that many                 %资源类别都包含连续的单元(map)。 如此下面
* contiguous units.                                      %malloc函数可以连续的查找。
* (The coremap unit is 64 bytes,
* the swapmap unit is 512 bytes)
* The addresses are increasing and              %可用资源列表结束的标志就是map类型单元为0.
* the list is terminated with the
* first zero count.
*/
struct map
{
char *m_size;  //map结构的大小
char *m_addr; //map结构的地址
};
/* -------------------------
*/
/*
* Allocate size units from the given
* map. Return the base of the allocated
* space.
* Algorithm is first fit.
*/
malloc(mp, size)
struct map *mp;
{
register int a;
register struct map *bp;
for (bp = mp; bp->m_size; bp++) {  //bp->m_size等价于:bp->m_size>0; bp++:列表为连续的。
if (bp->m_size >= size) { //找到要用的区域,由于算法采用的为:first fit.
a = bp->m_addr;   //记录该区域的首单元地址,作为返回的地址。
bp->m_addr =+ size;//更改该区域的首单元地址 
if ((bp->m_size =- size) == 0)//减少长度,判断是否恰好适配,恰好适配时,资源列表资源数减少。
do {
bp++;
(bp-1)->m_addr = bp->m_addr;//后续元素下移。
} while((bp-1)->m_size = bp->m_size);  //判断(bp-1)->m_size是否为0
return(a);//返回该区域的地址
}
}
return(0);//返回0,没找到区域。
}
/*----------------------
*/

/*
* Free the previously allocated space aa
* of size units into the specified map.
* Sort aa into map and combine on
* one or both ends if possible.
*/
mfree(mp, size, aa)  //将资源返回给mp指定的资源图。
struct map *mp;
{
register struct map *bp;
register int t;
register int a;
a = aa;
for (bp = mp; bp->m_addr<=a && bp->m_size!=0; bp++);//为了寻找释放资源在资源图中的位置。
if (bp>mp && (bp-1)->m_addr+(bp-1)->m_size == a) {//寻到在它之前插入新元素的元素。是否合并
(bp-1)->m_size =+ size;
if (a+size == bp->m_addr) {//是否应该与下一元素合并
(bp-1)->m_size =+ bp->m_size;
while (bp->m_size) {//元素数减1,位置前移。
bp++;
(bp-1)->m_addr = bp->m_addr;
(bp-1)->m_size = bp->m_size;
}
}
} else {//不能并于列表中的前一元素。
if (a+size == bp->m_addr && bp->m_size) {//是否与后一元素合并
bp->m_addr =- size;
bp->m_size =+ size;
} else if(size) do {//既不能与前面的&&后面的合并,在列表中增加元素。逐一后移。
t = bp->m_addr;
bp->m_addr = a;
a = t;
t = bp->m_size;
bp->m_size = size;
bp++;
} while (size = t);
}
}
/*-----------------------
*/
/*

算法分析:

在《os 概念》中,内存的分配介绍了三种方法:连续,分页,分段。 UNIX v6 是基于pdp11,上面的内存分析显然是简单的连续分配,对pdp11不太了解,不知道为什么用此类方式。采用动态分配的方式,可能含有外部碎片。使用了first fit 算法。这个算法的明显特点(就目前我所知)是搜索的速度快,可快速找到。

malloc :算法就是在资源可用图中连续找到可用元素,采用first fit.

free :将该内存释放在指定的区域,需要仔细的考虑合并的问题。

疑惑:

对malloc函数的连续查找还是不太明白,资源分配图好像可以不连续呀?blush

Makefile知识

Makefile包含5种内容:显式规则,隐含规则,变量定义,Makefile指示符(指示符指明在 make 程序读取 makefile 文件过程中所要执行的一个动作。),注释。

Makefile文件的名称默认顺序为:GNUmakefile,makefile,Makefile。 (gnu 文档中推荐使用 Makefile,因为大写开头更靠近一些重要文件,如README)。如果不采用以上的默认名称:可以采用"-f filename" 或者"--file=filename" 参数来指定Makefile文件。

1.包含其他的Makefile文件,可以采用include指示符,但是不能以<Tab>开头,可以有空格。

使用情况:

 make 程序在处理指示符 include 时,将暂停对当前使用指示符“include”的 makefile 文件的读取,而转去依此读取由“include”指示符指定的文件列表。直到完成所有这些文件以后再回过头继续读取指示符“include”所在的 makefile文件。

2.有些情况下,存在两个比较类似的 makefile 文件。其中一个(makefile-A)需要使用另外一个(makefile-B)中所定义的变量和规则。但是,两个文件有一些目标文件相同,为避免同意目标文件有两种或以上的规则产生,可采用一下方法。
看一个例子,如果存在一个命名为“Makefile”的makefile 文件, 其中描述目标“foo”的规则和其他的一些规,我们也可以书写一个内容如下命名为“GNUmakefile”的文件。

foo:
             frobnicate > foo
 %: force
             @$(MAKE) -f Makefile $@
 force: ;

注:(1).$(MAKE)是内置变量,可以通过make -p 查看。MAKE = $(MAKE_COMMAND)  MAKE_COMMAND := make。可以知道$(MAKE) :=make 。
    (2).规则之前加@,本命令行不显示。

例如:make foo 命令时,使用frobnicate > foo ,可是make bar 命令时,则采用模式规则,使用@MAKE -f Makefile $@,如果Makefile中,有bar 的规则,则可使用。

3.变量:

在gnu make中,有两种不同的变量定义风格,主要有2点不同:(1)定义方式;(2)展开时机。

(1)递归展开变量:采用方式  VALUENAME = value

(2)直接展开变量:采用方式  VALUENAME := value

尽量使用(2)种方式,第一种问题较多。

变量的追加: 使用+=符号。

注意:1. 如果被追加值的变量之前没有定义,那么,“+=”会自动变成“=”,此变量就被定义为一个递归展开式的变量。如果之前存在这个变量定义,那么“+=”就继承之前定义时的变量风格。
2. 直接展开式变量的追加过程:变量使用“:=”定义,之后“+=”操作将会首先替换展开之前此变量的值,尔后在末尾添加需要追加的值,并使用“:=”重新给此变量赋值。

4.条件执行:

条件语句 用于控制 make 实际执行的 makefile 文件部分,它不能控制规则的 shell 命令执行过程。Makefile 中使用条件控制可以做到处理的灵活性和高效性。

语法规则:

     conditional-directive
     text-if-one-is-true
     else conditional-directive
     text-if-true
     else
     text-if-false
     endif

条件判断有四种:

1.ifeq

ifeq (arg1, arg2)
ifeq 'arg1' 'arg2'
ifeq "arg1" "arg2"
ifeq "arg1" 'arg2'
ifeq 'arg1' "arg2"

2.ifneq.方式同上。

3.ifdef.

ifdef variable-name

4.ifndef.

ifndef variable-name

ifdef 只是测试一个变量是否有值,不会对变量进行替 换 展 开 来 判 断 变 量 的 值 是 否 为 空 。
 

Makefile文件的书写

学习分析源代码,和自己在Linux底下Makefile文件的书写很重要。但是,这是一个逐渐积累的过程,先学一下基本的吧!

Makefile的语法较为复杂,可以参考GNU的官网。http://www.gnu.org/software/make/manual/make.html

1.make命令不仅使用在编译类型的程序文件,还是用于有几个输入文件产生输出文件的情况;比如是文档处理。

2.Makefile语法:

一个Makefile文件包含:依赖关系和规则. 一条依赖关系包含目标文件和它所依赖的源文件.规则:描述了如何由源文件产生目标文件。

目标文件: 源文件1 源文件2 [...]

<tab> 规则                       #(注:此处必须是tab键打头)。

     (1.)依赖关系:

目标文件产生所依赖的源文件,两者用:与空格分开;格式为:

目标文件: 源文件1 源文件2 […]

例如:

all: main.o 1.o 2.o

main.o: main.c a.h

1.o: 1.c a.h

2.o: 2.c b.h

注意: 在gcc 编译器中有 -MM选项,它可以产生依赖关系。所以,把它产生的输出到某个文件就可以了。

     (2)规则:

产生目标文件的语法,可以使用gcc 命令。格式为:

<tab> rules

3.#为注释。从行首到行末。

4.Makefile文件里的宏。

MACRONAME=VALUE

使用时${MACRONAME} 或者 $(MACRONAME)

宏经常用在编译器的选项上。

在make执行shell命令时,每一条命令会打开一个shell,所以加\來连接。

自定义变量:

AR 库文件维护程序的名称,默认值为 ar

AS 汇编程序的名称,默认值为 as

CC C 编译器的名称,默认值为 cc

CPP C 预编译器的名称,默认值为$(CC) –E

CXX C++编译器的名称,默认值为 g++

FC FORTRAN 编译器的名称,默认值为 f77

RM 文件删除程序的名称,默认值为 rm –f

ARFLAGS 库文件维护程序的选项,无默认值

ASFLAGS 汇编程序的选项,无默认值

CFLAGS C 编译器的选项,无默认值

CPPFLAGS C 预编译的选项,无默认值

CXXFLAGS C++编译器的选项,无默认值

FFLAGS FORTRAN 编译器的选项,无默认值

自动变量:

$* : 不包含扩展名的目标文件名称。

$+ : 所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件。

$< : 第一个依赖文件的名称 。

$? : 所有时间戳比目标文件晚的依赖文件,并以空格分开

$@: 目标文件的完整名称

$^ : 所有不重复的依赖文件,以空格分开

$% : 如果目标是归档成员,则该变量表示目标的归档成员名称

5。内建规则:

模式规则是用来定义相同处理规则的多个文件的。它不同于隐式规则,隐式规则仅仅能够用 make 默认的变量来进行操作,而模式规则还能引入用户自定义变量,为多个文件建立相同的规则,从而简化 Makefile 的编写。

模式规则的格式类似于普通规则,这个规则中的相关文件前必须用“%”标明。使用模 式规则修改后的 Makefile 的编写如下:

OBJS = kang.o yul.o

CC = Gcc

CFLAGS = -Wall -O -g

sunq : $(OBJS)

$(CC) $^ -o $@

%.o : %.c

$(CC) $(CFLAGS) -c $< -o $@

6.make 管理函数库:

函数库是一种目标文件的集合,后缀.a。make语法使得管理变得很简单,语法为:lib(file.o),可以把文件加入到函数库中。

7.make使用:

-C dir 读入指定目录下的 Makefile

-f file 读入当前目录下的 file 文件作为 Makefile

-i 忽略所有的命令执行错误

-I dir 指定被包含的 Makefile 所在目录

-n 只打印要执行的命令,但不执行这些命令

-p 显示 make 变量数据库和隐含规则

-s 在执行命令时不显示命令

-w 如果 make 在执行过程中改变目录,则打印当前目录名。



 

gcc使用--编译c++程序

    gcc的c++编译器可以产生可执行的目标代码,而且其作为补充加入到gcc中的。直接使用gcc命令來编译c++程序,会出现错误。例如 程序1 exam.cc:

#include<iostream>
int main(int argc,char * argv[]){
	std::cout<<"hello world"<<std::endl;
	return 0;
}

命令1: gcc exam.cc (错误) 。程序编译错误,“ld returned 1 exit status“,无法编译通过。

命令2: gcc -lstdc++ exam.cc 。程序编译通过。-l:链接库文件。

命令3: g++ exam.cc 。程序编译通过。

g++ is a program that calls GCC and treats .c, .h and .i files as C++ source files instead of C source files unless -x is used, and automatically specifies linking against the C++ library.

(g++ 会自动调用GCC,而且会将.c,.h,.i作为c++源文件而不是c的源文件,除非-x 选项被使用,而且自动链接c++库.)。也就是将语言默认为c++了。

后缀:

头文件:C++文件的头文件较多,可以使用.hh, .hpp, .H, 或者.tcc。

源文件: .C, .cc, .cpp, .CPP, .c++, .cp, or .cxx。

汇编语言文件:.ii。

注意:

库文件:c++的库文件名为stdc++.a,包含几乎所有的c++例程。

其他的命令与前面GCC的简单使用的差不多。所以就不说了。当然,编译程序肯定会遇到各种的问题,一定要注意查资料。

GCC的简单使用

 

这学期要学操作系统的课程设计了,编译器的学习是必需的了。当然,由于平台基于Linux,所以学习GCC是必须的了。

GCC 是“GNU Compiler Collection” 的简称,而不是以前的 “GNU C Compiler “。它支持 c ,c++,object-c,objectc+,Fortran,Java和Ada。 GCC编译器的选项真的非常之多,通常可以分成三种:

1.平台相关的选项。所谓的平台,是指机器(主要是芯片)和OS。下表:

 

Hardware

Operating System

 

PowerPC

Linux

 

Sparc

Linux

 

ARM

Linux

 

Intel x86

Cygwin Linux

 

2.语言相关的选项。

3.一般性选项。

GCC的编译过程:

预处理、编译、汇编、链接;通过一定的gcc选项,可以使编译过程停留在某一阶段。例如:-C 可以阻止链接过程,只是产生目标文件。文件的后缀通常决定怎么编译。例如:

.a

Static object library (archive).

.c

C source code that is to be preprocessed.

.h

C source code header file.

.i

C source code that is not to be preprocessed. This type of file is

produced as an intermediate step in compilation.

.o

An object file in a format appropriate to be supplied to the linker.

This type of file is produced as an intermediate step in compilation.

.s

Assembly language code. This type of file is produced as an

intermediate step in compilation.

 

集中常用的gcc命令:

(1)、单个源文件产生可执行文件。

命令:源文件:a.c

gcc a.c

结果:产生a.out 可执行文件。

(2)、产生目标文件。

命令:gcc -c a.c -o a.o

结果:产生a.o目标文件。

(3)、多个源文件产生可执行文件。

命令: gcc a.c b.c -o a

(4)、预处理。

命令: gcc -E a.c -o a.i

(5)、产生汇编文件。

命理: gcc -S a.c

(6)、创建静态库。

命令:gcc -c a.c b.c

ar -r liba.a a.o b.o

(7)、创建动态库。

命令:gcc -c -fpic a.c b.c

gcc -shared a.o b.o -o a.so

(8)、避免后缀转换。

命令: gcc -xc a.ccc (-x 后面跟具体的语言。)

 


 

os实验--进程的调度

Linux系统中,进程的调度策略其实是一个比较困难的问题,实验指导书上的只是很不全面,而且模棱两可。现在只能大致了解一下: 通用Linux系统支持实时和非实时两种进程,实时进程相对于普通进程具有绝对的优先级。 通用Linux系统支持实时和非实时两种进程,实时进程相对于普通进程具有绝对的优先级。对应地,实时进程采用SCHED_FIFO或者SCHED_RR调度策略,普通的进程采用SCHED_OTHER调度策略。

优先级:

非实时进程有两种优先级,一种是静态优先级,另一种是动态优先级。实时进程又增加了第三种优先级,实时优先级。优先级是一些简单的整数,它代表了为决定应该允许哪一个进程使用 CPU 的资源时判断方便而赋予进程的权值——优先级越高,它得到 CPU 时间的机会也就越大:
1 .静态优先级——被称为“静态”是因为它不随时间而改变,只能由用户进行修改。它指明了在被迫和其它进程竞争 CPU 之前该进程所应该被允许的时间片的最大(但是也可能由于其它原因,在该时间片耗尽之前进程就被迫交出了 CPU。)值。
2.动态优先级——只要进程拥有 CPU,它就随着时间不断减小;当它小于 0 时,标记进程重新调度。它指明了在这个时间片中所剩余的时间量。
3.实时优先级——指明这个进程自动把 CPU 交给哪一个其它进程:较高权值的进程总是优先于较低权值的进程。因为如果一个进程不是实时进程,其优先级就是 0,所以实时进程总是优先于非实时进程的。
(这并不完全正确;如同后面论述的一样,实时进程也会明确地交出 CPU,而在等待 I/O 时也会被迫交出 CPU。前面的描述
仅限于能够交付 CPU 运行的进程).
 

 

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/types.h>
#include<signal.h>
#include<sched.h>
#include<sys/times.h>
#include<sys/resource.h>
typedef void (*sighandler_t)(int);
void sigcon(){
	pid_t pi=getpid();
        int pj=getpriority(PRIO_PROCESS,pi);
        setpriority(PRIO_PROCESS,pi,pj+1);
        printf("the  pid is : %d ,the priority is %d,after SIGINT the priority is : %d\n",pi,pj,pj+1);
}
void sigcon1(){
        pid_t pi=getpid();
	int pj=getpriority(PRIO_PROCESS,pi);
	setpriority(PRIO_PROCESS,pi,pj-1);
	printf("the  pid is : %d ,the priority is %d,after SIGTSTP the priority is : %d\n",pi,pj,pj-1);
}
int main(){
	pid_t p0;
	signal(SIGINT,(sighandler_t)sigcon);
	signal(SIGTSTP,(sighandler_t)sigcon1);
	int index;
	if((p0=fork())<0){
		perror("error!");
		exit(0);
	}
	else if(p0==0){
		for(index=0;index<4;index++){
		pid_t cp=getpid();
		int chpriority=getpriority(PRIO_PROCESS,cp);
		int schednum=sched_getscheduler(cp);
		printf("the child pid is : %d , the priority is : %d , the sched is : %d \n",cp,chpriority,schednum);
		kill(cp,SIGINT);
//                kill(cp,SIGTSTP);
		}
	}
	else{
		int in;
		for(in=0;in<4;in++){
		pid_t pp=getpid();
		int ppriority=getpriority(PRIO_PROCESS,pp);
		int pschednum=sched_getscheduler(pp);
		printf("the parent pid is : %d , the priority is : %d , the sched is : %d \n",pp,ppriority,pschednum);
		kill(pp,SIGINT);
        //	kill(pp,SIGTSTP);
		}
	}
	return 0;
}			 

os实验--线程的创建

Linux 利用了特有的内核函数__clone 实现了一个叫 phread 的线程库,__clone是 fork 函数的替代函数,通过更多的控制父子进程共享哪些资源而实现了线程。Pthread 是一个标准化模型,用它可把一个程序分成一组能够并发执行的多个任务。phread 线程库是 POSIX 线程标准的实现,它提供了 C 函数的线程调用接口和数据结构。

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<pthread.h>
int  tjobs0(int  );
int  tjobs1(int  );
pthread_t thr0,thr1;
int num0,num1;
int main(int argc,char * argv[]){
	int result;
	int ret0,ret1;
	int x,y;
	printf("input the x and y\n");
	printf("input x: ");
        scanf("%d",&x);
	printf("input y: ");
	scanf("%d",&y);
	ret0=pthread_create(&thr0,NULL,(void*)tjobs0,(void * )x);
	if(ret0<0){
		perror("error!");
		exit(0);
	}
        ret1=pthread_create(&thr1,NULL,(void *)tjobs1,(void *)y);
        if(ret1<0){
	 perror("error!");
	 exit(0);	
	}
	pthread_join(thr0,NULL);
	pthread_join(thr1,NULL);
	result=num0+num1;
	printf("the result is %d\n",result);
	return 0;
	}
int  tjobs0(int  i){
	if(i==1)
		num0=1;
	else if(i>1){
		num0=tjobs0(i-1)*i;
		i--;
	}
	return num0;
}
int  tjobs1(int  j){
	if(j==1||j==2)
		num1=1;
	else if(j>2){
		num1=tjobs1(j-1)+tjobs1(j-2);
		j--;
	}
	return num1; 
}

gcc :gcc是GNU计划的c /c++的编译器。

gcc的用法(详细:info gcc ):

-E: 预处理。

-S:z只编译,不汇编,生成汇编代码。

-c:编译或者汇编,但不链接。

-o:输出到指定文件。

 




Host by is-Programmer.com | Power by Chito 1.3.3 beta | © 2007 LinuxGem | Design by Matthew "Agent Spork" McGee