块设备硬盘简介

晚上值班…正好弄篇文章吧….内核看到了块设备这一章节了….有点头晕了..看了一下午…..继续努力了…相信世界上没有困难…只是自己心理上的障碍罢了..

: 块设备硬盘的基础知识:

硬盘存储信息的格式是按柱面、磁头号和扇区来存储的,硬磁盘每个存储表面被划分成若干个磁道,每道划分成若干个扇区。

存储容量=磁头数×柱面数×扇区数×每扇区字节数

磁头数: 磁头是硬盘读取数据的关键部件,,在工作状态时,磁头悬浮在盘片上方,而不与盘片直接接触,电源关闭之后,磁头会自动回到在盘片上的起始位置。硬盘的磁头数取决于硬盘中的碟片数,盘片正反两面都存储着数据,所以一个盘片对应两个磁头才能正常工作。比如总容量80GB的硬盘,采用单碟容量80GB的盘片,那只有一张盘片,该盘片正反面都有数据,则对应两个磁头;而同样总容量120GB的硬盘,采用二张盘片,则只有三个磁头,其中一张盘片的一面没有磁头。

扇区: 硬盘内部是金属盘片,将圆形的盘片划分成若干个扇形区域,这就是扇区.

磁道: 以盘片中心为圆心,把盘片分成若干个同心圆,那每一个划分圆的线条,就称为磁道。

柱面: N张盘片中相同位置的磁道组成一个柱面,磁道 v==柱面数

: 块设备硬盘的基础知识:

硬盘存储信息的格式是按柱面、磁头号和扇区来存储的,硬磁盘每个存储表面被划分成若干个磁道,每道划分成若干个扇区。

存储容量=磁头数×柱面数×扇区数×每扇区字节数

磁头数: 磁头是硬盘读取数据的关键部件,,在工作状态时,磁头悬浮在盘片上方,而不与盘片直接接触,电源关闭之后,磁头会自动回到在盘片上的起始位置。硬盘的磁头数取决于硬盘中的碟片数,盘片正反两面都存储着数据,所以一个盘片对应两个磁头才能正常工作。比如总容量80GB的硬盘,采用单碟容量80GB的盘片,那只有一张盘片,该盘片正反面都有数据,则对应两个磁头;而同样总容量120GB的硬盘,采用二张盘片,则只有三个磁头,其中一张盘片的一面没有磁头。

扇区: 硬盘内部是金属盘片,将圆形的盘片划分成若干个扇形区域,这就是扇区.

磁道: 以盘片中心为圆心,把盘片分成若干个同心圆,那每一个划分圆的线条,就称为磁道。

柱面: N张盘片中相同位置的磁道组成一个柱面,磁道 v==柱面数

终于明白”系统调用”的原理了…

以前不明白”系统调用”的原因是不懂函数指针这一概念….今天下午网上狂找答案…终于明白了…哈哈..嘿嘿..挺高兴的….一直想搞懂自己想要的东西…可是冥思苦想好几天还是搞不懂…..忽然有一天终于搞懂了自己想要的东西…..兴奋啊………..估计这就是看内核源码的乐趣所在吧…….明天回学校….祝自己一路顺风了

在unistd.h中有关于系统调用符号常数

#define __NR_setup 0   /* used only by init, to get system going */

/* __NR_setup 仅用于初始化,以启动系统 */

#define __NR_exit 1

#define __NR_fork 2

#define __NR_read 3

#define __NR_write 4

#define __NR_open 5

#define __NR_close 6

#define __NR_waitpid 7

#define __NR_creat 8

#define __NR_link 9

#define __NR_unlink 10

#define __NR_execve 11

#define __NR_chdir 12

#define __NR_time 13

#define __NR_mknod 14

#define __NR_chmod 15

#define __NR_chown 16

#define __NR_break 17

#define __NR_stat 18

#define __NR_lseek 19

#define __NR_getpid 20

#define __NR_mount 21

#define __NR_umount 22

#define __NR_setuid 23

#define __NR_getuid 24

#define __NR_stime 25

#define __NR_ptrace 26

#define __NR_alarm 27

#define __NR_fstat 28

#define __NR_pause 29

#define __NR_utime 30

#define __NR_stty 31

#define __NR_gtty 32

#define __NR_access 33

#define __NR_nice 34

#define __NR_ftime 35

#define __NR_sync 36

#define __NR_kill 37

#define __NR_rename 38

#define __NR_mkdir 39

#define __NR_rmdir 40

#define __NR_dup 41

#define __NR_pipe 42

#define __NR_times 43

#define __NR_prof 44

#define __NR_brk 45

#define __NR_setgid 46

#define __NR_getgid 47

#define __NR_signal 48

#define __NR_geteuid 49

#define __NR_getegid 50

#define __NR_acct 51

#define __NR_phys 52

#define __NR_lock 53

#define __NR_ioctl 54

#define __NR_fcntl 55

#define __NR_mpx 56

#define __NR_setpgid 57

#define __NR_ulimit 58

#define __NR_uname 59

#define __NR_umask 60

#define __NR_chroot 61

#define __NR_ustat 62

#define __NR_dup2 63

#define __NR_getppid 64

#define __NR_getpgrp 65

#define __NR_setsid 66

#define __NR_sigaction 67

