1、进程标识
•
#include <
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
2、fork函数
•
#include <
unistd.h
>
•
pid_t
fork
(void);
•
一次调用,两次返回
•
子进程是父进程的一个拷贝,如数据空间、堆、堆栈,子进程对变量的修改并不能影响父进程
•
fork
之后,不能确定父子进程谁先执行
•
如果标准输出是终端设备,那么是行缓冲的,否则是全缓冲的
•
依次采用下述两种方式运行,比较输出结果
•
./
a.out
•
./
a.out
> 1.out
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int glob = 6; /* external variable in initialized data */
int main(void)
{
int var = 8; /* automatic variable on the stack */
pid_t pid;
printf("before fork\n"); /* we don't flush stdout */
if( (pid = fork()) < 0 )
{
perror("fork");
exit(0);
}
else if( pid == 0 ) /* child */
{
glob++; /* modify variables */
var++;
}
else
{
sleep(2); /* parent */
}
printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
exit(0);
}
•
文件共享,父进程打开的文件描述符会被子进程继承
3、exit函数
•
不管进程如何终止,内核都会为其关闭所有打开的描述符,释放它所使用的内存等
•
子进程退出(不管正常退出,还是异常终止)后,父进程可以获取子进程的退出状态
•
几个宏定义(下一节描述),用以分析子进程的退出状态
•
WIFEXITED
(status),
WEXITSTATUS
(status)
•
WIFSIGNALED
(status),
WTERMSIG
(status)
•
考虑几个情况:
•
1
、父进程先于子进程终止,子进程被系统进程
init
接管
•
2
、子进程先于父进程终止,子进程成为僵尸进程
defunct
•
3
、被系统进程
init
接管的进程终止,不会成为僵尸
4、wait、waitpid函数
•
#include <sys/
wait.h
>
•
pid_t
wait
(
int
*
statloc
);
•
pid_t
waitpid
(
pid_t
pid
, int
*
statloc
,
int
options);
•
由父进程调用,获取子进程的退出状态
•
如果没有子进程,
wait
失败返回
•
如果子进程在
wait
之前就已经终止,
wait
立即成功返回
•
如果所有子进程都在运行,
wait
将阻塞
•
避免产生僵尸进程的方法:调用
fork
两次
(我们不关心子进程的退出状态,不想调用wait)
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
if( (pid = fork()) < 0 )
{
perror("fork");
exit(0);
}
else if( pid == 0 ) /* first child */
{
if ((pid = fork()) < 0)
{
perror("fork");
exit(0);
}
else if( pid > 0 )
{
exit(0); /* parent from second fork == first child */
}
/*
* We're the second child; our parent becomes init as soon
* as our real parent calls exit() in the statement above.
* When we're done, init will reap our status.
*/
sleep(2);
printf("second child, parent pid = %d\n", getppid());
exit(0);
}
/* wait for first child */
if( waitpid(pid, NULL, 0) != pid )
{
perror("waitpid");
exit(0);
}
/*
* We're the parent (the original process); we continue executing,
* knowing that we're not the parent of the second child.
*/
exit(0);
}
5、竞态条件
•
竞态:多个进程访问共享的数据,最终结果依赖于各进程的执行顺序
•
看一下这个程序,多次执行时结果很可能不一致
#include <stdio.h>
#include <stdlib.h>
static void charatatime(char *);
int main(void)
{
pid_t pid;
setbuf(stdout, NULL); /* set unbuffered */
if( (pid = fork()) < 0 )
{
perror("fork");
exit(0);
}
else if( pid == 0 )
{
charatatime("output from child\n");
}
else
{
charatatime("output from parent\n");
}
exit(0);
}
static void charatatime(char *str)
{
while( *str ) putc(*(str++), stdout);
}
•
修正一下,加入同步函数,避免竞态条件
#include <stdio.h>
#include <stdlib.h>
static void charatatime(char *);
int main(void)
{
pid_t pid;
TELL_WAIT(); /* 初始化同步结构 */
setbuf(stdout, NULL); /* set unbuffered */
if( (pid = fork()) < 0 )
{
perror("fork");
exit(0);
}
else if( pid == 0 )
{
WAIT_PARENT(); /* 等待父进程完成 */
charatatime("output from child\n");
}
else
{
charatatime("output from parent\n");
TELL_CHILD(pid); /* 通知子进程已完成 */
}
exit(0);
}
static void charatatime(char *str)
{
while( *str ) putc(*(str++), stdout);
}
•
注:同步函数的实现,参见后续章节
6、exec函数族
•
exec
用于执行一个程序文件,执行后新的程序文件将完全替代现有进程
•
执行前后,进程
ID
没有改变,因为并没有创建新的进程
•
#include <
unistd.h
>
•
int
execlp
(
const
char *filename,
const
char *arg0, ... /* (char *)0 */ );
•
int
execvp
(
const
char *filename, char *
const
argv
[]);
•
exec
函数族共有
6
个变种,其它的类似,有的允许指定新的环境
•
回忆一下
FD_CLOEXEC
标志
•
程序示例
被执行的程序,编译为echoall:
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
int i;
for( i = 0; i < argc; i++ ) /* echo all command-line args */
printf("argv[%d]: %s\n", i, argv[i]);
exit(0);
}
执行程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid;
if( (pid = fork()) < 0 )
{
perror("fork");
exit(0);
}
else if( pid == 0 ) /* specify pathname */
{
if( execlp("./echoall", "abc", "myarg1", "MY ARG2", (char *)0) < 0 )
{
perror("execlp");
exit(0);
}
}
if( waitpid(pid, NULL, 0) < 0 )
{
perror("waitpid");
exit(0);
}
exit(0);
}
7、改变用户ID、组ID
•
在
Unix
系统中,基于用户
ID
、组
ID
进行权限的检查
•
#include <unistd.h>
•
int
setuid
(uid_t uid);
•
int
setgid
(gid_t gid);
•
对于
root
用户,
setuid
将改变实际用户
ID
、有效用户
ID
、保存的用户
ID
•
对于普通用户,
setuid
仅改变有效用户
ID
8、解释器文件
•
当代
Unix
系统都支持解释器文件,文本文件,首行格式:
•
#!
pathname [ optional-argument ]
•
例如:
#! /bin/
ksh
•
真正执行的是
pathname
指定的解释器,而不是文本文件自身
9、system()函数
•
#include <
stdlib.h
>
•
int
system(
const
char *
cmdstring
);
•
方便的执行一个系统命令,如:
system("date > file");
•
任何一个在命令行执行的命令,都可以通过
system
函数调用
•
例子,使用
system()
函数调用
date
命令输出当前时间
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char **argv)
{
if( argc != 2 ) exit(0);
system(argv[1]);
return 0;
}
10、用户标识
•
获取登录用户名称
•
#include <
unistd.h
>
•
char *
getlogin
(void);