Linux进程编程介绍(三)

转载 2007年09月21日 14:07:00
Linux进程编程介绍(三)
2006-11-19 16:52

转自:计算机基础教程网(ITWEN.com)

  摘要:本节要介绍一些有关进程的特殊操作。有了这些操作,就使得进程的编程更加完善,能编制更为实用的程序。主要的内容有得到关于进程的各种ID、对进程的设置用户ID、改变进程的工作目录、改变进程的根、改变进程的优先权值等操作。
  
  3.进程的特殊操作
  
  上一节介绍了有关进程的一些基本操作,如进程的产生、进程的终止、进程执行映像的改变、等待子进程终止等。本节要介绍一些有关进程的特殊操作。有了这些操作,就使得进程的编程更加完善,能编制更为实用的程序。
  
  主要的内容有得到关于进程的各种ID、对进程的设置用户ID、改变进程的工作目录、改变进程的根、改变进程的优先权值等操作。
  
  3.1 获得进程相关的ID
  
  与进程相关的ID有:
  
  真正用户标识号(UID):该标识号负责标识运行进程的用户。
  有效用户标识号(EUID):该标识号负责标识以什么用户身份来给新创建的进程赋所有权、检查文件的存取权限和检查通过系统调用kill向进程发送软中断信号的许可权限。
  真正用户组标识号(GID):负责标识运行进程的用户所属的组ID。
  有效用户组标识号(EGID):用来标识目前进程所属的用户组。可能因为执行文件设置set-gid位而与gid不同。
  进程标识号(PID):用来标识进程。
  进程组标识号(process group ID):一个进程可以属于某个进程组。可以发送信号给一组进程。注意,它不同与gid。前面的系统调用wait中指定参数pid时,就用到了进程组的概念。
  如果要获得进程的用户标识号,用getuid调用。调用geteuid是用来获得进程的有效用户标识号。有效用户ID与真正用户ID的不同是由于执行文件设置set-uid位引起的。这两个调用的格式如下:
  
    uid_t getuid(void);
    uid_t geteuid(void);

  在使用这两个调用的程序中加入下列头文件:
  
    #include
    #include
  
  如果要获得运行进程的用户组ID,使用getgid调用来获得真正的用户组ID,用getegid获得有效的用户组ID。标识gid与egid的不同是由于执行文件设置set-gid位引起的。这两个调用的格式如下:
  
    gid_t getgid(void);
    gid_t getegid(void);
  
  在使用这两个调用的程序中加入下列头文件:
  
    #include
    #include
  
  如果要获得进程的ID,使用getpid调用;要获得进程的父进程的ID,使用getppid调用。这两个调用的格式如下:
  
    pid_t getpid(void);
    pid_t getppid(void);
  
  在使用这两个调用的程序中加入下列头文件:
  
    #include
  
  如果要获得进程所属组的ID,使用getpgrp调用;若要获得指定PID进程所属组的ID用getpgid调用。这两个调用的格式如下:
  
    pid_t getpgrp(void);
    pid_t getpgid(pid_t pid);
  
  在使用这两个调用的程序中加入下列头文件:
  
    #include
  
   注意一下gid和pgrp的区别,一般执行该进程的用户的组ID就是该进程的gid,如果该执行文件设置了set_gid位,则文件所属的组ID就是该 进程的gid。对于进程组ID,一般来说,一个进程在shell下执行,shell程序就将该进程的PID赋给该进程的进程组ID,从该进程派生的子进程 都拥有父进程所属的进程组ID,除非父进程将子进程的所属组ID设置成与该子进程的PID一样。由于这几个调用使用很简单,这里就不再举例。
  