#define __NR_sgetmask 68

#define __NR_ssetmask 69

#define __NR_setreuid 70

#define __NR_setregid 71

首先在sched.h中有这样的定义

typedef (*fn_ptr) ();

原来一直看不懂这是什么玩意….下午翻资料..终于明白了…….这是一个fn_ptr类型的定义…这个fn_ptr到底是什么东西呀……下午才搞懂…是指向函数的指针.

在sys.h中是函数的声明以及系统调用函数指针表

extern int sys_setup (); // 系统启动初始化设置函数。 (kernel/blk_drv/hd.c,71)

extern int sys_exit ();   // 程序退出。 (kernel/exit.c, 137)

extern int sys_fork ();   // 创建进程。 (kernel/system_call.s, 208)

extern int sys_read ();   // 读文件。 (fs/read_write.c, 55)

extern int sys_write (); // 写文件。 (fs/read_write.c, 83)

extern int sys_open ();   // 打开文件。 (fs/open.c, 138)

extern int sys_close (); // 关闭文件。 (fs/open.c, 192)

extern int sys_waitpid (); // 等待进程终止。 (kernel/exit.c, 142)

extern int sys_creat (); // 创建文件。 (fs/open.c, 187)

extern int sys_link ();   // 创建一个文件的硬连接。 (fs/namei.c, 721)

extern int sys_unlink (); // 删除一个文件名(或删除文件)。 (fs/namei.c, 663)

extern int sys_execve (); // 执行程序。 (kernel/system_call.s, 200)

extern int sys_chdir (); // 更改当前目录。 (fs/open.c, 75)

extern int sys_time ();   // 取当前时间。 (kernel/sys.c, 102)

extern int sys_mknod (); // 建立块/字符特殊文件。 (fs/namei.c, 412)

extern int sys_chmod (); // 修改文件属性。 (fs/open.c, 105)

extern int sys_chown (); // 修改文件宿主和所属组。 (fs/open.c, 121)

extern int sys_break (); // (-kernel/sys.c, 21)

extern int sys_stat ();   // 使用路径名取文件的状态信息。 (fs/stat.c, 36)

extern int sys_lseek (); // 重新定位读/写文件偏移。 (fs/read_write.c, 25)

extern int sys_getpid (); // 取进程id。 (kernel/sched.c, 348)

extern int sys_mount (); // 安装文件系统。 (fs/super.c, 200)

extern int sys_umount (); // 卸载文件系统。 (fs/super.c, 167)

extern int sys_setuid (); // 设置进程用户id。 (kernel/sys.c, 143)

extern int sys_getuid (); // 取进程用户id。 (kernel/sched.c, 358)

extern int sys_stime (); // 设置系统时间日期。 (-kernel/sys.c, 148)

extern int sys_ptrace (); // 程序调试。 (-kernel/sys.c, 26)

extern int sys_alarm (); // 设置报警。 (kernel/sched.c, 338)

extern int sys_fstat (); // 使用文件句柄取文件的状态信息。(fs/stat.c, 47)

extern int sys_pause (); // 暂停进程运行。 (kernel/sched.c, 144)

extern int sys_utime (); // 改变文件的访问和修改时间。 (fs/open.c, 24)

extern int sys_stty ();   // 修改终端行设置。 (-kernel/sys.c, 31)

extern int sys_gtty ();   // 取终端行设置信息。 (-kernel/sys.c, 36)

extern int sys_access (); // 检查用户对一个文件的访问权限。(fs/open.c, 47)

extern int sys_nice ();   // 设置进程执行优先权。 (kernel/sched.c, 378)

extern int sys_ftime (); // 取日期和时间。 (-kernel/sys.c,16)

extern int sys_sync ();   // 同步高速缓冲与设备中数据。 (fs/buffer.c, 44)

extern int sys_kill ();   // 终止一个进程。 (kernel/exit.c, 60)

extern int sys_rename (); // 更改文件名。 (-kernel/sys.c, 41)

extern int sys_mkdir (); // 创建目录。 (fs/namei.c, 463)

extern int sys_rmdir (); // 删除目录。 (fs/namei.c, 587)

extern int sys_dup ();   // 复制文件句柄。 (fs/fcntl.c, 42)

extern int sys_pipe ();   // 创建管道。 (fs/pipe.c, 71)

extern int sys_times (); // 取运行时间。 (kernel/sys.c, 156)

extern int sys_prof ();   // 程序执行时间区域。 (-kernel/sys.c, 46)

extern int sys_brk ();   // 修改数据段长度。 (kernel/sys.c, 168)

extern int sys_setgid (); // 设置进程组id。 (kernel/sys.c, 72)

extern int sys_getgid (); // 取进程组id。 (kernel/sched.c, 368)

extern int sys_signal (); // 信号处理。 (kernel/signal.c, 48)

extern int sys_geteuid (); // 取进程有效用户id。 (kenrl/sched.c, 363)

extern int sys_getegid (); // 取进程有效组id。 (kenrl/sched.c, 373)

extern int sys_acct ();   // 进程记帐。 (-kernel/sys.c, 77)

extern int sys_phys ();   // (-kernel/sys.c, 82)

extern int sys_lock ();   // (-kernel/sys.c, 87)

