Unix高级环境编程
[07] Unix进程环境
==================================
1、 进程终止
atexit()函数注册终止处理程序。
exit()或return语句:
终止处理程序->终止处理程序->标准I/O清除->_exit()->进入内核。
_exit()直接进入内核。
2、 环境表
extern char **environ;
例:
for( i=0; environ[i] != NULL; i++)
{
printf( "env[%d]: %s\n", i, environ[i] );
}
3、 C程序存储空间布局
* 正文 即代码段
* 初始化数据段 如:int maxcount = 99;
* bss(非初始化数据) 如:long sun[1000];
* 栈 自动变量及函数调用所需的信息
* 堆 动态存取分配,在bss顶端,栈的底端。
-----------------------------------------------------------
高地址 栈 -> .... <- 堆 bss 初始数据 正文 低地址
-----------------------------------------------------------
注: 可用size命令查看text data bss信息。
4、 存储器分配
alloca()是在栈中分配空间,函数调用结束后空间自动释放,但有些系统不支持。
5、 环境变量
char * getenv(char * name);
int putenv( const char * str );
int setenv(const char * name, const char * value, int rewrite );
void unsetenv(const char *name );
注:environ表及字串存放在栈的顶部,当调用上述函数时,可能需将其移到至堆中。
6、 setjmp和longjmp
#include <setjmp.h>
jmp_buf jmpbuffer;
......
_@: setjmp( jmpbuffer);
......
call my_function();
......
my_function(){
longjmp(jmpbuffer, 1 );
}
注:
* 代码中,在_@处调用setjmp函数,然后经过多层调用至my_function函数,
在该函数中调用longjmp(,1)函数,责程序调整至_@处。
即:反绕过上层的所有栈帧,跳至_@所在处的栈状态。
* setjmp返回值: 如果直接调用,则返回0, 否则返回longjmp的第二个参数。
7、 getrlimit和setrlimit
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit( int resource, struct rlimit * rlptr );
int setrlimit( int resource, const struct rlimit * rlptr );
[08] 进程控制
==================================
1、 进程标识
进程ID:
0 交换进程swapper
1 init,在自举结束后由内核调用。它不会终止,但是它是普通用户进程,以超级用户权限运行。
2 页精灵进程,pagedaemon
函数:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
uid_t getuid(void);
uid_t geteuid(void);
gid_t getgid(void);
git_t getegid(void);
2、 fork函数
fork() : 创建子进程。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
* 调用fork后,子进程和父进程继续执行fork之后的代码。
* fork返回两次,子进程返回0,父进程返回子进程ID。
* fork之后,无法知道父进程和子进程哪个先执行。
例:
int main()
{
if( (pdi = fork()) < 0 )
error();
else if( pid == 0 ){//子进程
}
else{ //父进程
}
…… //父子进程继续执行fork之后的代码
}
两种fork用法
* fork之后,父子进程各自执行自己的代码。在网络服务器中常见。
* 进程执行不同的程序。fork后,子进程调用exec函数。
3、 vfork
vfork()创建一个新进程。
与fork区别:
* vfork保证子进程先于父进程执行,
* vfork子进程在父进程的地址空间内执行。
4、 wait和waitpid函数
5、 竞态条件
多个进程企图对共享数据进行处理,但结果取决于进程的运行顺序。
6、 6个exec函数对比
--------------------------------------------------------------
函数 path name 参数表 argv[] environ envp[]
--------------------------------------------------------------
execl * * *
execlp * * *
execle * * *
execv * * *
execvp * * *
execve * * *
--------------------------------------------------------------
p 取filename做参数
l 取参数表
v 表示去argv[]数组
e 表示取envp[]数组
[10] 信号
=================================
1、 概念
信号:软件中断,以SIG开头,在<signal.h>中定义。
三种方式处理信号:
* 忽略此信号,但SIGKILL和SIGSTOP不能被忽略,因为他向超级用户提供了一种使进程终止或停止的可靠方法。如果忽略某些硬件异常产生的信号,则进程的行为未知。
* 捕捉信号,要通知内核在某信号发生时,调用一个用户函数。
* 执行系统默认动作。
2、 signal函数
#include <signal.h>
void (*signal (int signo, void (*func)(int))) (int );
注:
* 返回值 void (*signal)(int);
返回老的信号处理程序指针。
* 参数
signo: 信号表示
void (*func)(int); 新的信号处理程序,
若为SIG_IGN - 忽略此信号
若为SIG_DFL - 系统默认操作
用typedef方法定义signal函数:
typedef void Sigfunc(int);
Sigfunc *signal( int, Sigfunc *);
当进程调用fork时,子进程继承了父进程的信号处理方式。因为子进程开始时复制了父进程的存储映像,所有信号捕捉函数的地址在子进程中式有意义的。
3、kill和raise函数
#include <sys/types.h>
#include <signal.h>
int kill( pid_t pid, int signo );
int raise(int signo );
* kill 将信号发送给进程或进程组
pid>0 将信号发送给该进程
pid==0 将信号发送给进程组ID等于发送进程的进程组ID,而且发生进程有许可权向其发送信号的所有进程。
pid<0 将信号发送给进程组ID等于pid绝对值,与pid==0类似。
* raise 进程向自身发送信号
4、alarm和pause函数
alarm: 设置一时间段,当超过该时间时,产生SIGALRM信号,默认动作时终止该进程。
#include <unistd.h>
unsigned int alarm( unsigned int seconds );
注:
* seconds 是秒数。
* 每个进程只能有一个闹钟时间,如果调用alarm时,以前以为该进程设置过闹钟时间,而且还没有超时,则该闹钟时间的余留值作为本次alarm函数调用的返回值。以前登记的闹钟时间被新值替换。
#include <unistd.h>
int pause(void)
注:
只有执行了一个信号处理程序并从其返回,pause才返回-1, errno设为EINTR。
(书中有很多alarm例子,说明使用信号所要注意的地方)
5、 信号集
用sigset_t类型表示一信号集。
#include <signal.h>
int sigemptyset(sigset_t * set );
int sigfillset(sigset_t *set );
int sigaddset(sigset_t *set, int signo );
int sigdelset(sigset_t *set, int signo );
----返回值: 成功 0, 错误 -1
int sigismember( conset sigset_t *set, int signo );
----返回值: 真 1, 假 0
6、 sigprocmask函数
功能:检测或更改进程的信号屏蔽字。
#include <signal.h>
int sigprocmask(int how, const sigset_t *set, sigset_t *oset );
注:
* 若oset非空, oset为当前信号屏蔽字
* 若set非空, how指示如何修改当前信号屏蔽字。
* how:
* SIG_BLOCK 或操作
* SIG_UNBLOCK 与
* SIG_SETMASK 赋值操作
7、 sigpending函数
功能:返回调用进程的被阻塞不能递送和当前未决的信号集。
#include <signal.h>
int sigpending(sigset_t *set );
8、 sigaction函数
功能,取代了signal函数。
#include <signal.h>
int sigaction(int signo, const struct sigaction *act, \
struct sigaction * oact );
struct sigaction{
void (*sa_handler)();
sigset_t sa_mask;
int sa_flags;
};
sa_handler 信号捕捉函数
sa_mask 调用sa_handler之前需要添加的信号屏蔽字,调用结束后,进程的信号屏蔽字再恢复为原先值。
sa_flags 信号处理选项。
9、 sigsetjmp和siglongjmp
#include <setjmp.h>
int sigsetjmp( sigjmp_buf env, int savemask );
void siglongjmp( sigjmp_buf env, int val );
区别:
当savemask 为非0时,sigsetjmp在env中保存进程的当前屏蔽字,调用siglongjmp时,siglongjmp从中恢复保存的信号屏蔽字。
而,setjmp和longjmp并不保证该操作。
10、sigsuspend函数
#include<signal.h>
int sigsuspend(const sigset_t sigmask );
进程的信号屏蔽字设置为sigmask,在捕捉到一个信号或发生了一个会终止该进程的信号之前,该进程也被挂起。如果捕捉到一个信号而且从该信号处理程序返回,则sigsuspend返回,并且该进程的信号屏蔽字设置为调用sigsuspend之前的值。
[11] 终端
[12] 高级I/O
====================================
1、 非阻塞I/O
对一个给定的描述符有两种方法对其指定非阻塞I/O
* 如果是调用open以获取描述符,则可以指定O_NONBLOCK标志
* 对于已经打开的描述符,调用fcntl打开O_NONBLOCK文件状态标志。
2、 记录锁
功能: 一个进程正在读或者修改文件的某个部分时,可以阻止其他进程修改同一文件区。它锁定的只是文件的一个区域,也可是整个文件。
fcntl记录锁
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
int fcntl ( int filedes, int cmd, struct flock * flockptr );
参数:
* cmd /* F_GETLK, F_SETLK, F_SETLKW */
F_GETLK 如果存在一把锁,则把现存的锁的信息写到flockptr指向的结构中。若不存在,则将l_type设置为F_UNLCK,其他域保存不变。
F_SETLK 设置由flockptr所描述的锁。或者用于清除所描述的记录锁(设置l_type为F_UNLCK)。
F_SETLKW 这是F_SETLK得阻塞版本。
* flockptr 以下结构指针
struct flock {
short l_type; /* F_RDLCK, F_WRLCK, F_UNLCK */
off_t l_start; /* offset in bytes, ralative to l_whence */
short l_whence; /* SEEK_SET, SEEK_CUR, SEEK_END */
off_t l_len; /* length, in bytes; 0 means lock to EOF */
pid_t l_pid; /* returned with F_GETLK */
};
[注]
1、当进程终止,锁全部释放,当描述符被关闭时,该描述符的锁也被释放。
2、fork后,子进程不继承父进程的记录锁
3、exec后,新程序可以继续原执行程序的锁
[13] 精灵进程
================================
1. 编程规则
- 调用fork,然后父进程调用exit。
用处: 1.如果该精灵进程有Shell启动,那么父进程终止,是的Shell认为该条命令已经执行完成。
2.保证了子进程不是一个进程组的首进程,它进程了父进程的进程组ID。
- 调用setsid创建一个新的会话期。
1.是进程成为对话期首进程。 2.成为一个新进程组的首进程。 3.没有控制终端。
- 将工作目录更改为跟目录。
- 将文件方式创建屏蔽字设置为0。
1.去除由父进程继承得来的屏蔽字。
- 关闭不需要的文件描述符。这与具体的精灵进程有关。
#include <sys/type.h>
#include <sys/stat.h>
#include <fcntl.h>
int daemon_init(void)
{
pid_t pid;
if( (pid = fork() )<0 )
return -1;
else if( pid!=0 )
exit(0); //parent goes bye-bye
setsid();
chdir("/");
umask(0); //clear file mode creation mask
return 0;
}
[14] 进程间通信
================================
1、 管道
#include <unistd.h>
int pipe( int filedes[2]);
filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
14.6----