首页 > Linux/Unix > UNIX环境高级编程学习笔记(中)

UNIX环境高级编程学习笔记(中)

2013年5月1日 发表评论 阅读评论
文章作者:Yx.Ac   文章来源:勇幸|Thinking (http://www.ahathinking.com)   转载请注明,谢谢合作。

---

此文作者:冰镇南瓜汁;mail:munantv@qq.com

进程环境

常用函数:

<unistd.h>

void _exit(int status);

<stdlib.h>


void exit(int status);
void _Exit(int status);

正常终止程序,_Exit和_exit立即进入内核,exit要先执行清理工作(包括关闭所有打开的流,以及调用终止处理程序)。

int atexit(void (*func)(void));

登记终止处理函数,这些函数会被exit自动调用,调用的顺序与登记的顺序相反,登记多次会导致调用多次。成功返回0,出错返回非0值。


void *malloc(size_t size);
void *calloc(size_t nobj, size_t size);
void *realloc(void *ptr, size_t newsize);
void free(void *ptr);

malloc(memory allocation)分配指定字节数的存储区,初始值不定。calloc为指定大小指定个数的元素分配存储区,每一个位置都初始化为0。realloc更改以前分配区的长度。free释放ptr指向的存储空间。

char *getenv(const char *name)

返回与name相关联的环境变量值指针,未找到返回NULL。

<setjmp.h>


int setjmp(jmp_buf env);
void longjmp(jmp_buf env, int val);

实现在栈上跳过若干帧,返回到当前函数调用路径上的某一个函数中。在希望返回到的位置调用setjmp,此次直接调用返回0。调用longjmp的第一个参 数是setjmp的env(env应定义为全局变量,因为要在另一个函数中使用),第二个参数是非0的val,val将作为这一次setjmp的返回值。
常见的用法:

if (setjmp(jmpbuffer) != 0) {...}... f1();...   f1(){...; longjmp(jmpbuffer, 1); ...}

<sys/resource.h>

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

查询和更改进程的资源限制,成功返回0,出错返回非0值。

细节:

进程正常终止的5种方式:从main返回、调用exit、调用_exit或_Exit、最后一个线程从其启动例程返回、最后一个线程调用pthread_exit。

进程异常终止的3种方式:调用abort、接到一个信号并终止、最后一个线程对取消请求做出响应。

main函数返回一个整型值与用该值调用exit是等价的。

全局、静态、易失的变量值在longjmp时保持不变。

进程控制

常用函数:

<unistd.h>

pid_t getpid(void); 

返回调用进程的进程ID。

pid_t getppid(void);

返回调用进程的父进程ID。

uid_t getuid(void); 

返回调用进程的实际用户ID。

uid_t geteuid(void);

返回调用进程的有效用户ID。

gid_t getgid(void); 

返回调用进程的实际组ID。

gid_t getegid(void); 

返回调用进程的有效组ID。

pid_t fork(void);

创建一个新进程,在子进程中返回0,在父进程中返回子进程ID,出错返回-1。
常见用法:

if ((pid = fork()) < 0) {/*error*/} else if (pid == 0) {/*child*/} else {/*parent*/}
pid_t vfork(void);

创建一个新进程用于立即执行一个程序。在子进程调用exec或exit之前,它在父进程的空间中运行,且父进程处于休眠状态。

int execl(const char *pathname, const char *arg0, ... /* (char *)0 */);
int execv(const char *pathname, char *const argv[]);
int execle(const char *pathname, const char *arg0, ... /* (char *)0, char *const envp[] */);
int execve(const char *pathname, char *const argv[], char *const envp[]);
int execlp(const char *filename, const char *arg0, ... /* (char *)0 */);
int execvp(const char *filename, char *const argv[]);

执行一个程序,出错返回-1,成功不返回值。
l表示list,v表示vector,表示参数的区别(单独的参数列表还是参数数组,单独的参数列表要以(char *)0结尾)。
e表示传递环境变量数组envp;不带e则用调用进程中的environ变量复制现有的环境。
p表示函数取filename为参数,并用PATH环境变量寻找可执行文件;否则pathname提供完整路径。

int setuid(uid_t uid);
int setgid(gid_t gid);

若 进程具有超级用户特权,则setuid将实际用户ID,有效用户ID,以及保存的设置用户ID都设为uid。若进程没有超级用户特权,但是uid等于实际 用户ID或保存的设置用户ID,则setuid将有效用户ID设为uid,不改变实际用户ID和保存的设置用户ID值。若以上两个条件都不满 足,errno设置为EPERM,出错。成功返回0,出错返回-1。

int seteuid(uid_t uid);
int setegid(gid_t gid);

seteuid 只改变进程的有效用户ID,而不改变实际用户ID和保存的设置用户ID。如果原来的有效用户是超级用户,则新的有效用户可以随意设置;如果原来的有效用户 不是超级用户,在Linux系统下,只允许新的有效用户ID等于原来的三个用户ID中的一个,否则出错。成功返回0,出错返回-1。

int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

setreuid用于交换实际用户ID和有效用户ID的值。即setreuid(geteuid(), getuid())。成功返回0,出错返回-1。

char *getlogin(void);

获取用户登录时使用的登录名,成功返回登录名字符串的指针,出错返回NULL。

<sys/wait.h>

pid_t wait(int *statloc);

如 果调用进程的所有子进程都还在运行,调用进程阻塞;如果调用进程有一个子进程终止等待处理,获取该子进程的终止状态后立即返回;如果调用进程没有子进程, 出错。statloc指向的空间用于存放终止状态,不关心终止状态时可设为NULL。成功返回进程ID,出错返回-1。

pid_t waitpid(pid_t pid, int *statloc, int options);

相 比wait函数,多了一些功能。pid参数的四种情况:-1(等待任一子进程,此时waitpid与wait等效)、>0(等待进程ID等于pid 的子进程)、0(等待组ID等于调用进程组ID的任一子进程)、<-1(等待组ID等于pid绝对值的任一子进程)。options可以取0或者以 下常量使用或运算进行组合:WNOHANG(若pid指定的子进程不是立即可用的,则waitpid不阻塞)、WCONTINUED、 WUNTRACED(这两个用于作业控制)。

<stdlib.h>

int system(const char *cmdstring);

执 行shell命令,其中调用了fork、exec和waitpid。如果fork失败或者waitpid返回除了EINTR之外的出错,则system返 回-1,在errno中设置了错误类型值;如果exec失败,返回值相当于shell执行了exit(127);否则三个函数都执行成功,system的 返回值是shell的终止状态。

细节:

ID为0的进程通常是调度进程(交换进程);ID为1的进程通常是init进程。

调用fork创建的子进程是父进程的副本,获得父进程数据空间、堆和栈的副本,与父进程共享正文段。

对于父进程已经终止的进程,它们的父进程都改变为init进程。

一个已经终止,但父进程尚未对其进行善后处理的进程叫做僵死进程(zombie)。

只有execve是真正意义上的系统调用,其它exec族函数都是在此基础上经过包装的库函数,它们最终都要调用execve。

为确保安全,设置用户ID或设置组ID程序不应调用system函数。

进程关系

常用函数:

<unistd.h>

pid_t getpgrp(void);

返回调用进程所在的进程组ID。

pid_t getpgid(pid_t pid);

返回进程ID为pid的进程所在进程组的ID,若pid为0,则等价于getpgrp。出错返回-1。

int setpgid(pid_t pid, pid_t pgid);

将pid进程的进程组ID设置为pgid。如果pid与pgid相等,则pid变成进程组组长。如果pid是0,则使用调用进程的ID。如果pgid是0,则pid将用作进程组id。成功返回0,出错返回-1。

pid_t setsid(void);

调用进程如果是一个进程组的组长,返回出错。否则,创建一个新会话,调用进程成为新会话的首进程,也成为新会话中一个新的进程组的组长进程,该进程组的ID就是该调用进程的进程ID,该进程没有控制终端(如果之前有,会被中断)。成功返回进程组ID,出错返回-1。

pid_t getsid(pid_t pid);

返回进程所属会话的ID,即会话首进程的进程组ID,出错返回-1。

细节:

进程组是一个或多个进程的集合,会话是一个或多个进程组的集合。

进程组的ID等于组长进程的进程ID。组长进程可以创建一个进程组,创建该进程组中的进程,然后终止。只要进程组中有一个进程存在,进程组就存在,与组长进程是否终止无关。进程组的生存期:进程组创建到最后一个进程离开(终止或转移到另一个进程组)。

一个进程只能为自己或其子进程设置进程组ID,且改变子进程的进程组ID必须在子进程调用exec族函数之前进行。

大多数作业控制shell中,在fork之后调用setpgid,使父进程设置子进程的进程组ID,并且使子进程设置自己的进程组ID,这种冗余调用的目的是在父子进程运行顺序不定的情况下,确保子进程身份的确定性。

一次登录形成一个会话。一个会话可包含多个进程组, 但只能有一个前台进程组,其他进程组为后台进程组。

会话的首进程打开一个终端之后,该终端就成为该会话的控制终端。与控制终端建立连接的会话首进程称为控制进程。一个会话只能有一个控制终端。产生在控制终端上的输入和信号将发送给会话的前台进程组中的所有进程。终端上的连接断开时,挂断信号将发送到控制进程。

信号

常用函数:

<signal.h>

void (*signal(int signo, void (*func)(int)))(int);

设 置信号处理方式并获取信号原来的处理方式。第一个参数signo是信号编号,第二个参数func是常量SIG_IGN(忽略)、SIG_DFL(默认动 作)或一个信号处理函数的地址(捕捉),该信号处理函数有一个整型参数。signal返回一个函数指针,该指针指向的函数无返回值,有一个整型参数,该函 数指针表示该信号以前的处理方式。若出错,返回SIG_ERR。

int kill(pid_t pid, int signo);
int raise(int signo);

kill 函数发送名为signo的信号给进程或进程组。pid>0时,将信号发送给进程ID为pid的进程。pid==0时,将信号发送给与发送进程属于同 一进程组的所有进程,但不包括系统进程。pid<0时,将该信号发送给进程组ID等于pid的绝对值的所有进程,不包括系统进程。pid==-1 时,将该信号发送给所有进程,不包括系统进程。
超级用户有权限将信号发送给任意进程。对于非超级用户,信号发送者的实际或有效用户ID必须等于接收者的实际或有效用户ID。成功返回0,出错返回-1。
raise(signo)等价于kill(getpid(), signo)。

int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigismember(const sigset_t *set, int signo);

用 于信号集(表示多个信号的组合)的处理。sigemptyset初始化set指向的信号集,清除其中所有信号。sigfillset初始化set指向的信 号集,使其包括所有信号。sigaddset将一个信号添加到现有信号集中,sigdelset则从信号集中删除一个信号。这四个函数成功返回0,出错返 回-1。sigismember测试信号集是否包含某个特定信号,包含返回1,不包含返回0,出错返回-1。

int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

检 测或更改进程的信号屏蔽字。第一个参数how表示更改的方法,有三种取值SIG_BLOCK(set集包含需要增加阻塞的信号,原有信号屏蔽字与set求 并集)、SIG_UNBLOCK(set集包含需要解除阻塞的信号,原有信号屏蔽字与set的补集求交集)、SIG_SETMASK(原有信号屏蔽字被 set代替)。若set是空指针,则不改变信号屏蔽字,此时how无意义。若oset非空,则原有信号屏蔽字通过oset返回。成功返回0,失败返回 -1。

int sigpending(sigset_t *set);

通过参数set返回发送往调用进程但被阻塞的信号集合。成功返回0,失败返回-1。

int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact);

