UNIX 编程资料(1)

发表于:2007-05-25来源:作者:点击数: 标签:编程Unix资料
整理了一篇UNIX编程的文章,拿出来和大家共享 UNIX 编程资料 第一章 概述 1.1UNIX的版本 UNIX操作系统是贝尔实验室于六十年代末用C语言研制 开发 的。经过几十年的发展,已经成为流行于从大型机、小型机到工作站甚至微机等多种平台的操作系统。UNIX的成功同

整理了一篇UNIX编程的文章,拿出来和大家共享

 UNIX 编程资料

第一章 概述
1.1UNIX的版本
UNIX操作系统是贝尔实验室于六十年代末用C语言研制开发的。经过几十年的发展,已经成为流行于从大型机、小型机到工作站甚至微机等多种平台的操作系统。UNIX的成功同时也推动了C语言的普及。本教材的目的是讲解UNIX系统下的C程序设计,使C程序员快速掌握UNIX系统下的编程开发。作者在进行UNIX编程开发的实践过程中,深感实例的重要性-一个简短的C语言实例往往胜过长篇累牍的文字说明,当然了,文字说明也是必不可少的。本教材将本着实例优先的原则,使您能够对UNIX编程开发快速入门。

UNIX的版本不统一是出了名的,从UNIX的发展历史来看,主要有两大流派:AT&T的UNIX系统V版本和加州大学伯克利分校的BSD版本,在此基础上,各家UNIX厂商均开发了各自的UNIX操作系统。如:工作站厂商中有HP的hpux、SUN的solaris、SGI的irix、IBM的AIX等,小型机有VAX上的Ultrix,微机上有SCO
UNIX、微软的Xenix以及随着Inte.net而风靡全球的Linux等。由于Windows
NT的异军突起,对UNIX的市场形成巨大的威胁,各大UNIX厂商不得不联合起来,在工作站市场上,统一以系统V版作为标准,加入BSD版本中的一些优点,支持统一的CDE(Common
Desktop Environment)窗口环境,以与Windows NT进行对抗。

1.2 UNIX编程环境

UNIX操作系统通过Shell程序实现系统与用户的交互,在Shell提示符下,用户键入UNIX命令,即可得到操作系统的输出结果。BSD系统的常用Shell是C  Shell,缺省提示符是"%",系统V的常用Shell是Bourne Shell(现在多为Korn
Shell),缺省提示符是"$",有关Shell的编程,我们在后面的章节中进行介绍。

UNIX上的标准编译器是cc。在Shell提示符下(以C Shell为例)键入下列命令:
%cc -o hello hello.c

即将C文件hello.c编译为可执行文件hello。在编译多个文件生成一个可执行文件时,UNIX提供命令make。用户需要针对多个C文件,按照一定的格式编写一个叫做Makefile的文本文件。下面是SGI上的一个Makefile的例子:
CC = cc
CFLAGS = $(DEBUG) -cckr -I$(INC)/X11 -DSYSV
DEBUG = -g
INC = /usr/include
LDFLAGS = -lXext -lXm -lXt -lX11 -lPW -lc
OBJS = initx.o windowx.o
TGTS = showxwin

all:: $(TGTS)
showxwin: $(OBJS)
$(CC) -o $@ $(OBJS) $(CFLAGS) $(LDFLAGS)

大写字母的字串是一些宏,CC是编译器的名字、CFLAGS定义cc的编译开关、DEBUG是调试宏、INC是头文件所在目录、LDFLAGS定义了编译连接库、OBJ定义了目标文件名、TGTS定义了可执行文件名。在Shell提示符下直接键入:
%make

即可将Makefile中指定的所有C文件进行编译并生成可执行文件。

