APUE 第6-9章

第6章 系统数据文件和信息

/etc/passwd

getpwuid(),getpwname()用于获取口令文件,返回一个passwd结构,该结构定义于”pwd.h”。获取之后应该立即使用,因为passwd结构通常是函数内部的静态变量,只要调用任一相关函数,内容会变。

getpwent(),setpwent(),endpwent()。get返回口令文件的下一个记录,set定位到开头,end关闭。get之后用完一定记得end。

/etc/shadow

getspnam(),getspent(),setspent(),endspent()用于访问阴影文件,与口令文件的类似。

/etc/group

group结构定义于”grp.h”中,它的char **gr_mem字段的每个指针指向一个属于该组的用户名。getgrgid(),getgrnam(),getgrent(),setgrent(),endgrent()用来处理group,返回一个指向静态变量的指针,与上面相同。

附属组ID(supplementary group ID)

一个用户不仅可以属于口令文件记录项中的组,还可属于其他组。
getgroups(), setgroups(), initgroups()用于获取和设置附属组ID。

其他配置文件

一般,对每个配置文件提供get, set, end函数,如果该文件支持某种形式的搜索,则也提供搜索函数。

/etc/hosts, /etc/networks, /etc/protocols, /etc/services

utmp文件记录当前登录到系统的各个用户,wtmp文件跟踪各个登录和注销事件。用who和last命令查看。

uname(), gethostname()对应与uname和hostname命令

一大堆获得时间的函数

第7章 进程环境

内核指向C程序是,使用一个exec函数,在调用main之前先调用一个特殊的启动例程。

8种方式使进程终止:

正常终止:

  1. 从main返回
  2. 调用exit
  3. 调用_exit,_Exit
  4. 最后一个线程从启动例程返回
  5. 从最后一个线程调用pthread_exit

异常终止:

  1. 调用abort
  2. 接到一个信号
  3. 最后一个线程对取消请求做出响应

3个函数用于正常终止,_exit,_Exit立即进入内核,exit则先执行一些清理处理。历史原因,exit函数总是执行一个标准I/O库的清理关闭操作:对于所有打开流调用fclose函数,这造成输出缓冲中的所有数据被冲洗(写到文件上)。
在main中调用exit(0)等价于return 0.

如果不是C99,那么不调用return或exit将出现一个未定义的终止码,C99则是0.

atexit()用于登记至多32个终止处理程序。

每个程序会接收到一张环境表,是一个字符指针数组。使用environ指针来访问该数组,也可以用getenv(),putenv()来访问和设置特定的环境变量。
环境表存放于栈之上,这就导致系统对其的内存管理较为麻烦。

setjmp(), longjmp()

使用这两个函数实现跨越多个调用栈的非局部跳转。存放在存储器中的变量具有longjmp时的值,而CPU和寄存器中的变量恢复为调用setjmp的值,可以使用volatile使得变量存在存储器中。这两个函数的参数是jmp_buf结构,因为需要跨多个调用栈,所以一般该参数定义为全局的。longjmp还有另一个参数,会作为setjmp的返回值。

volatile

易变的,所以系统总是重新从它所在的内存读取数据。

当一个函数返回后,其栈区将由下一个被调用函数使用,所以不应当在其返回后使用其存储空间,而应该使用全局的或动态的(动态分配的空间没有回收则将一直被保留)

rlimit

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);

resource:可能的选择有

  • RLIMIT_AS //进程的最大虚内存空间,字节为单位。
  • RLIMIT_CORE //内核转存文件的最大长度。
  • RLIMIT_CPU //最大允许的CPU使用时间,秒为单位。当进程达到软限制,内核将给其发送SIGXCPU信号,这一信号的默认行为是终止进程的执行。然而,可以捕捉信号,处理句柄可将控制返回给主程序。如果进程继续耗费CPU时间,核心会以每秒一次的频率给其发送SIGXCPU信号,直到达到硬限制,那时将给进程发送 SIGKILL信号终止其执行。
  • RLIMIT_DATA //进程数据段的最大值。
  • RLIMIT_FSIZE //进程可建立的文件的最大长度。如果进程试图超出这一限制时,核心会给其发送SIGXFSZ信号,默认情况下将终止进程的执行。
  • RLIMIT_LOCKS //进程可建立的锁和租赁的最大值。
  • RLIMIT_MEMLOCK //进程可锁定在内存中的最大数据量,字节为单位。
  • RLIMIT_MSGQUEUE //进程可为POSIX消息队列分配的最大字节数。
  • RLIMIT_NICE //进程可通过setpriority() 或 nice()调用设置的最大完美值。
  • RLIMIT_NOFILE //指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
  • RLIMIT_NPROC //用户可拥有的最大进程数。
  • RLIMIT_RTPRIO //进程可通过sched_setscheduler 和 sched_setparam设置的最大实时优先级。
  • RLIMIT_SIGPENDING //用户可拥有的最大挂起信号数。
  • RLIMIT_STACK //最大的进程堆栈,以字节为单位。