extern int sys_ioctl (); // 设备控制。 (fs/ioctl.c, 30)

extern int sys_fcntl (); // 文件句柄操作。 (fs/fcntl.c, 47)

extern int sys_mpx ();   // (-kernel/sys.c, 92)

extern int sys_setpgid (); // 设置进程组id。 (kernel/sys.c, 181)

extern int sys_ulimit (); // (-kernel/sys.c, 97)

extern int sys_uname (); // 显示系统信息。 (kernel/sys.c, 216)

extern int sys_umask (); // 取默认文件创建属性码。 (kernel/sys.c, 230)

extern int sys_chroot (); // 改变根系统。 (fs/open.c, 90)

extern int sys_ustat (); // 取文件系统信息。 (fs/open.c, 19)

extern int sys_dup2 ();   // 复制文件句柄。 (fs/fcntl.c, 36)

extern int sys_getppid (); // 取父进程id。 (kernel/sched.c, 353)

extern int sys_getpgrp (); // 取进程组id,等于getpgid(0)。(kernel/sys.c, 201)

extern int sys_setsid (); // 在新会话中运行程序。 (kernel/sys.c, 206)

extern int sys_sigaction (); // 改变信号处理过程。 (kernel/signal.c, 63)

extern int sys_sgetmask (); // 取信号屏蔽码。 (kernel/signal.c, 15)

extern int sys_ssetmask (); // 设置信号屏蔽码。 (kernel/signal.c, 20)

extern int sys_setreuid (); // 设置真实与/或有效用户id。 (kernel/sys.c,118)

extern int sys_setregid (); // 设置真实与/或有效组id。 (kernel/sys.c, 51)

// 系统调用函数指针表。用于系统调用中断处理程序(int 0x80),作为跳转表。

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,

sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,

sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,

sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,

sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,

sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,

sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,

sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,

sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,

sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,

sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,

sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,

sys_setreuid, sys_setregid

};

各个系统调用的具体实现分别在相应的文件中……..终于搞定了….哦耶…嘘~~~低调…低调…

关于函数指针数组与返回数组指针的函数

int (*a[])(int);

int (*p())[10];

第一种情况为数组里面是函数指针的情况,因为(int (*)(int))是一个强制转换方式, 将里面的a[]这个数组转换成了一个函数指针的数组, 并且该函数是一个带一个整型变量,并且返回一个整型的函数.

第二种情况为函数返回的为指向一个一维数组的指针的情况. 因为(int (*)[10])将其强制转换成了一个指针,而该指针则是一个指向一维数组的指针.

分别举两个例子进行说明:

对于第一种函数指针数组的情况,如下:

#include<stdio.h>

int fun(int a)

{

         return a+1;

}

int main()

{

         int (*p[10])(int);

         int i;

         p[0] = fun;

         i = (p[0])(10);

         printf(“i=%dn”, i);

         return 0;

}

p[10]为一个指针数组,而该指针数组里面的值为函数指针类型.让p[0]指向fun()函数.然后再进行调用,就可以调用到fun()这个函数了.

对于第二种返回数组指针的函数,如下:

#include<stdio.h>

#include<stdlib.h>

int (*p())[10]

{

int (*m)[10];

int i;

m = (int (*)[10])calloc(10, sizeof(int));

if (m == NULL)

{

   printf(“calloc errorn”);

   exit(1);

}

for (i = 0; i < 10; i++)

   *(*m+i) = i+1;

return m;

}

int main()

{

int (*a)[10];

int i;

a = p();

for (i = 0; i < 10; i++)

   printf(“%d “, *(*a+i));

printf(“ndonen”);

return 0;

}

其实int (*m)[10];这种方式一般是用来指向一个二维数组的,例如

int b[4][10];

int (*m)[10] = b;

其指向二维数组中的一维.

使用*(*(m+i)+j);这种方式就可以访问b[i][j]这个元素.而上面的是使用这种方式来指向一个一维数组,同样也是一样的.只是前面的*(m+i)中的i变为0了.因为只有一维大小.即

int a[10];

int (*m)[10] = &a;

就使得m指向了a这个数组了.而平时所用的int *p = a;只是让p指向了a的第一个元素.比前面的指向一维数组的指针少了一维.前面的m+1跳过的是10个整型的长度.而后面的p+1则只是跳过了1个整型的长度.

typedef的真正含义2[转自csdn]

typedef使用大全3(指向函数的指针)

typedef的使用中,最麻烦的是指向函数的指针,如果没有下面的函数,你知道下面这个表达式的定义以及如何使用它吗?

