首先,关于进程的三个用户ID。实际用户ID、有效用户ID、保存设置用户ID。
实际用户ID:进程执行者的ID。
有效用户ID:如果进程对应的程序文件设置了设置用户ID,则为程序文件所有者的ID。
保存设置用户ID:有效用户ID的副本。
这里还有设置用户ID的概念,设置用户ID是针对程序文件来说的,设置了设置用户ID的程序文件,在执行这个文件时,进程的有效用户ID与保存设置用户ID都为程序文件所有者的ID,这时实际用户ID和有效用户ID就不一定相等了。
每个进程都有一个非负整型表示的唯一进程ID,返回进程ID的函数:
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
一个现有进程可以调用fork函数创建一个新进程,函数为:
pid_t fork(void);
fork函数被调用一次,但返回两次。两次返回的唯一区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程ID。子进程和父进程继续执行fork调用之后的指令。
使fork失败的原因主要有两个:系统中已经有了太多的进程、该实际用户ID的进程总数超过了系统限制。
父进程获取子进程的终止状态的函数:
pid_t wait(int *statloc);
pid_t waitpid(pid_t pid, int *statloc, int options);
waitpid可等待一个特定的进程,而wait则返回任一终止子进程的状态;waitpid提供了一个wait的非阻塞版本;waitpid支持作业控制。
这里有一个僵死进程的概念:在fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD信号处理函数调用waitpid()等待子进程结束,又没有显示忽略该信号,则子进程成为僵死进程,无法正常结束,此时即使是root身份kill -9也不能杀死僵死进程。补救方法是杀死僵死进程的父进程,这样僵死进程成为“孤儿进程”,过继给1号进程init,init始终会负责清理僵死进程。
用fork可以创建新进程,用exec可以执行新程序。其中execl函数用的比较多。
int execl(const char *pathname, const char *arg0, ... /* (char *)0 */ );
更改用户ID和组ID
int setuid(uid_t uid);
int setgid(uid_t gid);
这里要注意的是:若进程具有超级用户特权,则setuid函数将实际用户ID、有效用户ID、以及保存的设置用户ID设置为uid。若进程没有超级用户特权,但是uid等于实际用户ID或保存的设置用户ID,则setuid只将有效用户ID设置为uid,不改变实际用户ID和保存的设置用户ID。如果上面两个条件都不满足,则将errno设置为EPERM,并返回-1。
在程序中执行一个命令字符串很方便
int system(const char *cmdstring);
进程间通信有很多种方法,以下是最常见的两种方法:
1、管道
管道是由pipe函数创建的,一般是半双工的,只能在具有公共祖先的进程之间使用。
int pipe(int filedes[2]);
filedes[0]为读而打开,filedes[1]为写而打开。filedes[1]的输出是filedes[0]的输入。
常见的操作是创建一个管道连接到另一个进程,然后读其输出或向其输入端发送数据,为此,标准I/O库提供了两个函数popen和pclose。
FILE *popen(const char *cmdstring, const char *type);
int pclose(FILE *fp);
2、FIFO(命名管道)
通过FIFO,不相关的进程也能交换数据。
int mkfifo(const char *pathname, mode_t mode);
一旦已经用mkfifo创建了一个FIFO,就可用open打开它。
另外,消息队列、信号量和共享存储器也是常见的进程间通信的方法,这里就不一一介绍了。