rlimit:描述资源软硬限制的结构体,原型如下

struct rlimit {
  rlim_t rlim_cur;  //soft limit
  rlim_t rlim_max;  //hard limit
};

soft limit是指内核所能支持的资源上限。比如对于RLIMIT_NOFILE(一个进程能打开的最大文件数,内核默认是1024),soft limit最大也只能达到1024。对于RLIMIT_CORE(core文件的大小,内核不做限制),soft limit最大能是unlimited。

hard limit在资源中只是作为soft limit的上限。当你设置hard limit后,你以后设置的soft limit只能小于hard limit。要说明的是,hard limit只针对非特权进程,也就是进程的有效用户ID(effective user ID)不是0的进程。具有特权级别的进程(具有属性CAP_SYS_RESOURCE),soft limit则只有内核上限。

返回说明:

  • 成功执行时,返回0。失败返回-1,errno被设为以下的某个值
  • EFAULT:rlim指针指向的空间不可访问
  • EINVAL:参数无效
  • EPERM:增加资源限制值时,权能不允许
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
struct rlimit
{        
   rlim_t rlim_cur;        
   rlim_t rlim_max;
};

//代码实例
struct rlimit rlim,rlim_new;
if (getrlimit(RLIMIT_CORE, &rlim)==0) {
  rlim_new.rlim_cur = rlim_new.rlim_max = RLIM_INFINITY;
  if (setrlimit(RLIMIT_CORE, &rlim_new)!=0) {
    rlim_new.rlim_cur = rlim_new.rlim_max = rlim.rlim_max;
    (void) setrlimit(RLIMIT_CORE, &rlim_new);
  }
}

结构体中,rlim_cur是要取得或设置的资源软限制的值,rlim_max是硬限制
这两个值的设置有一个小的约束:
1) 任何进程可以将软限制改为小于或等于硬限制;
2)任何进程都可以将硬限制降低,但普通用户降低了就无法提高,该值必须等于或大于软限制;
3) 只有超级用户可以提高硬限制;
一个无限的限制由常量RLIM_INFINITY指定.

第8章 进程控制

进程ID实现了延迟复用,以防新进程被误认为是另一个已终止进程。
ID为0的进程通常为调度进程,swapper。

getpid(),getppid(),getuid(),geteuid(),getgid(),getegid().用于获得进程的进程ID,父进程ID,实际用户ID,有效用户ID,实际组ID和有效组ID。

fork()创建的子进程获得父进程的副本,只共享正文段。但采用写时复制策略,一开始是共享数据的,一旦试图写,则创建副本。Linux的clone()允许调用者控制哪些部分是共享的。

两种用法:一个父进程希望复制自己;一个进程要执行一个不同的程序,这通常接一个exec().

父进程与子进程的区别:子进程的tms_utime, tms_stime, tms_cutime, tms_ustime为0,进程ID不同,父进程ID不同,不继承父进程设置的文件锁,未处理闹钟被清除,未处理信号集设为空集。

vfork()创建一个新进程,而新进程的目的是exec一个新程序。它保证子进程先运行,在子进程调用exec或exit之后父进程才可能被调度(注意,此处会存在死锁风险,如果子进程依赖于父进程的进一步动作的话)

一个进程如果父进程先于它结束,则会被init进程收养。一个进程终止后内核还为其保存了一定量的信息,当其父进程调用wait或waitpid时,可以处理这些信息。如果没有这一步wait,则这些进程终止后变成zombie。被init收养的进程将永远不会僵死,因为init一直会wait。

wait()阻塞,但只要有一个子进程终止,就返回。其只接受一个整型指针参数statloc,用于保存终止进程的终止状态,statloc可以通过4个互斥的宏进行测试:WIFEXITED(),WIFSIGNALED(),WIFSTOPPED(),WIFCONTINUED(),分别在正常终止、异常终止、暂停、继续的时候返回真。WEXITSTATUS()用于从statloc中获取子进程传送给exit的参数的低8位。