检查或修改与指定信号相关联的处理动作。第一个参数signo是要检测或修改处理动作的信号编号。第二个参数act若非空,则根据act修改处理动作。第三个参数oact若非空,则将上一个动作通过oact返回。成功返回0,失败返回-1。
结构sigaction定义如下:

struct sigaction {
 void (*sa_handler)(int);
 sigset_t sa_mask;
 int sa_flag;
 void (*sa_sigaction)(int, siginfo_t *, void *);
 };

sa_handler 字段包含一个信号捕捉函数的地址;sa_mask字段说明了一个信号集,在调用该信号捕捉函数之前,这一信号集要加进进程的信号屏蔽字中,仅当从信号捕捉 函数返回时再将进程的信号屏蔽字复位为原先值。sa_flag是一个选项,常用的有三个:SA_INTERRUPT(由此信号中断的系统调用不会自动重 启),SA_RESTART(由此信号中断的系统调用会自动重启),SA_SIGINFO(提供附加信息,一个指向siginfo结构的指针以及一个指向 进程上下文标识符的指针)。最后一个参数是一个替代的信号处理程序,当设置SA_SIGINFO时才会使用。

int sigsuspend(const sigset_t *sigmask);

将信号屏蔽字为sigmask指向的值,然后使进程休眠,如果捕捉到信号且从该信号处理程序返回,则sigsuspend返回,并将进程的信号屏蔽字设置为调用sigsuspend之前的值。返回-1,并将errno设置为EINTR。