int (*s_calc_func(char op))(int, int);
如果不知道,请看下面的程序,里面有比较详细的说明
// 定义四个函数
int add(int, int);
int sub(int, int);
int mul(int, int);
int div(int, int);
// 定义指向这类函数的指针
typedefint (*FP_CALC)(int, int);
// 我先不介绍,大家能看懂下一行的内容吗?
int (*s_calc_func(char op))(int, int);
// 下一行的内容与上一行完全相同,
// 定义一个函数calc_func,它根据操作字符 op 返回指向相应的计算函数的指针
FP_CALC calc_func(char op);
// 根据 op 返回相应的计算结果值
int calc(int a, int b, char op);
int add(int a, int b)
{
    return a + b;
}
int sub(int a, int b)
{
    return a – b;
}
int mul(int a, int b)
{
    return a * b;
}
int div(int a, int b)
{
    return b? a/b : -1;
}
// 这个函数的用途与下一个函数作业和调用方式的完全相同,
// 参数为op,而不是最后的两个整形
int (*s_calc_func(char op)) (int, int)
{
    return calc_func(op);
}
FP_CALC calc_func(char op)
{
    switch (op)
    {
    case ‘+': return add;
    case ‘-‘: return sub;
    case ‘*': return mul;
    case ‘/': return div;
    default:
       returnNULL;
    }
    returnNULL;
}
int calc(int a, int b, char op)
{
    FP_CALC fp = calc_func(op); // 下面是类似的直接定义指向函数指针变量
       // 下面这行是不用typedef,来实现指向函数的指针的例子,麻烦!
       int (*s_fp)(int, int) = s_calc_func(op);
       // ASSERT(fp == s_fp); // 可以断言这俩是相等的
    if (fp) return fp(a, b);
    elsereturn -1;
}
void test_fun()
{
    int a = 100, b = 20;
    printf(“calc(%d, %d, %c) = %dn”, a, b, ‘+’, calc(a, b, ‘+’));
    printf(“calc(%d, %d, %c) = %dn”, a, b, ‘-‘, calc(a, b, ‘-‘));
    printf(“calc(%d, %d, %c) = %dn”, a, b, ‘*’, calc(a, b, ‘*’));
    printf(“calc(%d, %d, %c) = %dn”, a, b, ‘/’, calc(a, b, ‘/’));
}
运行结果
    calc(100, 20, +) = 120
    calc(100, 20, -) = 80
    calc(100, 20, *) = 2000
    calc(100, 20, /) = 5
typedef可以定义各种类型名,但是不能用来定义变量;

typedef只是对已经存在的类型增加一个类型名,没有创造新的类型;

typedef和#define虽然能起到类似的作用,但是两者不同,

#define属于预编译处理,typedef采用定义变量的方法定义一个类型;

当不同的源文件中使用同一类型数据时,常常吧它们单独存放在一个文件中,

然后在需要用到它们的时候,在文件中用#include命令包含进来;

使用typedef有益于程序的通用性和移植。


1、用其定义新的类型名代替已有的类型
typedef int   INTEGER;

用INTEGER代表int类型

即int i,j;可以用INTEGER i,j;来代替


2、定义结构体
typedef struct

{

int month;

int day;

int year;

}DATE;

新类型名DATE代表上面的结构体类型。

即DATE birthday;声明一个结构体。

DATE *p;//p为指向此结构体类型数据的指针
3、其他



typedef int NUM[100];//定义NUM为整型数组类型

NUM xyz;//定义xyz为整型数组变量



typedef char *cs;//定义cs为字符串指针类型

cs p,s[10];//p为字符串指针变量,s为指针数组



typedef int (*ptr)();//定义ptr为指向函数的指针类型,该函数返回整型值

ptr p1,p2;//p1,p2为ptr类型的指针变量


————–

附注:

㈠读代码用带入法来理解

typedef int NUM[100];//

NUM xyz;// 相当于 int xyz[100]; 在typedef中将NUM用变量名xyz代替
typedef int (*ptr)();//

ptr p1;//相当于 int (*p1)(); 在typedef中将ptr用变量名p1代替
㈡写代码

如果希望int xyz[100];

首先,一般性的描述 int NUM[100];

接着添加头,为typedef int NUM[100];

好处在于

int x[100],y[100],z[100];//不使用

NUM x,y,z;//使用

方便书写代码,并且方便修改程序,一处更新其他地方都更新
同样

如果希望int (*x)();

首先,一般性的描述 int (*ptr)();

接着添加头,为typedef int (*ptr)();

好处在于

int (*x)(),(*y)(),(*z)();//不使用

ptr x,y,z;//使用

typedef的真正含意[转自csdn]

typedef的真正含意

1. 找到你所要申明的通用格式。例如这里申明int a[4], b[4], c[4];只有a,b,c不同,它们有相同的申明模式 int <名字>[4];

2.
用你想申明的新类型名代替通用格式中的变化部分。例如这里就是用新类型int_array代替a,b,c所在的位置,再在前面加上typedef符号,即:

typedef int int_array[4];

3.
以后你想申明处在上面int_array位置的a,b,c类型时,就可以用如下语句:

int_array a, b, c;
简单点说在一个定义 typedef <字符串>;中, <字符串>中会出现一个未定义的类型名Type_A, 当你使用该typedef的时候:

Type_A object;
它的含义就是:你实际上申明的是用object名去替换<字符串>中的 Type_A
例如 typedef int int_array[4];

int_array object;
实际上你写的申明是:用object替换字符串” int int_array[4];”中的 int_array得到的结果: int object[4];
这也就是编译器处理tyepdef定义的原理。
定义类型只用写出一个完整的定义..前边上typedef 就行了.


char *cstr;//
定义了一个char*变量cstr.

typedef char *cstr;//
char*起个别名cstr;



int int_array[10];//
定义一个数组int_array.

typedef int int_array[10]//int_array
变成了int[10]的别名.


void (*pfn)(void);//
定义了一个函数指针.