waitpid()用于等待特定进程。其pid参数:
1. pid==-1 等价wait()
2. pid>0 进程ID与pid相等
3. pid==0 组ID与调用进程组ID相等
4. pid<-1 组ID与pid绝对值相等
waitpid()可提供非阻塞版本。

使用fork()两次,让子进程再创建它的子进程,子进程终止,那么孙进程就被init收养,此时相当于孙进程变成了平辈进程。

waitid()有更细致的选项。wait3(),wait4()允许内核返回由终止进程及其所有子进程使用的资源情况。

UNIX使用信号机制实现进程间通信(IPC)。

7个exec函数使得进程去执行另一个程序,ID不变。这些函数的一个区别是有些可指定env有些不行,对于无法指定的可以用setenv和putenv这样的函数用来改变env,但只对新产生的子进程有效。execve是系统调用,其他6个是库函数,最终都调用它。它们正常执行则不返回,因为会加载新的程序到内存然后执行。

在exec时,保存的设置用户ID就被设置为有效ID,普通用户可以将有效ID改为实际ID或者保存的设置用户ID。

使用system()执行命令,它的-c选项告诉shell取下一个命令行参数作为命令输入。

进程会计在进程终止时写一个会计记录,所以无法获取永远不终止的进程的会计记录,记录的顺序是进程终止的顺序。

getlogin()获取登录名,有了登录名就可以使用getpwnam()查找了。

nice(),getpriority(),setpriority()用来改变nice值,后两者用于为进程、进程组和属于特定用户ID的所有进程。非特权用户只能降低优先级。

times()用于获取时间。

第9章 进程关系

每个进程属于一个进程组,同一进程组的各进程接收来自同一终端的各种信号。每个组有一个进程组ID。getpgrp(),getpgid().每个组的组长ID等于其进程组ID。

进程组组长可以创建一个进程组、创建该组的进程,然后终止。只要该组有一个进程存在,则该进程组就存在,与组长无关。

setpgid()可以让进程加入一个现有的进程组或者新建一个。

session是一个或多个进程组的集合。用setsid(), getsid()。会话首进程(session leader)的进程组ID可以视为会话ID。

setsid(pid_t pid);

如果调用函数的进程不是进程组的组长,则会创建一个新会话,结果将发生下面3件事情:
1. 该进程会变为新会话的首进程。
2. 该进程会成为一个新进程组的组长进程
3. 该进程没有控制终端。

如果该调用进程已经是一个进程组的组长,则调用会出错。
为了保证不会出错,通常先fork一个子进程,在关闭父进程,因为子进程继承了父进程的进程组ID,
而进程iD则是新分配的,两者不可能相等,从而保证了子进程不会是进程组组长。(后面编写守护进程时会用到。)

一个会话可以有一个控制终端(终端设备或伪终端设备),建立和控制终端连接的会话首进程被称为控制进程,会话有一个前台进程组和若干后台进程组。键入中断键或退出键都会将相应信号发送至前台进程组。如果终端断开连接,则将挂断信号发送至控制进程。

tcgetpgrp()返回前台进程组ID,tcsetpgrp()设置前台进程组ID,tcgetsid()获得会话首进程的进程组ID

  • 中断字符产生SIGINT;
  • 退出字符产生SIGQUIT;
  • 挂起字符产生SIGTSTP.

后台作业如果试图读stdin,则会被终端发送一个SIGTTIN,然后停止,shell向用户发送此消息,用户可以选择将它调到前台,它会收到一个SIGCONT继续执行。如果试图写stdout,或许可以直接写,用stty命令可以改变这一选项,使得跟读stdin的过程一样了。

孤儿进程组:该组的所有成员的父进程不是该组所属会话其他组的成员。

kill(pid_t pid, int sig);用于将sig信号发送给参数pid指定的进程。

终端登录过程:

当系统自举时,内核创建ID为1的进程,也就是init进程,init进程系统进入多用户状态。
init进程读取/etc/inittab,对每一个允许登录的终端设备,init调用一次fork,它所生成的子进程执行(exec)getty程序。
getty为终端设备调用open函数,如果没有请求则阻塞,如果有请求,则文件描述符0,1,2就设置到该设备,然后getty输出”login“等的信息并等带用户输入用户名。
当用户键入用户名后,getty的工作就完成了。
然后以类似于这样的方式调用login程序

execle(“/bin/login”,“login”,“-p”,username,(char*)0,envp);

init以空环境(即无envp参数)调用一个getty,gettty以终端名和gettytab中说明的环境字符串。-p标识通知login保留传递给它的环境,也可将其他环境字符串加到该环境中,但是不要替换它。

你可能感兴趣的:(学习笔记)