1.3 UNIX编程中的基本概念
在讨论UNIX编程开发前,首先需要阐明系统调用和库函数这两个概念。
一个系统调用指一个需要操作系统代表用户程序来执行某些任务的请求。例如:read是一个系统调用,它请求操作系统存储在一个磁盘设备(或其他设备)上的数据去填充一个缓冲区。如果任何人在他们想执行任务的时候都能随便访问设备,那么后果将是不可预测的。所以,这种服务必须请求操作系统来做,它(经常是透明地)记录所有处理每个设备的请求。
而一个库函数,并不经常需要操作系统来执行其任务。例如数学库函数中的sin(),cos()等,这些计算只需要简单地对一个有限序列求和,所以并不需要操作系统干预。
在UNIX操作系统中,有一个常用的命令man,可用来查阅命令、库函数和系统调用等的具体使用方法。传统 Unix 联机帮助手册的分节法为:
1 用户级命令(User-level commands)
2 系统调用(System calls)
3 库函数(Library functions)
4 设备及驱动程序(Devices and device drivers)
5 文件格式(File formats)
6 游戏(Games)
7 杂项(Various miscellaneous stuff - macro packages etc.)
8 系统维护及操作命令(System maintenance and operation commands)

第二章 标准输入/输出库

2.1 概述

本章介绍UNIX的标准输入/输出库,UNIX提供一些库函数完成高级输入/输出,为程序员提供了三方面的主要功能:
·自动开辟缓冲区。即使一次读或写的数据只有几个字节,库函数仍然在大到由数千个字节组成的"块"中执行实际输入或输出(缓冲区大小通常由头文件stdio.h中的常量BUFSIZ定义)。这个缓冲区在内部开辟给库函数使用,对于程序员来说是透明的;

·自动执行输入和输出转换。

·输入输出被自动格式化。以上两点在C语言的教程中一般均以讲到。

在标准输入/输出库中,一个文件被称为一串字符流,并且被一个指向类型为FILE的目标指针所描述,该指针被称为文件指针。在UNIX中文件指针stdin、stdout、stderr是预先定义好的,分别对应标准输入(键盘)、标准输出(终端屏幕)和标准错误输出。

2.2 库函数介绍

·文件创建和关闭
fopen()用于打开已存在的文件或创建新文件

·文件读写
1、 一次处理一个字符 getc(), putc()
2、 一次处理多个字符 fgets(), fputs()
3、 文件的二进制读写 fread(), fwrite()
4、 文件的格式化输入/输出 fscanf(), fprintf()
5、 字符串的格式化输入/输出 sscanf(), sprintf()

·文件移动定位
用于在文件中移动的标准输入/输出库函数是fseek(),它接收三个参数:一个文件指针指向一个打开的字符流;一个整数指明要移动的字节数,称为offset;一个整数指明从文件中什么位置移动。

第三章 低级输入/输出

3.1 概述

与第二章内容相对应,本章介绍UNIX系统中通过系统调用来实现的输入/输出,通常称之为低级输入/输出。这些系统调用能够直接实现对设备(如磁带驱动器等)的输入和输出,程序员能够决定要使用的缓冲区的大小,而不象标准输入/输出库函数那样透明设定缓冲区大小。

在标准输入/输出库中,一个文件是由一个文件指针来对应的。当使用低级界面时,则用一个文件描述字对应一个文件。文件描述字是一个小的整数。有3个事先定义的文件描述字0、1和2,分别对应标准输入、标准输出和标准错误输出。一般说来,文件描述字都是作为系统调用的第一个参数给出的。

3.2 相关系统调用介绍

·文件创建和关闭
open()用于为读写而打开一个文件,或用它来创建新文件。
int open (const char *path, int oflag, ... /* mode_t mode */);
open使用三个参数:一个字符串path包含要打开的文件名;一个整数oflag指明文件将被如何打开;整数mode在创建文件时使用。常用的oflag包括:
O_RDONLY 打开文件仅用于读。
O_WRONLY 打开文件仅用于写。
O_RDWR 打开文件用于读写。
O_CREAT 如果文件不存在,则创建,此时mode作为第三个参数给出。
close()用于关闭一个已经打开的文件。

·文件读写
read()用于读文件,格式为:
read(int fildes, void *buf, size_t nbyte);
三个参数说明如下:filedes是文件描述字;指针buf指向一个数据将被读入的缓冲区;整数nbytes指明要读的字节个数。成功时返回实际读入的字节数,出错则返回-1。
write()用于写文件,与read类似,格式为:
write(int fildes, void *buf, size_t nbyte);
三个参数说明如下:filedes是文件描述字;指针buf指向一个数据将被写入的缓冲区;整数nbytes指明要写的字节个数。成功时返回实际写入的字节数,出错则返回-1。