3.2 setuid 和 setgid 系统调用
  
   前面讲述了如何得到uid和gid,现在来看看如何设置它们。在讲述这两个调用以前我们先来看看对文件设置set_uid位会有什么作用。我们先编了一 个小程序来做试验。这个程序的作用是,打印出进程的uid和euid,然后打开一个名为tty.c的文件。如果打不开,就显示错误代码;如果打开了,就显 示打开成功。假设该程序名叫uid_test.c:

    /* uid_test.c */
    #include
    #include
    #include
    #include
    #include
    #include
    extern int errno;
  
    int main()
    {
    int fd;
    printf("This process's uid = %d, euid = %d ",getuid(),geteuid());
    if ((fd = open("tty.c",O_RDONLY))==-1)
    {
      printf("Open error, errno is %d ",errno);
      exit(1);
    }
    else
    {
      printf("Open success ");
    }
    }
  
  下面列出这几个文件的目录,可以看到文件tty.c的存取许可权仅为属主root可读写。
  
    [wap@wapgw /tmp]$ ls -l
    total 3
    -rw------- 1 root root 0 May 31 16:15 tty.c
    -rwxr-xr-x 1 root root 14121 May 31 16:15 uid_test
    -rw-r--r-- 1 root root 390 May 31 16:15 uid_test.c
    [wap@wapgw /tmp]$
  
  在该系统中的用户中个用户wap(500),以root用户身份执行程序:
  
    [root@wapgw /tmp]# ./uid_test
    This process's uid = 0, euid = 0
    Open success
    [root@wapgw /tmp]#
  
  下面使用su命令,转到用户wap下,执行程序
  
    [root@wapgw /tmp]#su wap
    [wap@wapgw /tmp]$ ./uid_test
    This process's uid = 500, euid = 500
    Open error, errno is 13
    [wap@wapgw /tmp]$
  
  这是由于进程的uid是500(wap),对文件tty.c没有存取权,所以出错。
  
  给程序文件设置set-uid位
  
    [root@wapgw /tmp]# chmod 4755 uid_test
  
  再转到用户wap下,执行程序uid_test。
  
    [wap@wapgw /tmp]$ ./uid_test
    This process's uid = 500, euid = 0
    Open success
    [wap@wapgw /tmp]$
  
  从上面我们看到,进程打印出的euid是0(root),而运行该进程的用户是500(wap)。由于进程的euid是root,所以成功打开了文件tty.c。
  
   上面的例子说明了两个事实:第一,内核对进程存取文件的许可权的检查,是通过检查进程的有效用户ID来实现的;第二,执行一个设置set_uid位的程 序时,内核将进程表项中和u区中的有效用户ID设置为文件属主的ID。为了区别进程表项中的euid和u区中的euid,我们将进程表项中的euid 域称为保存用户标识号(saved user ID)。
  
  下面我们来看看这两个调用。调用的声明格式如下:
  
    int setuid(uid_t uid);
    int setgid(gid_t gid);
  
  在使用这两个调用的程序中加入下面的头文件:
  
    #include
  
   调用setuid为当前发出调用的进程设置真正和有效用户ID。参数uid是新的用户标识号(该标识号应该在/etc/passwd文件中存在)。如果 发出调用的进程的有效用户ID是超级用户,内核将进程表中和u区中的真正用户标识号和有效用户标识号置为参数uid。如果发出调用的进程的有效用户 ID而不是超级用户,那么内核将根据指定的参数uid来执行,如果这时指定的参数uid的值是真正用户标识号或者是保存用户标识号(saved user ID),则内核将u区中的有效用户标识号改为参数uid,否则,该调用返回错误。该调用成功时,返回值为0;发生错误时,返回-1,并设置相应的错误代码 errno,下面是经常可能发生的错误代码:
  EPERM:用户不是超级用户,并且指定的参数uid与发出调用的进程的真正用户ID或保存用户ID不匹配。
   调用setgid设置当前发出调用的进程的真正、有效用户组ID。该调用允许进程指定进程的用户组ID为参数gid,如果进程的有效用户ID不是超级用 户,该参数gid必须等于真正用户组ID、有效用户组ID中的一个。如果进程的有效用户ID是超级用户,可以指定任何存在的用户组ID(在 /etc/group文件中存在)。
  
  注意: 对于setuid程序尤其要小心,当进程的euid是超级用户时,如果将进程setuid到其他用户,就无法再得到超级用户的权力。我们可能这样用这个调 用,某个程序,开始需要root权力才能完成开始的工作,但后续的工作不需要root的权力,所以,我们将程序的执行文件设置set_uid位,并使得执 行文件的属主是root,这样进程开始时,就具有了root的权限,在不再需要root权限的地方,用setuid(getuid)恢复进程的uid、 euid。对于可执行文件设置set_uid位,一定要注意,尤其是对那些属主是root的更要注意。因为LINUX系统中root拥有任何权力。使用不 当,会对系统安全有极大的损害。
  
3.3 setpgrp和setpgid 系统调用
  
  这两个调用是用来设置进程组ID的,其声明格式如下:
  
    int setpgrp(void);
    int setpgid(pid_t pid, pid_t pgid);
 
  在使用这两个调用的程序中加入下面的头文件:
  
  #include
  
  调用setpgrp用来将发出调用的进程的进程组ID设置成与该进程的PID相等。注意,以后由这个进程派生的子进程都拥有该进程组ID(除非修改子进程的进程组ID)。
  
   调用setpgid用来将进程号为参数pid的进程的进程组ID设定为参数pgid。如果参数pid为0,则修改发出调用进程的进程组ID。如果参数 pgid为0,将进程号为pid的进程改为与发出调用的进程同组。如果不是超级用户发出的调用,那么被指定的进程必须与发出调用的进程有相同的 EUID,或者被指定的进程是发出调用进程的子进程。
  
  进程组可用于信号的发送,或者终端输入的仲裁(与终端控制进程有相同的进程组ID且在前台可以读取终端,其他进程在企图读的时候被阻塞并发送信号给该进程)。
  
  该调用成功时,返回值为0;如果请求失败,返回-1,并设置全局变量errno为对应的值。下面是可能遇到的错误代码:
  
  ESRCH:参数pid指定的进程不存在。
  EINVAL:参数pgid小于0。
  EPERM:指定进程的EUID与发出调用进程的euid不同,且指定进程不是发出调用进程的子进程。
