getpwuid(),getpwname()用于获取口令文件,返回一个passwd结构,该结构定义于”pwd.h”。获取之后应该立即使用,因为passwd结构通常是函数内部的静态变量,只要调用任一相关函数,内容会变。
getpwent(),setpwent(),endpwent()。get返回口令文件的下一个记录,set定位到开头,end关闭。get之后用完一定记得end。
getspnam(),getspent(),setspent(),endspent()用于访问阴影文件,与口令文件的类似。
group结构定义于”grp.h”中,它的char **gr_mem字段的每个指针指向一个属于该组的用户名。getgrgid(),getgrnam(),getgrent(),setgrent(),endgrent()用来处理group,返回一个指向静态变量的指针,与上面相同。
一个用户不仅可以属于口令文件记录项中的组,还可属于其他组。
getgroups(), setgroups(), initgroups()用于获取和设置附属组ID。
一般,对每个配置文件提供get, set, end函数,如果该文件支持某种形式的搜索,则也提供搜索函数。
/etc/hosts, /etc/networks, /etc/protocols, /etc/services
utmp文件记录当前登录到系统的各个用户,wtmp文件跟踪各个登录和注销事件。用who和last命令查看。
uname(), gethostname()对应与uname和hostname命令
一大堆获得时间的函数
内核指向C程序是,使用一个exec函数,在调用main之前先调用一个特殊的启动例程。
3个函数用于正常终止,_exit,_Exit立即进入内核,exit则先执行一些清理处理。历史原因,exit函数总是执行一个标准I/O库的清理关闭操作:对于所有打开流调用fclose函数,这造成输出缓冲中的所有数据被冲洗(写到文件上)。
在main中调用exit(0)等价于return 0.
如果不是C99,那么不调用return或exit将出现一个未定义的终止码,C99则是0.
atexit()用于登记至多32个终止处理程序。
每个程序会接收到一张环境表,是一个字符指针数组。使用environ指针来访问该数组,也可以用getenv(),putenv()来访问和设置特定的环境变量。
环境表存放于栈之上,这就导致系统对其的内存管理较为麻烦。
使用这两个函数实现跨越多个调用栈的非局部跳转。存放在存储器中的变量具有longjmp时的值,而CPU和寄存器中的变量恢复为调用setjmp的值,可以使用volatile使得变量存在存储器中。这两个函数的参数是jmp_buf结构,因为需要跨多个调用栈,所以一般该参数定义为全局的。longjmp还有另一个参数,会作为setjmp的返回值。
易变的,所以系统总是重新从它所在的内存读取数据。
当一个函数返回后,其栈区将由下一个被调用函数使用,所以不应当在其返回后使用其存储空间,而应该使用全局的或动态的(动态分配的空间没有回收则将一直被保留)
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
resource:可能的选择有
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则只有内核上限。
返回说明:
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指定.
进程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()用于获取时间。
每个进程属于一个进程组,同一进程组的各进程接收来自同一终端的各种信号。每个组有一个进程组ID。getpgrp(),getpgid().每个组的组长ID等于其进程组ID。
进程组组长可以创建一个进程组、创建该组的进程,然后终止。只要该组有一个进程存在,则该进程组就存在,与组长无关。
setpgid()可以让进程加入一个现有的进程组或者新建一个。
session是一个或多个进程组的集合。用setsid(), getsid()。会话首进程(session leader)的进程组ID可以视为会话ID。
如果调用函数的进程不是进程组的组长,则会创建一个新会话,结果将发生下面3件事情:
1. 该进程会变为新会话的首进程。
2. 该进程会成为一个新进程组的组长进程
3. 该进程没有控制终端。
如果该调用进程已经是一个进程组的组长,则调用会出错。
为了保证不会出错,通常先fork一个子进程,在关闭父进程,因为子进程继承了父进程的进程组ID,
而进程iD则是新分配的,两者不可能相等,从而保证了子进程不会是进程组组长。(后面编写守护进程时会用到。)
一个会话可以有一个控制终端(终端设备或伪终端设备),建立和控制终端连接的会话首进程被称为控制进程,会话有一个前台进程组和若干后台进程组。键入中断键或退出键都会将相应信号发送至前台进程组。如果终端断开连接,则将挂断信号发送至控制进程。
tcgetpgrp()返回前台进程组ID,tcsetpgrp()设置前台进程组ID,tcgetsid()获得会话首进程的进程组ID
后台作业如果试图读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保留传递给它的环境,也可将其他环境字符串加到该环境中,但是不要替换它。