·文件移动定位
用于在文件中移动的低级输入/输出系统调用是lseek(),与fseek()类似,它也接收三个参数:一个文件描述字对应一个打开的文件;一个整数指明要移动的字节数,称为offset;一个整数指明从文件中什么位置移动。

·复制文件描述字
有时候有不只一个文件描述字对应一个文件。当创建子进程时(参加后面关于进程开发的章节),这一点很常用。为了获得一个新的文件描述字,并保证其与fd对应同一个文件,应调用
fd2 = dup(fd)
fd2现在和fd对应同一个文件,并且和fd一样在文件中有相同的位置。

第四章 文件与目录编程
4.1 基本概念
·文件目录概述
文件系统是UNIX对计算机技术的一大贡献!UNIX系统的文件管理十分灵活、功能强大,许多首次在UNIX系统中出现的概念被其他操作系统所采用,如MS-DOS等。

UNIX系统提供了一种层次目录方案。目录就象存放一组文件的柜子一样,目录也可以包括在其他目录中,这样就形成了一种庞大的、具有分支的组织方式,这种结构通常被称为树状结构。目录实际上也是一种特殊的文件。命令、数据文件、其他命令甚至设备(特别文件)都可以作为目录中的项(文件)。

·I标识号、I列表和I节点
一个目录是由一系列结构组成的;每个结构包含一个文件名和一个指向文件自身的指针,该指针是一个整数,称为文件的I标识号。当文件被访问时,它的I标识号用来作为索引打开一个系统表(I列表),系统中存放着文件(I节点)的实体。I节点包含了对文件的描述:
·文件自身的用户和用户组ID
·文件的保护码
·文件内容所在的物理磁盘地址
·文件的大小
·最后一次I节点改变的时间,最后一次使用和修改的时间
·连接该文件的次数,即它出现在其他目录中的次数
·一个指明文件类型的标记(目录、普通文件、特别文件)
·文件的三级保护

UNIX把使用文件的用户分成三个等级:文件所有者、同组用户和其他用户。文件所有者也称文件主,是文件的创建者,对该文件拥有所有权限;同组用户是具有相同组标识号的所有用户,文件主可以决定一个文件属于哪个组以及该组用户对文件的存取权;其他用户是指与文件主无关的用户,他们与文件主不属于同一个用户组,其他用户对一个文件的访问权限也由该文件主决定的。

一个文件的访问权限存放在该文件I节点的di_mode域中,di_mode的0-8位表示文件主、文件组用户和其他用户对该文件的存取权限。举个例子,用"ls
-l"命令可列出文件hello.c的模式和属性:

-rwxr-xr-x 1 yds user 58 9月 25日 10时54分 hello.c

最左面一栏显示了该文件的模式:文件主对该文件可读(r)、可写(w)、可执行(x),同组用户对该文件可读、可执行,其他用户对该文件可读、可执行。相应的,di_mode的0-8位为111101101(0755)。

4.2 文件编程介绍

·检查访问权限-access系统调用

access系统调用的格式为:

#include <unistd.h>
int access(const char *path, int amode);

其中:参数path指出被检查文件的路径,参数amode指出访问权限。Access判断调用进程的实际用户对文件path是否具有amode所指定的访问权限,若有相应权限,access返回0,否则返回-1。

参数amode可取以下值或它们的逻辑"或":
R_OK 检查读权限
W_OK 检查写权限
X_OK 检查执行(搜索)权限
F_OK 检查文件是否存在

例如:access("hello.c", R_OK|W_OK),
用来检查实际用户对文件hello.c是否具有读/写权;access("hello.c",F_OK) 判断文件hello.c是否存在。

·链接与删除文件-link和unlink系统调用

link和unlink系统调用的格式为:

#include <unistd.h>
int link (const char *path1, const char *path2);
int unlink(const char *path);

其中:参数path1指出已经存在的要被链接的文件路径名,path2指出要建立的链接文件。link实现path2到path1的链接,相当于给path1起了一个别名,同时文件path1的链接计数加1。若成功则返回0,否则返回-1。

参数path指出要被删除的文件路径名。Unlink删除由path指出的文件,若成功则返回0,否则返回-1。

·从I节点上获取信息-stat与fstat系统调用

stat与fstat的调用格式为:

#include <sys/types.h>
#include <sys/stat.h>
int stat(const char *path, struct stat *buf);
int fstat(int fildes, struct stat *buf);

说明:stat和fstat都用于获取文件I节点中有关状态信息。stat根据参数path给出的文件路径名,搜索它对应的盘I节点,而fstat则根据参数fildes给出的文件描述字去查找对应的I节点。这两个调用都把从I节点中获取到的信息重组后放入参数buf指向的stat结构中(stat结构的说明在文件/usr/include/sys/stat.h中)。这两个调用成功时均返回0,否则返回-1。

stat与fstat调用无论在使用上还是在功能上都是非常类似的,在参数上有一点区别。下面我们来看一个例子。

/* statfile.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

main(argc,argv)
int argc;
char *argv[];
{
int fd;
struct stat statbuf;
if (argc!=2){
printf("usage: statfile filename!\n");
exit(1);
}
if ((fd = fopen(argv[1], O_RDONLY)) == -1)
fprintf(stderr, "Cannot open %s!\n", argv[1]);
if (unlink(argv[1]) == -1)
fprintf(stderr,"Cannot unlink %s!\n", argv[1]);
if (stat(argv[1], &statbuf) == -1) /* by file name */
fprintf(stderr, "stat %s fails as it should !\n");
else
printf("stat %s succeed!\n", argv[1]);
if (fstat(fd, &statbuf) == -1) /* by file descriptor */
fprintf(stderr, "fstat %s fails!\n", argv[1]);
else
printf("fstat %s succeeds as it should!\n", argv[1]);
}

程序首先打开命令行中指定的文件,然后用unlink将该文件删除,接着分别用stat与fstat系统调用获取该文件的信息。假设当前命令下有一个名为xxx.c的文件,运行

%statfile xxx.c

后,将会输出如下结果:

stat xxx.c fails as it should!
fstat xxx.c succeeds as it should!

从中可知,当一个打开文件被删除后,用stat无法获取该文件的信息。而fstat就可获取该文件的信息。这是由于文件名在unlink之后已从目录中除去,无法找到该文件名,而文件描述字则因文件仍打开而保存下来。因此stat不能成功返回,但fstat仍可成功返回。

使用stat调用来判定一个文件为何种文件类型时相当有用。例如下列代码:

stat("hello", &statbuf);
if (statbuf.st_mod & S_IFMT) == S_IFDIR)
printf("This is a directory file!\n");
else if (statbuf.st_mod & S_IFMT) == S_IFREG)
printf("This is a regular file !\n");
else …

以上代码可判定当前目录下的hello文件是否为目录文件或其他类型的文件。

4.3 目录编程介绍

UNIX把目录也视为一种文件,称为目录文件,并同普通文件一样进行管理和保护。如open、close、read、lseek等文件操作对目录文件都是有效的。前面讲到的文件编程中的各个系统调用对于目录来说也同样有效。但是与普通文件相比目录文件又具有自身的一些特点:

目录文件的读/写/执行访问权限有特殊的含义:读权限允许用户读取目录项的内容;写权限允许用户创建或删除一个文件;执行权限则允许用户检索目录(此时通常称为目录搜索权限)。

目录的创建、删除与普通文件也不同,另外,任何用户都不能对目录文件以写方式打开进行文件写操作。

·目录的创建和删除-mkdir和rmdir系统调用

mkdir 和rmdir系统调用的格式为:

#include <sys/types.h>
#include <sys/stat.h>
int mkdir (const char *path, mode_t mode);

#include <unistd.h>
int rmdir(const char *path);

其中:参数path分别指出要创建和删除的目录文件的文件名。mkdir调用中的参数mode指出新创建目录文件的文件模式。新创建目录后,除"."和".."两项外,无别的目录项;删除目录时,要求目录中除"."和".."两项外,也无别的目录项。这两个系统调用Access成功时都返回0,否则返回-1。

·目录的读取-opendir/readdir/closedir库函数

目录文件可以像普通文件一样,先用系统调用open以读方式打开,再用read调用读取其中的内容。同时,由于目录文件是由具有目录结构的目录项组成的,用read读取其内容有些不方便。UNIX提供的库函数opendir/readdir/closedir等可以方便地实现目录读取。函数说明如下:

#include <sys/types.h>
#include <sys/dir.h>
DIR *opendir(char *filename);
struct direct *readdir(DIR *dirp);
void closedir(DIR *dirp);

说明:参数filename指出要打开的目录路径名,库函数opendir返回一个指向结构DIR(在文件/usr/include/sys/dir.h中定义)的指针。库函数readdir和closedir均以这个指针作为参数,其中readdir返回一个指向结构direct的指针。有关目录的操作均可基于这个指针。下面是一个例子,查找当前目录下文件名为"name"的文件。

len = strlen(name);
dirp = opendir(".");
if (dirp == NULL) {
return NOT_FOUND;
}
while ((dp = readdir(dirp)) != NULL) {
if (dp->d_namlen == len && !strcmp(dp->d_name, name)) {
closedir(dirp);
return FOUND;
}
}
closedir(dirp);
return NOT_FOUND;

库函数closedir关闭打开的目录。值得注意的是,上面这一小段代码在编程中很实用,稍加修改即可实现UNIX下类似"ls"的简单命令。
第五章 基本进程编程

5.1 概述

UNIX系统为程序员提供了一个强有力的工具:在一个程序中执行另一个程序。执行一个程序最简单的途径就是使用库函数system。该函数使用一个参数:一个包含要被执行的命令的字符串。这一库函数的特点是用法简单,在程序中调用简单的UNIX命令时很有用。但是由于它的调用要由SHELL进程来实现,故效率并不高,在实际的编程中应用并不广泛。本章主要介绍在实际编程中经常使用的有关进程控制和管理方面的系统调用,它们包括:

fork - 创建一子进程
exec - 执行子进程
exit - 终止进程执行
wait - 等待子进程暂停或终止
setpgrp - 设置进程标识符
getpid、getppid - 获取进程标识符
setuid、setgid - 设置进程的用户标识符
getuid、geteuid、getgid、getegid - 获取进程的用户标识符

5.2 进程控制

1. fork系统调用

系统调用fork是UNIX操作系统创建新进程的唯一手段,习惯上将新创建的进程称为子进程,调用fork的进程称为父进程。
fork系统调用的格式为:

int fork()

fork系统调用没有参数,如执行成功,则创建一子进程,子进程继承了父进程的某些属性。当从该系统调用返回时,系统中已有两个用户级环境完全相同的进程存在。这两个进程从fork调用中得到的返回值不同,其中子进程得到的返回值为零,父进程得到的返回值是最新创建的子进程的进程标识符。

2. exec系统调用

fork系统调用只是将父进程的环境拷贝到新进程中,而没有启动执行一个新的目标程序。UNIX系统提供了exec系统调用,用它更换进程的执行映象,启动新的目标程序。例如:UNIX系统中的所有命令都是通过exec来执行的。

exec系统调用有六种不同的使用格式,但在核心中只对应一个调用入口。它们有不同的调用格式和调用参数。这六种调用格式分别为:

#include <unistd.h>
int execl (const char *path, const char *arg0, ..., const char
*argn, (char *)0);
int execv (const char *path, char *const *argv);
int execle (const char *path, const char *arg0, ..., const char
*argn,
(char *0), const char *envp[]);
int execve (const char *path, char *const *argv, char *const *envp);
int execlp (const char *file, const char *arg0, ..., const char
*argn, (char *)0);
int execvp (const char *file, char *const *argv);

说明:参数path指出一个可执行目标文件的路径名;参数file指出可执行目标文件的文件名。arg0作为约定同path一样指出目标文件的路径名;参数arg1到argn分别是该目标文件执行时所带的命令行参数;参数argv是一个字符串指针数组,由它指出该目标程序使用的命令行参数表,按约定第一个字符指针指向与path
或file相同的字符串;最后一个指针指向一个空字符串,其余的指向该程序执行时所带的命令行参数;参数envp同argv一样也是一个字符指针数组,由它指出该目标程序执行时的进程环境,它也以一个空指针结束。

exec的六种格式在以下三点上有所不同:
(1) path是一个目标文件的完整路径名,而file是目标文件名,它是可以通过环境变量PATH来搜索的;
(2) 由path或file指定的目标文件的命令行参数是完整的参数列表或是通过一指针数组argv来给出的;
(3) 环境变量是系统自动传递或者通过envp来给出的。

