现在的位置: 首页 > 综合 > 正文

Linux进程编程基本概念

2013年10月19日 ⁄ 综合 ⁄ 共 6053字 ⁄ 字号 评论关闭
 

1.1 登录

1.   用户登录名

登录Linux系统时,需先键入用户登录名,然后键入用户密码,系统通过/etc/passwd(口令文件)文件校验用户登录名和用户密码。口令文件中的登录项由7个以冒号分隔的字段组成,分别为登录名、加密口令、数字用户ID(224)、数字组ID(20)、注释字段、起始目录(/home/stevens)、以及Shell程序(/bin/sh)。

2.   登录Shell

登录后,系统先显示一些典型的系统信息,然后就可以向Shell程序键入命令。Shell是一个命令行解释器,它读取用户输入,然后执行命令,用户通常用终端,有时则通过Shell脚本文件向Shell进行输入。常用的 Shell有:

Ÿ Bourne Shell, /bin/sh

Ÿ C Shell, /bin/csh

Ÿ Korn Shell, /bin/ksh

系统从口令文件中登录项的最后一个字段确定应该执行哪一种Shell。

Bourne Shell是其最早版本,也是最流行的,C Shell和Korn Shell是其后继开发和升级的。三种Shell语法类似,功能基本相同。

 

1.2 文件和目录

1.   文件系统

Linux文件系统是一种树形层次结构,包括目录和文件,目录的起点称为根(root),其名字是一个字符/。

目录(directory)是一个包含目录项的文件,在逻辑上,可以认为每个目录项都包含一个文件名,同时还包含说明该文件属性的信息。文件属性有文件类型、文件长度、文件所有者、文件的许可权、文件最后的修改时间等,可用stat和fstat函数返回一个包含所有文件属性的信息结构。

2.   文件名

目录中的各个名字称为文件名,斜线 (/)不能出现在文件名中,斜线分隔是构成路径名。当创建一个新目录时,系统自动创建了两个文件名,分别为.(称为点 )和..(称为点-点),点引用当前目录,点-点则引用父目录,在最高层次的根目录中,点-点与点相同。

3.   起始目录

登录时,工作目录设置为起始目录(home directory),该起始目录从口令文件中的登录项中取得。起始目录是创建用户时创建的,如test的起始目录为/home/test。起始目录又称为主目录,用“~”可表示该用户的主目录。

4.   路径名

0个或多个以斜线分隔的文件名序列构成路径名(pathname),以斜线开头的路径名称为绝对路径名(absolute pathname ),否则称为相对路径名(relative pathname)。如当前目录为/home/test/hello,cd  /home/test/hello/why是使用绝对路径方式到达该目录,cd  ./why是使用相对路径到达该目录,cd  ~/hello/why是通过主目录到达该目录。

5.   工作目录

每个进程都有一个工作目录(working directory),有时称为当前工作目录(current working directory)。所有相对路径名都从工作目录开始解释,进程可以用chdir函数更改其工作目录。

登录一个用户时就有一个工作目录,用cd命令可以改变工作目录,如登录test用户后敲入cd hello,其工作目录变为/home/test/hello。

 

1.3 输入和输出

1.   文件描述符

文件描述符是一个非负整数,内核用来标识一个特定进程正在访问的文件。当内核打开一个现存文件或创建一个新文件时,它就返回一个文件描述符。

每个进程在Linux内核中都有一个task_struct结构体来维护进程相关的信息,称为进程描述符(Process Descriptor),又称为进程控制块(PCB,Process  Control Block)。task_struct中有一个指针指向files_struct结构体,称为文件描述符表,其中每个表项包含一个指向已打开文件的指针,如下图12-1所示。

进程控制块(task_struct)指向文件指针的顺序为task_struct-> files(file_struct) ->fd数组,fd数组大小决定了进程打开的最大文件个数。

图12-1 文件描述符图

用户程序不能直接访问内核中的文件描述符表,而只能使用文件描述符表的索引(即0、1、2、3这些数字),这些索引就称为文件描述符(File  Descriptor),int型变量保存。当调用open打开一个文件或创建一个新文件时,内核分配一个文件描述符并返回给用户程序,该文件描述符表项中的指针指向新打开的文件。当读写文件时,用户程序把文件描述符传给readwrite,内核根据文件描述符找到相应的表项,再通过表项中的指针找到相应的文件。

2.   标准输入、标准输出和标准出错

