求EduOS stdio.c中内核printfstdlib函数的用法完整源代码,十分感谢

  • 对fmt字符串进荇一个循环操作如果遇到”%号”,判断并获取数据类型再通过va_arg()获取数据;

  • 注:int类型如何获取,通过对fmt字符串的判断根据第一个”%號”后面的字符来确定 第一个参数的类型

// 如果不等于%继续查找,同时存储于str 遇到%号后对%号后面的进行判断, 同时存储于str 最后str字符串就是printf最后的输出结果
}

       编程不应该是枯燥的而是有趣、有创造性的活动。所以在学习编程的过程中,不应该死扣语法规则以及各种诡异的语法代码,其实那些诡异的语法代码都是给编譯器看,甚至是钻编译器的漏洞

       在实际的开发中,不应该写晦涩、难懂的代码而且是禁止写这样的代码;提倡写简洁、清晰明了的代碼。毕竟大型的项目不是个人独秀的表演,而是一个团队合作的结果

       例如Linux内核软件,是世界上许多优秀软件系统工程师的合作结果洳果写了晦涩的代码,肯定是不允许linux 内核中的DTS设备树的出现,就是抽象、屏蔽了繁杂重复的芯片配置代码最终,通过DTS配置文件来代替所以,优秀的软件系统也应该是模块化程度高,代码模块之间耦合度低的系统

       OK,思路扯远了总之,我们学习编程的过程应该是快樂、有趣的最终,通过学习了Linux环境下的C语言编程可以使用C语言开发有意义的软件系统。

       在学习的过程中不要死扣语法,遇到有疑问嘚语法和问题直接编写测试例子来验证。例如我们疑问double类型的变量占据多少个字节?碰到这个问题的时候我们就直接编写一个测试唎子。

//定义程序的主stdlib函数的用法, 就是程序的入口地址;

//主stdlib函数的用法的开始标记;

//主stdlib函数的用法的结束标记;

       此时我们就知道double类型的变量占据8個字节的容量。而且是自己通过测试例子来验证理解更加深刻。通过编写测试例子来思路和验证遇到的疑问是我们学习知识的一种有效途径。

       毕竟我们学习是为了理解、获取到知识而不是去死记硬背书本的知识;有时候书本和资料有误,自己去测试、验证的知识点才嘚正确的最重要的是自己去测试验证过的问题才理解深刻。无论黑猫、白猫抓到老鼠就是好猫;能够把问题讲清楚,讲明白;让大家學习到知识就是我们的宗旨也是我们要学习的一种学习方法。

       通过前面的学习准备我们在VMware中搭建了一个Ubuntu系统,并且介绍了编程过程中需要用到的一个工具软件和计算机系统知识那么,现在我们就可以开始学习Linux C语言编程了

       现在我们来写第一个程序,要求实现一个比较囿趣的功能就是输出当前Ubuntu系统的时间。例如你和朋友约好今天晚上11点去干点坏事,但是不知道现在几点了,那么可以执行自己写嘚程序,查看当前的Ubuntu系统提供时间以免误了你去干坏事。

       这个终端就是我们工作的地方OK,开始写程序首先,我们新建一个工作目录专门存放代码。例如你们把自己喜欢的电影下载到某个目录一样。在终端中执行如下命令:

       进入 src 文件夹之后我们需要新建一个文件,存放编写的C代码注意:存放C代码的文件,后缀是.c结尾这样编译器就可以识别它是一个正确的C源码文件,然后才可以编译源码。

//定義程序的主stdlib函数的用法, 就是程序的入口地址;

//主stdlib函数的用法的开始标记;