typedef void (*pfn)(void);//
为此函数指针起了个别名.


typedef int (*pf)();

pf test[10];

这样定义就是把pf作为一个类型,是一个int型的函数指针类型。
所以,pf test[10];//这样就是定义了函数指针的数组,数组含10个元素。
总结:typedef的作用是将一个变量变为它本身的类型,而不是为一个类型定义一个别名。
typedef使用大全1(数组)

typedef到处都是,但是能够真正懂得typedef使用的不算太多。对于初学者而言,看别人的源码时对到处充斥的typedef往往不知所错,而参考书又很少,所以在此给出一个源码,供大家参考

#include <stdio.h>
#include <iostream.h>
/* 避免Visual Cfor与标准for的不同 */
#definefor if (0);   elsefor
/* dim(a)是用于计算a的维数,不过只能计算数组的维数,不能计算指针的维数 */
#define dim(a) (sizeof(a)/sizeof(a[0]))
/* N1N4是几个常量,以枚举的形式定义 */
enum {N1 = 2, N2 = 3, N3 = 4, N4 = 5};
/* 这个C程序员都知道,就是将DataType定义为int型,便于扩充 */
typedefint DataType;
/* 定义一个一维数组,数组的元素维整型值 */
typedef DataType ARR1[N4];
/* 再定义一个一维数组,数组的元素维ARR1型,不过ARR1又是一个数组,所以
* ARR2 实际上是一个矩阵
*/
typedef ARR1 ARR2[N3]; /* 此处完全等价为typedef int ARR2[N3][N4];*/
/* 按照ARR2的解释,ARR3也是一个一维数组,不过数组元素的类型是ARR2的类型
* 所有ARR3是一个三维数组
*/
typedef ARR2 ARR3[N2]; /* 此处完全等价为typedef int ARR3[N2][N3][N4];*/
/* 分别用定义好的ARR1ARR2ARR3定义三个变量a, b, c */
ARR1 a; /* 此处完全等价于:int a[N4]; */
ARR2 b; /* 此处完全等价于:int b[N3][N4]; */
ARR3 c; /* 此处完全等价于:int c[N2][N3][N4]; */
/* 下面函数给大家个示例看a,b,c如何使用 */
void exam_1()
{
    for (int i=0; i<dim(a); i++) a[i] = i+1;
    for (int i=0; i<dim(b); i++) for (int j=0; j<dim(b[0]); j++)
        b[i][j] = (i+1)*10 + (j+1);
    for (int i=0; i<dim(c); i++) for (int j=0; j<dim(c[0]); j++)
        for (int k=0; k<dim(c[0][0]); k++) c[i][j][k] = (i+1)*100 + (j+1)*10 + (k+1);
    printf(“nThe a is :n”);
    for (int i=0; i<dim(a); i++) printf(“%4d “, a[i]);
    printf(“n”);
    printf(“nThe b is :n”);
    for (int i=0; i<dim(b); i++)
    {
        for (int j=0; j<dim(b[0]); j++) printf(“%4d “, b[i][j]);
        printf(“n”);
    }
    printf(“nthe c is:n”);
    for (int i=0; i<dim(c); i++)
    {
        for (int j=0; j<dim(c[0]); j++)
        {
            for (int k=0; k<dim(c[0][0]); k++) printf(“%4d “, c[i][j][k]);
            printf(“n”);
        }
        printf(“n”);
    }
}
/* 下面函数给大家演示数组在内存中的排列 */
void exam_2()
{
    int *pn = NULL;
    pn = (int *)a; /* 等价于 pn = &a[0]; */
    printf(“nThe a is :n”);
    for (int i=0; i<sizeof(a)/sizeof(DataType); i++) printf(“%4d “, pn[i]);
    printf(“n”);
    pn = (int *)b; /* 等价于 pn = &b[0][0]; */
    printf(“nThe b is :n”);
    for (int i=0; i<sizeof(b)/sizeof(DataType); i++) printf(“%4d “, pn[i]);
    printf(“n”);
    pn = (int *)c; /* 等价于 pn = &c[0][0][0]; */
    printf(“nThe c is :n”);
    for (int i=0; i<sizeof(c)/sizeof(DataType); i++) printf(“%4d “, pn[i]);
    printf(“n”);
}
int main(int argc, char* argv[])
{
    exam_1();
    exam_2();
    return 0;
}

C语言复杂指针声明应如何阅读[转自csdn]

C语言所有复杂的指针声明,都是由各种声明嵌套构成的。如何解读复杂指针声明呢?右左法则是一个既著名又常用的方法。不过,右左法则其实并不是C标准里面的内容,它是从C标准的声明规定中归纳出来的方法。C标准的声明规则,是用来解决如何创建声明的,而右左法则是用来解决如何辩识一个声明的,两者可以说是相反的。右左法则的英文原文是这样说的:


The right-left rule: Start reading the declaration from the innermost parentheses, go right, and then go left. When you encounter parentheses, the direction should be reversed. Once everything in the parentheses has been parsed, jump out of it. Continue till the whole declaration has been parsed.



这段英文的翻译如下:


右左法则:首先从最里面的圆括号看起,然后往右看,再往左看。每当遇到圆括号时,就应该掉转阅读方向。一旦解析完圆括号里面所有的东西,就跳出圆括号。重复这个过程直到整个声明解析完毕。