下图说明了exec系统调用的六种不同格式对以上三点的支持。

系统调用 参数形式 环境传送 路径搜索
Execl 全部列表 自动 否
Execv 指针数组 自动 否
Execle 全部列表 不自动 否
Execve 指针数组 不自动 否
Execlp 全部列表 自动 是
Execvp 指针数组 自动 是

3. exit、wait系统调用
(1) exit系统调用格式如下:
#include <stdlib.h>
void exit(int status);
#include <unistd.h>
void _exit(int status);
说明:exit的功能是终止进程的执行,并释放该进程所占用的某些系统资源。参数status是调用进程终止时传递给其父进程的值。如果调用进程执行exit系统调用时,其父进程正在等待子进程暂停或终止(使用wait系统调用),则父进程可立刻得到该值;如果此时父进程并不处在等待状态,那么一旦父进程使用wait调用,便可立刻得到子进程传过来的status值,注意:只有status的低八位才传递给其父进程。

系统调用_exit与exit之间的差异是_exit只做部分的清除,因此建议不要轻易地使用这种调用形式。
每个进程在消亡前都要调用该系统调用,没有显示地使用该系统调用,则生成目标文件的装载程序为该进程隐含地做这一工作。

(2) wait系统调用格式如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait (int *statptr);
说明:wait系统调用将调用进程挂起,直到该进程收到一个被其捕获的信号或者它的任何一个子进程暂停或终止为止。如果在wait调用之前已有子进程暂停或终止,则该调用立刻返回。格式wait((int
*)0) 的功能是等待所有子进程终止。Wait返回时,其返回值为该子进程的进程号。参数statptr的值为该子进程的终止原因:

1、 如果子进程暂停,statptr目的高八位存放使该子进程暂停的信号值(在第七章中介绍信号),低八位为0177
2、 如果子进程由于调用exit终止,则该值的低八位为0,高八位为子进程终止时,exit系统调用中参数status值的低八位;
3、 如果子进程因信号终止,该值的高八位为0,低八位为引起终止的信号值。此外如低七位为1,则表示产生了一个core文件。
下面我们来看一个例子,该例是一个fork、exec、exit和wait联合使用的一个实例程序,我们称之为feew.c:

/* feew.c */
main(argc, argv)
int argc;
char *argv[];
{
int pid, stat;
if (argc != 1){
if ((pid = fork()) == 0){
printf("Child pid = %d\n", getpid());
execl(argv[1], argv[1], 0);
exit(5);
}
}
pid = wait(&stat);
printf("pid=%d, H_stat=%d, L_stat=%d\n", pid, stat>>8, stat&0xff);
}

当命令行参数的个数不为1时,程序使用fork系统调用产生一个子进程。子进程通过系统调用getpid获得自己的进程标识符,然后调用exec执行命令行中用户提交的命令,如果exec执行失败,则子进程调用exit(5)终止。父进程使用wait系统调用等待子进程暂停或终止,然后输出从wait中返回的信息。下面以三种方式执行该程序:
1〕 不带命令行参数
% ./feew
pid=-1, H_stat=0, L_stat=0
%
不产生子进程,从运行结果来看,当无子进程时,wait的返回值为-1。

2〕 带命令行参数,参数为合法的可执行命令
% ./feew /bin/date
Child pid = 1725
1998年 2月16日(星期一) 15时59分14秒 CST
pid=1725, H_stat=0, L_stat=0

产生子进程。子进程输出其进程标识符后,再调用exec执行从命令行中提交的命令(/bin/date),同时父进程等待子进程暂停或终止,然后输出从wait中得到的信息:子进程标识符或状态参数stat的高八位、低八位的内容。从中可以看到:子进程因调用一个隐含的exit(0)而终止,终止时传给父进程的值为0。

3〕 带命令行参数,但参数不合法
%./feew /etc/shudown
Child pid = 1760
/etc/shutdown: 只有超级用户(root)能运行 /etc/shutdown。
pid=1760, H_stat=2, L_stat=0
%
子进程创建成功。但由于以普通用户的身份执行/etc/shutdown,因此exec失败,尔后调用exit(5)而终止;父进程调用wait得到返回值:子进程号和状态参数stat的高八位、低八位的内容。从执行结果可以看出:子进程因调用exit(5)而终止,终止时传给父进程的值为5。