每当运行一个新程序(进程,包括Shell程序产生的进程)时,进程自动打开三个文件描述符,即标准输入、标准输出以及标准出错。Shell中0表示标准输入(stdin),1表示标准输出(stdout),2表示标准错误(stderr)。

标准输入对应键盘,标准输出对应屏幕,标准错误同样对应屏幕。

Shell都提供一种方法,使任何一个或所有这三个描述符都能重新定向到某一个文件。例如:ls>file.list,就是将标准输出重新定向到名为file.list的文件上。

3.   不用缓存的I/O

函数open、read、write、lseek以及close提供了不用缓存的I/O,这些函数都是用文件描述符进行工作。不用缓存的I/O函数一次读写操作完成一次系统调用,当初级文件I/O写函数write所带的写大小参数太小时,会引起系统调用次数过多而造成系统效率低下。

4.   标准I/O

标准I/O函数读写时无需关心缓存大小,操作系统对标准I/O函数自动分配缓存、使用缓存和管理缓存。使用标准I/O可无需担心如何选取最佳的缓存长度,例如fread、fwrit、fprintf、fscanf、fgets等都是标准I/O,标准I/O是不用缓存的I/O的发展,这些函数默认都使用了缓存。

1.4程序与进程

1.   程序

可执行程序是存放在磁盘文件中的可执行文件,可使用6个exec函数中的一个由内核将程序读入内存,并使其执行。

2.   进程和进程 ID

程序的执行实例被称为进程(process)。进程空间(包括代码段空间、数据区空间、运行的堆栈空间)存在于内存中。CPU执行的是进程代码段的代码,进程执行时间就是CPU执行该进程代码的时间。程序是静态的,存放在硬盘上,是永久的;而进程是动态的,是暂时的,有其生命周期,当程序加载到内存时,操作系统为其分配好其进程空间时,从main函数开始运行时,标志着该进程生命的开始,当调用exit函数退出时标志着该进程生命的结束,随后操作系统回收进程空间和所占资源。

每个Linux进程都一定有一个唯一的数字标识符,称为进程ID(process ID)。进程ID总是一非负整数,getpid函数可得到本进程的进程ID号。

下面是getpid的函数范例,getpid.c源代码如下:

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

int main()

{

    printf("pid=%d\n", getpid()) ;

    printf("parent pid=%d\n", getppid()) ;

    return 0 ;

}

编译 gcc getpid.c -o getpid。

执行./getpid,执行结果如下:

pid=6307

parent pid=5441

使用 ps  -ef|grep 5441,得到如下结果:

zjkf      5441  5438  0 06:20 pts/0    00:00:00 -bash

其中getpid.c就是源程序,getpid执行码就是编译后的可执行程序,./getpid执行码执行的过程叫做进程。可见,不管源程序,还是编译后的可执行程序,都是静态的,而进程是动态的。getpid进程的父进程为Shell(bash)进程,Shell进程是所有在终端上执行进程的父进程。

3.   进程控制

用于进程控制的主要函数有fork、exec(exec函数有六种变体,但经常把它们统称为exec函数)和waitpid。

 

1.5 ANSI C

American National Standards Institute(ANSI——美国国家标准学会)是由公司、政府和其他成员组成的自愿组织。它们协商与标准有关的活动,审议美国国家标准,并努力提高美国在国际标准化组织中的地位。

由于美国在计算机早期发展中一直处于领先地位,因此ANSI的很多标准已经成为事实上的国际标准。其中常见的ANSI ASCII字符编码几乎为所有的编码方式所兼容。

标准的力量无处不在,标准是一种约定和约束,提供了技术目标说明与技术规范,标准的导向性和强制性加快了技术交流和提高了技术质量。

1.   ANSI C

符合ANSI标准的C语言称为ANSI C,现在的C语言程序一般都符合ANSI C标准。

2.   ANSI  C函数原型

标准库函数的函数原型都在头文件中提供,程序可以用#include指令包含这些原型文件。对于用户自定义函数,程序员需要在头文件或文件头声明其原型。对于返回int型和void型且不带形参的C语言函数可以不声明其函数原型,但不鼓励这么用。

函数原型描述了函数到编译器的接口,编译程序在编译时就可以检查在调用函数时是否使用了正确的参数。

如getpid函数原型如下,其中pid_t是一个typedef定义类型,其定义为typedef  int pid_t。