笔者要对这个法则进行一个小小的修正,应该是从未定义的标识符开始阅读,而不是从括号读起,之所以是未定义的标识符,是因为一个声明里面可能有多个标识符,但未定义的标识符只会有一个。


现在通过一些例子来讨论右左法则的应用,先从最简单的开始,逐步加深:


int (*func)(int *p);


首先找到那个未定义的标识符,就是func,它的外面有一对圆括号,而且左边是一个*号,这说明func是一个指针,然后跳出这个圆括号,先看右边,也是一个圆括号,这说明(*func)是一个函数,而func是一个指向这类函数的指针,就是一个函数指针,这类函数具有int*类型的形参,返回值类型是int。


int (*func)(int *p, int (*f)(int*));


func被一对括号包含,且左边有一个*号,说明func是一个指针,跳出括号,右边也有个括号,那么func是一个指向函数的指针,这类函数具有int *和int (*)(int*)这样的形参,返回值为int类型。再来看一看func的形参int (*f)(int*),类似前面的解释,f也是一个函数指针,指向的函数具有int*类型的形参,返回值为int。


int (*func[5])(int *p);


func右边是一个[]运算符,说明func是一个具有5个元素的数组,func的左边有一个*,说明func的元素是指针,要注意这里的*不是修饰func的,而是修饰func[5]的,原因是[]运算符优先级比*高,func先跟[]结合,因此*修饰的是func[5]。跳出这个括号,看右边,也是一对圆括号,说明func数组的元素是函数类型的指针,它所指向的函数具有int*类型的形参,返回值类型为int。



int (*(*func)[5])(int *p);


func被一个圆括号包含,左边又有一个*,那么func是一个指针,跳出括号,右边是一个[]运算符号,说明func是一个指向数组的指针,现在往左看,左边有一个*号,说明这个数组的元素是指针,再跳出括号,右边又有一个括号,说明这个数组的元素是指向函数的指针。总结一下,就是:func是一个指向数组的指针,这个数组的元素是函数指针,这些指针指向具有int*形参,返回值为int类型的函数。


int (*(*func)(int *p))[5];


func是一个函数指针,这类函数具有int*类型的形参,返回值是指向数组的指针,所指向的数组的元素是具有5个int元素的数组。


要注意有些复杂指针声明是非法的,例如:


int func(void) [5];


func是一个返回值为具有5个int元素的数组的函数。但C语言的函数返回值不能为数组,这是因为如果允许函数返回值为数组,那么接收这个数组的内容的东西,也必须是一个数组,但C语言的数组名是一个右值,它不能作为左值来接收另一个数组,因此函数返回值不能为数组。


int func[5](void);


func是一个具有5个元素的数组,这个数组的元素都是函数。这也是非法的,因为数组的元素除了类型必须一样外,每个元素所占用的内存空间也必须相同,显然函数是无法达到这个要求的,即使函数的类型一样,但函数所占用的空间通常是不相同的。


作为练习,下面列几个复杂指针声明给读者自己来解析,答案放在第十章里。


int (*(*func)[5][6])[7][8];


int (*(*(*func)(int *))[5])(int *);


int (*(*func[7][8][9])(int*))[5];


实际当中,需要声明一个复杂指针时,如果把整个声明写成上面所示的形式,对程序可读性是一大损害。应该用typedef来对声明逐层分解,增强可读性,例如对于声明:


int (*(*func)(int *p))[5];


可以这样分解:


typedef int (*PARA)[5];

typedef PARA (*func)(int *);


这样就容易看得多了。

试试你的C语言功底…..这些能区别开吗?

a) 一个整型数(An integer)

b) 一个指向整型数的指针(A pointer to an integer)

c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)

d) 一个有10个整型数的数组(An array of 10 integers)

e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)

f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)

g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)

h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

想知道答案吗?组合键ctrl+a

答案是:

a) int a; // An integer

b) int *a; // A pointer to an integer

c) int **a; // A pointer to a pointer to an integer

d) int a[10]; // An array of 10 integers

e) int *a[10]; // An array of 10 pointers to integers

f) int (*a)[10]; // A pointer to an array of 10 integers

g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer

h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

函数指针类型

一 通常的函数调用

一个通常的函数调用的例子:

//自行包含头文件
void MyFun(int x);    //此处的申明也可写成:void MyFun( int );

int main(int argc, char* argv[])
{
   MyFun(10);     //这里是调用MyFun(10);函数
   return 0;
}

void MyFun(int x)  //这里定义一个MyFun函数
{
   printf(“%dn”,x);
}

这个MyFun函数是一个无返回值的函数,它并不完成什么事情。这种调用函数的格式你应该是很熟悉的吧!看主函数中调用MyFun函数的书写格式:

MyFun(10);

我们一开始只是从功能上或者说从数学意义上理解MyFun这个函数,知道MyFun函数名代表的是一个功能(或是说一段代码)。

直到——

学习到函数指针概念时。我才不得不再思考:函数名到底又是什么东西呢?

(不要以为这是没有什么意义的事噢!呵呵,继续往下看你就知道了。)

二 函数指针变量的申明

