C语⾔编程常见问题解答之系统调⽤
C语⾔编程常见问题解答之系统调⽤
PC中最主要的难题之⼀,也是最容易引起误解的,就是系统调⽤。系统调⽤所代表的那些函数实际上是计算机的所有底层操作——屏幕和磁盘的控制,键盘和⿏标的控制,⽂件系统的管理,时间,打印,这些只不过是系统调⽤所实现的⼀部分功能。
总的来说,系统调⽤往往涉及到BIOS(基本输⼊输出系统)。实际中有好⼏种不同的BIOS,例如主板的BIOS负责初始硬件检测和系统引导,VGA BIOS(如果有VGA卡的话)处理所有的屏幕处理函数,固定磁盘BIOS管理硬盘驱动器,等等。DOS是位于这些低级BIOS之上的⼀个软件层,并且提供了进⼊这些低级BIOS的基本接⼝。⼀般说来,这意味着有⼀个DOS系统调⽤可以调⽤⼏乎所有你想使⽤的系统功能。实际上,DOS将调⽤相应的⼀种低级BIOS来完成所要求的任务。在本章中,你将会发现你既可以调⽤DOS来完成⼀项任务,也可以直接调⽤低级BIOS来完成相同的任务。
14.1 怎样检索环境变量(environment variables)的值?
ANSI C标准提供了⼀个名为getenv()的函数来完成这项任务。getenv()函数很简单⼀把指向要查的环境串的指针传递给它,它就返回⼀个指向该变量值的指针。下⾯的程序说明了如何从C中获得环境变量PATH的值: # include
main(int argc, char * * argv) {
char envValue[l29]; / * buffer to store PA TH * /
char * envPtr = envValue / * pointer to this buffer * /
envPtr = getenv("PA TH"); /* get the PATH */
printf ("PATH= %s\n" , envPtr) / * print the PATH * / }
如果你编译并运⾏了这个程序,你就会看到与在DOS提⽰符下输⼊PA TH命令完全相同的结果。事实上,你可以⽤getenv()检索AUTOEXEC.BA T⽂件中的或者系统引导后在DOS 揭⽰符下输⼊的所有环境变量的值。
这⾥有⼀个⼩技巧。当运⾏Windows时,Windows设置了⼀个名为WINDIR的新的环境变量,它包含了Windows⽬录的路径全名。下⾯这段简单的程序⽤来检索这个串: # include
main(int argc, char * * argv) {
char envValue[l29];
char * envPtr = envValue
envPtr = getenv("windir");
/ * print the Windows directory * /
printf("The Windows Directory is %s\n" , envPtr); }
这个程序还可以⽤来判断当前是否正在运⾏Windows,以及DOS程序是否运⾏在⼀个DOS shell下,⽽不是运⾏在“真正的"DOS下。注意,程序中的windir字符串是⼩写——这⼀点很重要,因为它对⼤⼩写是敏感的。如果你使⽤
WINDIR,getenv()就会返回⼀个NULL 串(表⽰变量未到错误)。
⽤⼀putenv()函数也可以设置环境变量。但要注意,该函数不是⼀个ANSI标准函数,在某些编译程序中它可能不以这个名字出现,或者根本就不存在。你可以⽤⼀putenv()函数做许多事情。实际上,在上⾯那个例⼦中,Windows正是⽤这个函数创建了windir环境变量。请参:
14.2 怎样在程序中调⽤DOS函数? 14.3 怎样在程序中调⽤BIOS函数?
14.2 怎样在程序中调⽤DOS函数?
其实,当调⽤printf(),fopen(),fclose(),名字以⼀dos开始的函数以及很多其它函数时,都将调⽤DOS函数。Microsoft和Borland还提供了⼀对名为int86()和int86x()的函数,使你
函数,⽽不是getch()和printf()函数,从键盘上得到⼀个字符并将其打印出来(该程序需要在⼤存储模式下编译)。
# include
char GetAKey(void); void OutputString(char * );
main(int argc, char * * argv) {
char str[l28];
union REGS regs;
int ch;
/ * copy argument string; if none, use "Hello World" * /
strcpy(str, (argv[1]== NULL ? "Hello World": argv[1])),
while ((ch = GetAKey()) ! =27){
OutputString(str); }
}
char GetAKeyO {
union REGS regs;
regs.h. ah = 1; /* function 1 is "get keyboard character" * /
int86(0x21, ®s, ®s);
return( (char)regs. h. al) }
void
OutputString(char * string)
{
union REGS regs;
struct SREGS segregs;
/ * terminate string for DOS function * /
* (string + strlen(string)) = '$';
regs.h. ah = 9; / * function 9 is "print a string" * /
< dx = FP_OFF(string)
segregs. ds= FP_SEG(string)
int86x(0x21, ®s, ®s, &segregs); }
上例创建了两个函数来代替getch()和printf(),它们是GetAKey()和OutputString()。实际上,函数GetAKey()与标准c函数getche()更为相似,因为它与getche()⼀样,都把键⼊的字符打印在屏幕上。这两个函数中分别通过int86()(在GetAKey()中)和int86x()(在OutputString()中)调⽤DOS函数来完成所要求的任务。
可供函数int86()和int86x()调⽤的DOS函数实在太多了。尽管你会发现其中许多函数的功能已经被标准的C函数覆盖了,但你也会发现还有许多函数没有被覆盖。DOS也包含⼀些未公开的函数,它们既有趣⼜有⽤。DOS忙标志(DOS Busy Flag)就是⼀个很好的例⼦,它也被称作InDos标志。DOS函数34H返回指向⼀个系统内存位置的指针,该位置包含了DOS 忙标志。当DOS正忙于做某些重要的事情并且不希望被调⽤(甚⾄不希望被它⾃⼰调⽤)时,该标志就被置为1;当DOS不忙时,该标志将被清除(被置为O)。该标志的作⽤是当DOS 正在执⾏重要的代码时,把这⼀情况通知DOS。然⽽,该标志对程序员也是很有⽤的,因为他们能由此知道什么时候DOS处于忙状态。尽管从DOS 2.0版开始就有这个函数了,但因为Microsoft最近已经公开了这个函数,所以从技术⾓度上讲它已不再是⼀个未公开的函数。有⼏本很不错的书介绍了已公开和未公开的DOS函数,对这个问题有兴趣的读者可
以去阅读这些书。
14.3 怎样在程序中调⽤BIOS函数?
14.3 怎样在程序中调⽤BIOS函数?
与前⽂中的例⼦⼀样,在使⽤象⼀setvideomode()这样的函数时,将频繁地调⽤BIOS 函数。此外,就连前⽂例⼦中使⽤过的DOS函数(INT 21H,AH=01H和INT 21H,AH=09H)最终也要通过调⽤BIOS来完成它们的任务。在这种情况下,DOS只是简单地把你的DOS 请求传给相应的低级BIOS函数。下⾯的例
⼦很好地说明了这⼀事实,该例与前⽂中的例⼦完成相同的任务,只不过它完全跳过了DOS,直接调⽤了BIOS。
# include
# include
char GetAKey(void)
void OutputString( char * );
main(int argc, char * * argv) {
char str[128];
union REGS regs;
int ch;
/ * copy argument string; if none, use "Hello World" * /
strcpy(str, (argv[1] == NULL ? "Hello World" : argv[1]));
while ((ch = GetAKeyO) !=27){
OutputString(str); }
}
char GetAKey() {
union REGS regs;
regs. h. ah = 0; /* get character */
int86(0xl6, &xegs, ®s);
return( (char)regs. h. al) }
void
OutputString(char * string) {
union REGS regs;
regs. h. ah = 0x0E; /* print character * /
regs. h. bh = 0;
/ * loop, printing all characters * /
for(; * string !='\0'; string+ + ){
c语言编程软件是系统软件吗regs. h. al= * string; int86(0xl0, ®s, ®s); }
}
你可以发现,唯⼀的变化发⽣在GetAKey()和OutputString()⾃⾝之中。函数GetAKey()跳过了DOS,直接调⽤键盘BIOS来获得字符(注意,在本例这个调⽤中,键⼊的字符并不在屏幕上显⽰,这⼀点与前⽂中的例⼦不同);函数OutputString()跳过了DOS,直接调⽤了Video BIOS来打印字符串。注意本例效率不⾼的⼀⾯——打印字符串的C代码必须位于⼀个循环中,每次只能打印⼀个字符。尽管Vidoeo BIOS⽀持打印字符串的函数,但C⽆法存取创建对该函数的调⽤所需的所有寄存器,因此不
请参见:
14.2 怎样在程序中调⽤DOS函数?
14.4 怎样在程序中存取重要的DOS内存位置?
与DOS和BIOS函数⼀样,有很多内存位置也包含了计算机的⼀些有⽤和有趣的信息。你想不使⽤中断就知道当前显⽰模式吗?该信息存储在40:49H(段地址为40H,偏移量为49H)中。你想知道⽤户当前是否按下了Shift,Ctrl或Alt键吗?该信息存储在40:17H中。你想直接写屏吗?单⾊显⽰(Monochrome)模式的视频缓冲区起始地址为B800:O,彩⾊⽂本模式和16⾊图形模式(低于640×480 16⾊)的视频缓冲区起始地址为B8000:0,其余标准图形模式(等于或⾼于640×480 16⾊)的视频缓冲区起始地址为A000:O,详见14.8。下⾯的例⼦说明了如何把彩⾊⽂本模式的字符打印到屏幕上,注意它只是对前⽂中的例⼦做了⼀点⼩⼩的修改。
# include
# include
char GetAKey(void)
void OutputString(int, int, unsigned int, char * );
main (int argc, char * * argv) {
char str[l28];
union REGS regs;
int ch, tmp;
/ * copy argument string; if none, use "Hello World" * /
strcpy(str, (argv[1] == NULL ? "Hello World" : argv[1]));
/ * print the string in red at top of screen * /
for(tmp = 0;((ch = GetAKeyO) ! = 27);
tmp+=strlen(str)) {
outputString(0, tmp, 0x400,str);
}
}
char GetAKey() {
union REGS regs;
regs. h. ah = 0;
/ * get character * /
int86(0xl6, ®s, ®s);
return((char)regs. h. al); } void
OutputString(int row, int col, unsigned int video Attribute, char * outStr) {
unsigned short far * videoPtr;
videoPtr= (unsigned short far * ) (0xB800L <<16);
videoPtr + = (row * 80) + col; /* Move videoPtr to cursor position * /
videlAttribute & = 0xFF00;
/ * print string to RAM * /
while ( * outStr ! = '\0'){
/ * If newline was sent, move pointer to next line, column 0 * /
if( (* outStr == '\n') || (*outStr == 'V') ){
videoPtr + = (80- (((int)FP-OFF(videoPtr)/2) % 80));
outStr+ + continue; }
/ * If backspace was requested, go back one * /
if( *outStr = = 8){ videoPtr -- outStr++ continue; }
/* If BELL was requested, don't beep, just print a blank and go on * /
if ( * outStr = = 7) { videoPtr+ +
outStr++ continue }
/ * If TAB was requested, give it eight spaces * /
if ( * outStr == 9){
* videoPtr++ = video Attribute | ' '
* videoPtr++ = video Attribute | ' '
* videoPtr++ = video Attribute | ' '
* videoPtr++ = video Attribute | ' '
* videoPtr++ = video Attribute | ' '
* videoPtr++ = video Attribute | ' '
* videoPtr++ = video Attribute | ' '
* videoPtr++ = video Attribute | ' '
outStr+ + continue; }
/ * If it was a regular character, print it * /
* videoPtr = videoAttribute | (unsigned char) * outStr;
videoPtr+ +
outStr + + }
return; }
显然,当你⾃⼰来完成把⽂本字符打印到屏幕上这项⼯作时,它是有些复杂的。笔者甚⾄已经对上例做了⼀些简化,即忽略了BELL字符和⼀些其它特殊字符的含义(但笔者还是实现了回车符和换⾏符)。不管怎样,这个程序所完成的任务与前⽂中的例⼦基本上是相同的,只不过现在打印时你要控制字符的颜⾊和位置。这个程序是从屏幕的顶端开始打印的。如果你想看更多的使⽤内存位置的例⼦,可以阅读20.12和20.17——其中的例⼦都使⽤了指向DOS内存的指针来查关于计算机的⼀些有⽤信息。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论