3.4 chdir 系统调用系统调用
  
  chdir是用来将进程的当前工作目录改为由参数指定的目录。该调用的声明格式如下:
  
    int chdir(const char *path);
  
  在使用该调用的程序中加入下面的头文件:
  
    #include
  
  使用该调用时要注意,发出该调用的进程必须对参数path指定的目录有搜索(execute)的权力。调用成功时,返回值为0;错误时,返回-1,并设置相应的错误代码。
  
3.5 chroot 系统调用
  
  系统调用chroot用来改变发出调用进程的根(“/”)目录。该调用声明的格式如下:
  
    int chroot(const char *path);
  
  在使用该调用的程序中加入下面的头文件:
  
    #include
  
   调用chroot将进程的根目录改到由参数path所指定的地方。以后该进程中以“/”(根)开始的路径,都从指定目录处开始查找。发出调用进程的子进 程都继承这个根目录的位置。该调用只能由超级用户(root)发出。注意,该调用并不改变当前工作目录,所以有可能当前工作目录“.”在根目录“/” 之外。调用成功时,返回值为0;错误时,返回-1,并设置相应的错误代码。
  
  注意: 如果用chroot调用改变根后,不能由调用chroot(“/”)来返回真正的根,因为调用中的参数“/”会被理解成新设置的根。该调用一般可以用在 login程序中,或者现在国内常见的BBS系统等应用程序中,用户登录后执行系统的一个程序,该程序将根改变成用户登录的目录(例如 /home/bbs)。这样使用的好处是,利于调试和安装;也利于安全。

 

相关文章推荐

linux进程编程介绍

  • 2009年04月25日 23:25
  • 862KB
  • 下载

Linux C编程--进程间通信(IPC)3--信号集和发送信号介绍

Linux信号集   1.信号集概念   信号集是一个能表示多个信号的数据类型,sigset_t set ;set即一个信号集。   既然是一个集合,就需要对集合进行添加/删除等操作...

Linux C编程--进程介绍7--综合应用实例

第一个实例重点说明fork和exec系统函数 该实例是一个交互式命令处理程序,它能完成Linux系统标准Shell的小部分功能,具体功能如下所述: 1.提交命令的参数最多为8个 2.可前,后台执...

Linux C编程--进程介绍5--system函数

表头文件 #i nclude 定义函数 int system(const char * string); 这个函数是用fork,exec,waitpid这三个系统函数实现的,返回值相对...

Linux C编程--进程间通信(IPC)3--信号集和发送信号介绍

Linux信号集   1.信号集概念   信号集是一个能表示多个信号的数据类型,sigset_t set ;set即一个信号集。   既然是一个集合,就需要对集合进行添加/删除等操作...

Linux系统编程——进程的介绍

我们现在的电脑基本上都是多任务,我们聊着 QQ 的时候,同时可以看着视频,这里相当于 QQ 和视频两个程序同时运行着(两个进程)。早期的时候,电脑的 CPU 是单核的(单核理论上只运行操作一个任务),...

【Linux系统编程】进程介绍

进程 我们平时写的 C 语言代码,通过编译器编译,最终它会成为一个可执行程序,当这个可执行程序运行起来后(没有结束之前),它就成为了一个进程。 程序是存放在存储介质上的一个可执行文件,...

Linux 守护进程编程介绍

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,I...

Linux系统编程——进程的介绍

进程 我们平时写的 C 语言代码,通过编译器编译,最终它会成为一个可执行程序,当这个可执行程序运行起来后(没有结束之前),它就成为了一个进程。 程序是存放在存储介质...

Linux下的socket编程实践(三)端口复用和 P2P多进程服务器

Socket端口复用 先说为什么要使用socket端口复用?如果你遇到过这样的问题:server程序重启之后,无法连接,需要过一段时间才能连接上?  1.一个监听(listen)serve...
  • NK_test
  • NK_test
  • 2015年10月05日 10:17
  • 2402
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:Linux进程编程介绍(三)
举报原因:
原因补充:

(最多只允许输入30个字)