就象某一数据变量的内存地址可以存储在相应的指针变量中一样,函数的首地址也以存储在某个函数指针变量里的。这样,我就可以通过这个函数指针变量来调用所指向的函数了。

在C系列语言中,任何一个变量,总是要先申明,之后才能使用的。那么,函数指针变量也应该要先申明吧?那又是如何来申明呢?以上面的例子为例,我来申明一个可以指向MyFun函数的函数指针变量FunP。下面就是申明FunP变量的方法:

void (*FunP)(int) ;//也可写成void (*FunP)(int x);

你看,整个函数指针变量的申明格式如同函数MyFun的申明处一样,只不过——我们把MyFun改成(*FunP)而已,这样就有了一个能指向MyFun函数的指针FunP了。(当然,这个FunP指针变量也可以指向所有其它具有相同参数及返回值的函数了。)

三 通过函数指针变量调用函数

有了FunP指针变量后,我们就可以对它赋值指向MyFun,然后通过FunP来调用MyFun函数了。看我如何通过FunP指针变量来调用MyFun函数的:

//自行包含头文件
void MyFun(int x);    //这个申明也可写成:void MyFun( int );
void (*FunP)(int );   //也可申明成void(*FunP)(int x),但习惯上一般不这样。

int main(int argc, char* argv[])
{
   MyFun(10);     //这是直接调用MyFun函数
   FunP=&amp;MyFun;  //将MyFun函数的地址赋给FunP变量
   (*FunP)(20);    //这是通过函数指针变量FunP来调用MyFun函数的。
}

void MyFun(int x)  //这里定义一个MyFun函数
{
   printf(“%dn”,x);
}

请看黑体字部分的代码及注释。

运行看看。嗯,不错,程序运行得很好。

哦,我的感觉是:MyFun与FunP的类型关系类似于int 与int *的关系。函数MyFun好像是一个如int的变量(或常量),而FunP则像一个如int *一样的指针变量。

int i,*pi;
pi=&amp;i;    //与FunP=&amp;MyFun比较。

(你的感觉呢?)

呵呵,其实不然——

四 调用函数的其它书写格式

函数指针也可如下使用,来完成同样的事情:

//自行包含头文件
void MyFun(int x);
void (*FunP)(int );    //申明一个用以指向同样参数,返回值函数的指针变量。

int main(int argc, char* argv[])
{
   MyFun(10);     //这里是调用MyFun(10);函数
   FunP=MyFun;  //将MyFun函数的地址赋给FunP变量
   FunP(20);    //这是通过函数指针变量来调用MyFun函数的。

   return 0;
}

void MyFun(int x)  //这里定义一个MyFun函数
{
   printf(“%dn”,x);
}

我改了黑体字部分(请自行与之前的代码比较一下)。

运行试试,啊!一样地成功。

咦?

FunP=MyFun;

可以这样将MyFun值同赋值给FunP,难道MyFun与FunP是同一数据类型(即如同的int 与int的关系),而不是如同int 与int*的关系了?(有没有一点点的糊涂了?)

看来与之前的代码有点矛盾了,是吧!所以我说嘛!

请容许我暂不给你解释,继续看以下几种情况(这些可都是可以正确运行的代码哟!):

代码之三:

int main(int argc, char* argv[])
{
   MyFun(10);     //这里是调用MyFun(10);函数
   FunP=&amp;MyFun;  //将MyFun函数的地址赋给FunP变量
   FunP(20);    //这是通过函数指针变量来调用MyFun函数的。

   return 0;
}

代码之四:

int main(int argc, char* argv[])
{
   MyFun(10);     //这里是调用MyFun(10);函数
   FunP=MyFun;  //将MyFun函数的地址赋给FunP变量
   (*FunP)(20);    //这是通过函数指针变量来调用MyFun函数的。

   return 0;
}

真的是可以这样的噢!

(哇!真是要晕倒了!)

还有呐!看——

int main(int argc, char* argv[])
{
   (*MyFun)(10);     //看,函数名MyFun也可以有这样的调用格式

    return 0;
}

你也许第一次见到吧:函数名调用也可以是这样写的啊!(只不过我们平常没有这样书写罢了。)

那么,这些又说明了什么呢?

呵呵!依据以往的知识和经验来推理本篇的“新发现”,我想就连“福尔摩斯”也必定会由此分析并推断出以下的结论:

1. 其实,MyFun的函数名与FunP函数指针都是一样的,即都是函数指针。MyFun函数名是一个函数指针常量,而FunP是一个函数数指针变量,这是它们的关系。

2. 但函数名调用如果都得如(*MyFun)(10);这样,那书写与读起来都是不方便和不习惯的。所以C语言的设计者们才会设计成又可允许MyFun(10);这种形式地调用(这样方便多了并与数学中的函数形式一样,不是吗?)。

3. 为统一起见,FunP函数指针变量也可以FunP(10)的形式来调用。

4. 赋值时,即可FunP=&MyFun形式,也可FunP=MyFun。

上述代码的写法,随便你爱怎么着!

请这样理解吧!这可是有助于你对函数指针的应用喽!

最后——

补充说明一点:在函数的申明处:

void MyFun(int );  //不能写成void (*MyFun)(int )。
void (*FunP)(int );//不能写成void FunP(int )。

(请看注释)这一点是要注意的。