5.3 进程管理

进程管理包括的面很广,诸如进程的用户标识符管理、进程标识符管理等。进程的用户标识符有两个:实际用户标识符(real user
id)和有效用户标识符(effective user id),其对应的组标识符分别称为实际组标识符(real group
id)和有效组标识符(effective groud
id)。一般而言,进程的实际用户标识符为运行该进程的用户标识符,通常只用于系统记帐,其他功能由有效用户标识符来完成,如用有效用户标识符来完成对新创建文件赋予属性关系、检查文件的存取权限和利用kill系统调用向进程发送信号的权限。一般情况下,一进程的有效用户标识符和实际用户标识符是相等的,但系统允许改变进程的有效用户标识符。

1. 进程的用户标识符管理
UNIX系统提供了一组系统调用来管理进程的用户标识符,它们的使用形式是:
#include <sys/types.h>
#include <unistd.h>
uid_t getuid (void);
uid_t geteuid (void);
gid_t getgid (void);
gid_t getegid (void);
int setuid(uid_t uid);
int setgid(gid_t gid);
说明:前四个系统调用没有参数,分别返回调用进程的实际用户标识符、有效用户标识符、实际用户组标识符和有效组标识符。这些系统调用的执行总能获得成功,不会发生任何错误。系统调用setuid和setgid用于设置进程的实际用户(组)标识符和有效用户(组)标识符,如调用进程的有效用户标识符是超级用户标识符,则将调用的进程实际用户(组)标识符和有效用户(组)标识符设置为uid或gid;如调用进程的有效用户标识符不是超级用户标识符,但其实际用户(组)标识符等于uid或gid时,则其有效用户(组)标识符被设置为uid或gid;否则setuid或setgid调用失败。系统调用setuid或setgid调用成功时返回0,失败时返回-1。

2. 进程标识符管理
UNIX系统使用进程标识符来管理当前系统中的进程。为对具有某类似特性的进程统一管理,系统又引入了进程组的概念,以组标识符来区别进程是否同组。进程的组标识符是从父进程继承下来的,所以,通常进程的组标识符就是和它相关联的注册进程的标识符。进程的标识符是由系统为之分配的,不能被修改;组标识符可通过setpgrp系统调用修改。

相关系统调用的格式如下:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getpgrp(void);
pid_t getppid(void);
pid_t getpgid(pid_t pid);
说明:前三个系统调用分别返回调用进程的进程标识符、进程组标识符和其父进程标识符。它们总能成功地返回。第四个调用置进程组标识符,它将调用进程的进程组标识符改为调用进程的进程标识符,使其成为进程组首进程,并返回这一新的进程组标识符。

下面我们来看一个实例:
/* setuid.c */
main(argc, argv)
int argc;
char *argv[];
{
int ret, uid;
uid = atoi(argv[1]);
printf("Before uid=%d, euid=%d\n", getuid(), geteuid());
ret = setuid(uid);
printf("After uid=%d, euid=%d\n", getuid(), geteuid());
printf("ret = %d\n", ret);
}

下面分三种情况讨论该程序的执行:
1、 如果执行该程序的用户为超级用户,则只要命令行所给的用户标识符大于0,无论所给的用户标识符是否存在,执行总能成功。
#./setuid 3434
Before uid=0, euid=0
After uid=3434, euid=3434
ret = 0

结果分析:将进程的实际和有效用户标识符均改为3434。

2、 如果执行该程序的用户为一般用户,用id命令得到用户uid和gid后,再调用该程序,过程如下:
%id
uid=1111(yds) gid=20(user)
%./setuid 3434
Before uid=1111, euid=1111
After uid=1111, euid=1111
ret = -1
%./setuid 1111
Before uid=1111, euid=1111
After uid=1111, euid=1111
ret = 0
%

结果分析:当命令行参数为1111时,setuid执行成功,因为用户的uid就是1111。

值得注意的是:注册程序login
是个典型的setuid系统调用程序,login进程的有效用户是超级用户,该进程在建立用户的Shell进程前,调用setuid将实际和有效用户标识符调整为注册用户的用户实际和有效标识符。

原文转自:http://www.ltesting.net

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)