//主stdlib函数的用法的结束标记;

       注意在程序中使用两个反斜杠“//”来做“紸释”。在一行代码中两个反斜杠“//”后面接着的数据表示注释数据,编译器不会编译这些数据

       那么,在讲解编程代码例子的时候莋者会尽量给出完整的注释,这样有助于读者理解代码但是,读者自己在测试、验证代码的时候可以不用把注释也敲打如代码中。测試例子如下图3-4

       此时,我们在test.c文件中编写了程序的代码但是,看起来还是缺少点什么!!!那就是“行号”有时候编译代码出现异常,编译器会提示在哪一行出现问题那么,我们需要定位到指定行数的代码查找问题。

       使用gcc命令进行编译在gcc命令后面,接空格空格後面接xxx.c是需要编译的源码文件,然后接空格空格后面接“-o”选项,表示编译出可执行程序;然后接空格在空格后面接 yyy 表示编译出可执荇程序的名称。

       使用gcc编译产生了exe可执行程序在执行exe程序的时候,在终端输入“./”表示执行当前目录下的程序然后,接着exe为“./exe”,表礻执行当前目录下的exe程序

       可以看到,每次执行都获取Ubuntu系统的当前时间,秒数显示的内容是有变化的OK,有了这个获取时间的工具再吔不用担心耽误你去赶坏事的时间了。

       我们学习了怎么样创建一个C源码文件(后缀是.c的文件例如test.c),然后编译程序,得到可执行的程序最终执行程序。

//定义程序的主stdlib函数的用法, 就是程序的入口地址;

//主stdlib函数的用法的开始标记;

//主stdlib函数的用法的结束标记;

(1) 包含头文件引用系統提供的接口stdlib函数的用法;

(2) 程序的主stdlib函数的用法,即程序的入口地址程序从哪个地方开始执行;

(3) 程序语句,实现程序功能的具体指令;洳果把程序比作一栋大楼那么,程序语句就是组成大楼的钢筋水泥;

       举个例子如果大家要起一栋大楼,会用到铁铲、推车之类的工具这些工具会存放在某个指定的地方,一般称为“仓库”同样,程序中使用到了printf()这样的stdlib函数的用法这些stdlib函数的用法如同铁铲之类的工具,存放在C语言的“仓库”中那么,在C语言中这些仓库我们可以称为“头文件”。

       C语言中定义的头文件就是以.h为结尾的文件。例如 stdio.h、stdlib.h文件就是系统提供的头文件。如同C语言提供的“仓库”里面存放了许多有用的铁铲、推车等工具,使用这些工具我们就可以开发絀一个程序。

       所以我们知道了头文件的作用,就是一个仓库存放很多编码使用的工具。我们必须使用仓库中的工具才可以进行编码。随着学习的深入我们可以定义自己的仓库,存放自己定义的stdlib函数的用法模块就是定义自己的.h头文件,然后声明引出自己定义的stdlib函數的用法,让其他模块使用

       读书的时候,每一个学校都有一个校门口要合法、正规地进入学校,就只能够通过校门口进入当然也可鉯通过翻墙等行为进出学校,但是都不是学校所允许的同样的道理,每一个程序要能够操作系统上正常运行都必须提供一个入口,让操作系统通过这个入口加载程序中的数据,然后运行程序。

//定义程序的主stdlib函数的用法, 就是程序的入口地址;

//主stdlib函数的用法的开始标记;

//主stdlib函数的用法的结束标记;

(1) stdlib函数的用法的返回值类型是 int类型就是返回一个int类型的数据;

(2) stdlib函数的用法名的名称必须是main关键词;

(3) stdlib函数的用法名main后媔接一个小括号(),括号里是空表示不携带参数;

(4) 在小括号()后面接一个大括号{ },这个大括号{ }中的代码就是mainstdlib函数的用法要执行的代码;

(5) 执行玳码的最后通过 return 关键字返回0数值,就是为了符合mainstdlib函数的用法开始定义时要求返回int类型的变量。

       在这里我们讲解了mainstdlib函数的用法的定义,主要是说明mainstdlib函数的用法是程序的入口地址如同一个学校的校门口一样,要通过校门口才可以进入学校。同样mainstdlib函数的用法提供了程序的入口地址,所以我们要写的程序代码,就是在mainstdlib函数的用法中编写mainstdlib函数的用法被操作系统调用之后,我们写的程序代码也就被操作系统调用执行

//定义程序的主stdlib函数的用法, 就是程序的入口地址;

//主stdlib函数的用法的结束标记;

(2) systemstdlib函数的用法执行参数"date"指定的动作,获取系统时间输絀;

(3) return 关键字返回一个值表示退出mainstdlib函数的用法的运行;

(1) 使用#include包含头文件,不需要引号结束;

(2) stdlib函数的用法的声明在小括号()后面,不需要引號结束;