pid_t   getpid ( void ) ;

调用带参数的getpid(如getpid(1)),则ANSI C程序将给出下列形式的出错信息:

line   8: too  many arguments to  function  " getpid "

另外,因为编译程序知道参数的数据类型,所以如果可能,它就会将参数强制转换成所需的数据类型。

3.   ANSI  C类属指针

在标准ANSI中,read和write的第二个参数现在是void  *类型,而早期的 Unix(Linux是Unix的后继者,站在Unix的肩膀上)系统都使用char *这种指针类型。

ANSI C使用void *作为类属指针来代替char *,这样函数原型和类属指针的组合消去了很多非ANSI C程序需要的显式类型强制转换。

例如,下面代码使用ANSI C标准,可以写成:

float data[100];

write(fd,data,sizeof(data)) ;

若使用非ANSI C编译程序,则需写成:

write( fd , (void  *)data , sizeof(data)) ;

 

1.6 用户标识

1.   用户ID

在Linux系统,每个用户有一个唯一的用户ID号。

口令文件登录项中的用户ID(user ID)是个数值,它向系统标识各个不同的用户。系统管理员在确定一个用户登录名的同时,确定其用户ID,用户不能更改其用户ID,通常每个用户有一个唯一的用户ID。

用户ID为0的用户为根 (root)或超级用户 (superuser)。在口令文件中,通常有一个登录项,其登录名为root,root用户属于特权用户。如果一个进程具有超级用户特权,则大多数文件许可权检查都不再进行。某些操作系统功能只限于向超级用户提供,超级用户对系统有自由的支配权。

2.   组ID

在Linux系统,每个用户有一个唯一的组ID号。

口令文件登录项也包括用户的组 ID(group ID),它也是一个数值。组 ID也是由系统管理员在确定用户登录名时分配的。

组文件将组名映射为数字组 ID,它通常是/etc/group。

一个用户可以属于多个组,但只能属于一个主组,非主组又称为添加组或附属组。

getuid.c源代码如下:

#include <stdio.h>

#include <unistd.h>

#include <sys/types.h>

int main()

{

    printf("uid=%d, gid=%d\n", getuid(), getgid()) ;

    return 0 ;

}

编译 gcc getuid.c -o getuid。

执行./getuid,执行结果如下:

uid=1008, gid=1003

 

1.7 出错处理

当Linux函数出错时,经常会给整型变量errno设置一个值,error中每个值都表示特定的含义。

文件<errno.h>中定义了变量errno以及可以赋予它的各种常数宏定义,这些常数都以E开头,如errno等于常数EACCES时,表示“此进程没有打开该文件的权限”。

对于errno有两条规则:第一条规则是如果没有出错,则error的值不会被重设;因此,仅当函数的返回值指明出错时,才检验其值。第二条是任一函数都不会将errno值设置为0,在<errno.h>中定义的所有常数都不为0。

C标准定义了两个函数,即strerror和perror,它们帮助打印出错信息,这两个函数的函数原型如下:

# include   <string.h>

char   *strerror(int  errnum);

此函数将errnum(它通常就是errno值)映射为一个出错信息字符串,并且返回此字符串的指针。

perror函数在标准出错上产生一条出错消息(基于errno当前值),然后返回。

# include   <stdio.h>

void   perror( const   char  *msg);

它首先输出由msg指向的字符串,然后是一个冒号、一个空格、然后是对应errno值的出错信息,最后是一个换行符。

 

下面以两个范例来说明strerror和perror的使用。

strerror.c源代码如下,此代码打印出0到131的错误原因描述。

#include <stdio.h>

#include <string.h>

int main()

{

    int i;

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

    {

        printf("%d : %s\n", i, strerror(i));

    }

    return 0 ;

}

编译 gcc strerror.c -o strerror。

执行./strerror,执行结果如下:

0 : Success

1 : Operation not permitted

2 : No such file or directory

……

 

perror.c源代码如下,此函数打印出错误原因信息字符串。

#include <stdio.h>

int main()

{

    FILE *fp;

    fp = fopen("/tmp/noexist", "r+") ;

    if ( fp == NULL )

    {

        perror("fopen") ;

        return -1 ;

    }

    return 0 ;

}

编译 gcc peror.c –o perror。

执行./perror,执行结果如下:

fopen: No such file or directory

 

           摘录自《深入浅出Linux工具与编程》

       

抱歉!评论已关闭.