<unistd.h>

unsigned int alarm(unsigned int seconds);

设置计时器,经过seconds秒之后产生SIGALRM信号。返回0或者上次设置的闹钟时间的余留秒数。每个进程只能有一个计时器。

int pause(void);

调用进程挂起直到捕捉到一个信号。返回-1,并将errno设置为EINTR。
常见用法:

if (signal(SIGALRM, sig_alrm) == SIG_ERR) { /*error*/}
alarm(nsecs);
pause();
return(alarm(0));

<setjmp.h>

int sigsetjmp(sigjmp_buf env, int savemask);
void siglongjmp(sigjmp_buf env, int val);

在信号处理程序中进行局部转移时应当使用这两个函数。如果savemask非0,则sigsetjmp在env中保存进程的当前信号屏蔽字。调用siglongjmp时,如果sigsetjmp已经保存了env,则siglongjmp从其中恢复保存的信号屏蔽字。

细节:

信号的处理方式有3种:忽略、捕捉、执行系统默认动作。SIGKILL和SIGSTOP不能被忽略,也不能被捕捉。

在大多数UNIX系统中,SIG_ERR、SIG_DFL和SIG_IGN被定义为(void (*)()) -1/0/1。

产生信号的特殊字符:中断字符(Delete或Ctrl+C)产生SIGINT,退出字符(Ctrl+\)产生SIGQUIT,挂起字符(Ctrl+Z)产生SIGTSTP。

在一个进程终止或停止时,将SIGCHLD信号发送给其父进程。

在信号处理程序中调用不可重入函数,结果是不可预见的。

编号为0的信号定义为空信号,用于kill函数中检测进程是否存在。若不存在,kill出错并将errno设置为ESRCH。

每个进程都有一个信号屏蔽字,规定了当前阻塞递送到该进程的信号集合。

数据类型sig_atomic_t,这种类型的变量在写时不会被中断,总是带有修饰符volatile。

(全文完)