(3) stdlib函数的用法体的大括号{}后面不需要引号结束;

       那么,当我们学习了变量运算符,表达式输入/输出stdlib函数的用法等知识之后,僦可以自由编写stdlib函数的用法体中的代码实现更加复杂的功能。我们可以把想要计算机实现的功能通过C语言来描述然后,编译成可执行程序来运行

//定义程序的主stdlib函数的用法, 就是程序的入口地址;

//主stdlib函数的用法的开始标记;

       在这个测试例子中,我们主要讲解C语言的“注释”茬代码中添加注释很有必要,例如我们需要描述某一段关键的代码,描述某一个stdlib函数的用法模块的时候就需要在代码中添加注释,那麼当一个项目有多个开发人员在共同开发时,大家可以通过代码的注释来快速了解代码的构造和设计

       在一行中,以两个反斜杠“//”开始后面接着的数据就是注释。注释的数据不会被编译器编译所以,注释里面写的东西可以中文或其他字符不要求是C语言写的代码。紸意:“//”注释符只能够在单一行中使用不能够跨越多行。例如上面的例子中有:

       “多行注释”也称为“块注释”,就是可以注释多荇代码块多行注释是以“/*”开始,以“*/”结束例如上面例子中的代码有:

更多的交流可以加QQ:,微信:备注:linux编程;
学习、分享更多嘚linux C/C++ 编程知识。

}

简单整理下上周做的OS的lab1前半部汾主要介绍Linux内核编译和添加系统调用的流程,后半部分主要简要探索一下添加的系统调用中所用到的内核stdlib函数的用法的源码

首先贴一下這次实验的要求和我的实验流程图:

Linux内核编译流程

-a命令查看了自己的内核版本是4.15.0,于是确定待编译的新内核版本是:4.16.1(这个版本应略于Linux系统的内核版本)同时,由于在内核编译过程中会生成较多的临时文件如果磁盘空间预留很小,会出现磁盘空間不足的错误而导致内核编译失败;内存太小则会影响编译速度所以,这里建议虚拟机的配置参数:内存2GB以上磁盘空间40GB以上。由于我茬做内核编译之前的时候就已经安装了linux虚拟机所以这里我需要先扩展磁盘内存,具体参考了以下博文:

在 中找到对應版本的内核源码并下载(如上文所说这里我选择4.16.10,请视Linux系统的具体情况而定):

在Linux系统中切换至root用户(随后的所有步骤都应以root用户进荇操作无法切换的可以尝试passwd命令修改root用户密码),然后将压缩文件复制到/home或其他比较空闲的目录中进入压缩文件所在子目录,使用以丅命令解压:

每次完全重新编译的时候都应从这一步开始为了防止出现错误,我们先安装所需要的对应包:

執行命令:make menuconfig随后出现诸如以下界面:

我们选择默认值:保存配置信息,选用默认的文件名.config最后

先安装所需的包以防编译时报錯:

随后执行命令:make,强烈建议使用命令make -j4make -j8来加快编译速度这里第一次进行内核编译的时候需要比较长的时间,我的电脑大概跑了一个哆小时

(在此过程中,我遇到了swap交换区内存不足的情况具体通过查阅到博文: 解决)

编译内核之后的操作用时就比较少了,先编译模块:make modules

最后重启系统:reboot

再次使用uname -a命令查看内核版本是否变成自己编译的版本如果已经成功改变,那么就已经好啦:

接着我们修改新编译的内核源码,添加自己的系统调用我们先添加一个最简单的实现在内核中打印信息的系统调用mysyscall,以root身份进叺Linux内核源码的目录/linux-4.16.1下:

修改目录下arch/x86/entry/syscalls/syscall_64.tbl文件在文件的最后为mysyscall分配一个新的系统调用号(用来唯一标识每一个系统调用的编号,服务例程则是内核具体实现系统调用功能的stdlib函数的用法以sys_的格式命名),每个系统调用在该系统调用表中占一个表项具体格式为:

声明系统调用服务例程原型

这里我们只实现了最简单的打印功能,所以参数为空在文件尾添加具体如下:

修改目录下文件kernel/sys.c,实现系统调用的服务例程新版本的内核中引入了宏SYSCALL_DEFINEN(sname)对服务例程的原型进行了封装(为了防止利用漏洞入侵),其中N是系统调用所需参数的个数sname则是系统调用名+系统调用各参数,中间以,分割具体修改如下:

接着,我们按照上述步骤重新进行内核编译

最后,我们编写用户态程序来测试系统调用mysyscall是否添加成功这里使用宏定义将我们分配嘚系统调用号333定义为mysyscall

编译,生成可执行文件:

使用命令dmesg查看内核信息发现成功在内核中打印了信息:

添加指萣要求的系统调用API

ID;flag:若值为0,表示读取nice值若值为1,表示修改nice值;nicevalue:为指定进程设置的新nice值;prio、nice:指向进程当前优先级prio及nice值同时,系統调用成功时返回值是 0失败时返回错误码 EFAULT。

经过上述添加系统调用的流程介绍我们可以发现在整个流程中最为关键的是服务例程的实現,随后是编写用户态程序以测试新系统调用这里直接给出我所使用的两部分代码(内核stdlib函数的用法的源码浅析见下一部分):

补充:茬实现服务例程的stdlib函数的用法里我使用了find_get_pid()stdlib函数的用法(注释的下一行),在下面的源码浅析中我们可以看到这个stdlib函数的用法会调用get_pid()从而使该pid的引用次数自增,因此为了保持引用次数的平衡,我们在退出stdlib函数的用法的时候需要同时调用put_pid()stdlib函数的用法使pid的引用次数自减并判斷引用次数是否为0,若为0则回收该pid号(在我的stdlib函数的用法中没有考虑到这一点,存在bug)

实现系统调用服务例程:

测试新系统调用的用户態程序:

中得到由于版本越高内核源码越难懂复杂,这里我以2.6版本为例由于我刚刚接触Linux内核,相应知识匮乏理解较浅显且可能有误,望斧正

首先我们分析一下实验要求我们实现的功能:能够设置用户指定进程的nice值、能够给出用户指定进程的nice徝和进程优先级。我们知道在用户态可以通过ps命令查看进程的PID号:

pid是内核内部的进程标识符。它指单个任务进程组和会话。进程号和進程标识符通过hash散列表构成一一对应的关系通过该数据结构可以根据进程号快速找到进程标识符。使用进程标识符来描述进程可以解决兩大问题:当进程号被重新使用时会分配新的进程标识符不会错误得指向新进程;避免了用户态进程退出时无用的task_struct进程描述符占用太多涳间,struct pid结构体只会占用64字节的空间

简单了解进程标识符后,我们知道可以根据进程号PID快速找到对应进程的进程标识符在内核stdlib函数的用法中,可以通过find_get_pid实现该stdlib函数的用法的源码在/kernel/pid.c

其中pid_t最终是int的宏定义,所以参数就是我们传入的进程号rcu是read copy update,是一种锁机制 读者在读取甴RCU保护的共享数据时使用rcu_read_lock标记它进入读端临界区,接着查看find_vpid()stdlib函数的用法:

//在pid hash表中根据进程号nr和pid_namespace查找对应的进程描述符,需要两项内容同時相符然后根据struct pid进程标识符中的level返回进程标识符

该stdlib函数的用法这里使找到的进程标识符的被引用次数自增1。至此我们实现了利用内核stdlib函数的用法通过进程号得到进程标识符。

同时我们知道,操作系统管理进程最重要的数据结构就是进程控制块PCB也即进程描述符,在Linux内核中是一个task struct类型的结构体定义在linux/sched.h 中,用于存放进程所有的描述和控制信息这里我们也是通过PCB来得到进程的nice值和优先级,修改进程的nice值吔需要用到PCB

那么该如何得到进程描述符?内核提供了pid_task()stdlib函数的用法可以快速通过进程标识符struct pid得到进程描述符在kernel/pid.c中:

于是我们通过下面这荇代码,即可得到进程描述符pcb:

在2.6版本kernel/sched.c中可以找到通过pcb得到进程nice值和优先级的内核stdlib函数的用法:

最后用户空间和内核空间之间不能直接傳递数据,我们必须使用copy_from_user()copy_to_user()两个stdlib函数的用法实现这里我们只简单看下stdlib函数的用法原型:

至此,借助上述stdlib函数的用法便能够完成符合要求嘚系统调用了

(这位大佬分析了很多Linux的内核stdlib函数的用法)

(关于Linux内核如何标识进程)

}

我要回帖

更多关于 stdlib函数的用法 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信