五 定义某一函数的指针类型:

就像自定义数据类型一样,我们也可以先定义一个函数指针类型,然后再用这个类型来申明函数指针变量。

我先给你一个自定义数据类型的例子。

typedef int* PINT;    //为int* 类型定义了一个PINT的别名
int main()
{
  int x;
  PINT px=&amp;x;   //与int * px=&amp;x;是等价的。PINT类型其实就是int * 类型
  *px=10;       //px就是int*类型的变量
  return 0;
}

根据注释,应该不难看懂吧!(虽然你可能很少这样定义使用,但以后学习Win32编程时会经常见到的。)

下面我们来看一下函数指针类型的定义及使用:(请与上对照!)

//自行包含头文件
void MyFun(int x);    //此处的申明也可写成:void MyFun( int );
typedef void (*FunType)(int );   //这样只是定义一个函数指针类型
FunType FunP;              //然后用FunType类型来申明全局FunP变量

int main(int argc, char* argv[])
{
//FunType FunP;    //函数指针变量当然也是可以是局部的 ,那就请在这里申明了。
   MyFun(10);
   FunP=&amp;MyFun;
   (*FunP)(20);

   return 0;
}

void MyFun(int x)
{
   printf(“%dn”,x);
}

看黑体部分:

首先,在void (*FunType)(int ); 前加了一个typedef 。这样只是定义一个名为FunType函数指针类型,而不是一个FunType变量。

然后,FunType FunP;   这句就如PINT px;一样地申明一个FunP变量。

其它相同。整个程序完成了相同的事。

这样做法的好处是:

有了FunType类型后,我们就可以同样地、很方便地用FunType类型来申明多个同类型的函数指针变量了。如下:

FunType FunP2;

FunType FunP3;

//……

六 函数指针作为某个函数的参数

既然函数指针变量是一个变量,当然也可以作为某个函数的参数来使用的。所以,你还应知道函数指针是如何作为某个函数的参数来传递使用的。

给你一个实例:

要求:我要设计一个CallMyFun函数,这个函数可以通过参数中的函数指针值不同来分别调用MyFun1、MyFun2、MyFun3这三个函数(注:这三个函数的定义格式应相同)。

实现:代码如下:

//自行包含头文件
void MyFun1(int x);
void MyFun2(int x);
void MyFun3(int x);
typedef void (*FunType)(int ); //②. 定义一个函数指针类型FunType,与①函数类型一至
void CallMyFun(FunType fp,int x);

int main(int argc, char* argv[])
{
   CallMyFun(MyFun1,10);   //⑤. 通过CallMyFun函数分别调用三个不同的函数
   CallMyFun(MyFun2,20);
   CallMyFun(MyFun3,30);
}
void CallMyFun(FunType fp,int x) //③. 参数fp的类型是FunType。
{
   fp(x);  //④. 通过fp的指针执行传递进来的函数,注意fp所指的函数是有一个参数的
}
void MyFun1(int x) // ①. 这是个有一个参数的函数,以下两个函数也相同
{
   printf(“函数MyFun1中输出:%dn”,x);
}
void MyFun2(int x)
{
   printf(“函数MyFun2中输出:%dn”,x);
}
void MyFun3(int x)
{
   printf(“函数MyFun3中输出:%dn”,x);
}

输出结果:

对linux分页机制有了进一步的理解.

昨天在看fork()函数的时候..心中生一疑惑..linux是怎么把线性地址高十位用于寻找页目录项..而线性地址又是怎么对应于页目录项的,为什么每个进程产生的线性地址高十位就是对应的页目录项…..

今日.通过认真的把fork()深入的看下去了..终于明白了…线性地址是怎样对应于页目录项的……..

原来总以为,每个进程的页目录项是紧挨着的…..今天通过看fork()函数才明白…原来不是我认为的那么回事..

线性地址空间有32位,最大容量是4G…….以前的疑惑是为什么线性地址高十位能对应到页目录项………每个页表能映射4M的地址……页目录项最多为1024个…..以前总认为每个进程的页目录是紧挨着的..没有多余的…如下图

页目录项1023

…….

页目录项5

页目录项4

页目录项3

页目录项2

页目录项1

页目录项0

例如linux0.11内核中把进程0映射到16M的物理内存空间….占用四个页目录项(每个页目录项可以映射4M),那么进程1就直接从页目录项5开始分配………..我就纳闷了……为什么会这样?错了?还是对了?

今天发现我错了…..原来线性地址空间已经被分成相对于页目录来4M,相对于页表来4K的内存页……………..

页目录项1023

……..

………

页目录项16

页目录项15

页目录项14

页目录项13

页目录项12

页目录项11

页目录项10

页目录项

页目录项8

页目录项7

页目录项6

页目录项5

页目录项4

页目录项3

页目录项2

页目录项1

页目录项

linux0.11把进程0映射16M物理内存..需要4个页目录项….那么页目录项4—页目录15就会被标识为p=0,代表页表不存在..进程1使用页目录的时候就会从页目录16开始…..

举个例子.

进程1的起始线性地址是0x4000000   

写成32位二进制是    0000 0100 0000 0000 0000 0000 0000 0000

最高十位正好是0x10   对应于页目录项16....

终于搞懂了...呵呵..