1、进程ID总是唯一的,常将其作为其他标识符的一部分以确保其唯一性。一个进程结束后ID可以复用,一般采用延迟复用法。ID为0是调度进程(交换进程),是内核的一部分属于系统进程。ID=1是init进程,在内核自举后启动UNIX系统,他通常读取与系统相关的初始化文件(/etc/rc*,/etc/inittab,/etc/init.d中文件等)。init进程不会停止,虽然是普通进程却是以root权限允许。
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);//返回父进程ID
uid_t getuid(void);
uid_t geteuid(void);//返回调用进程的有效用户ID
gid_t getgid(void);
gid_t getegid(void);
2、fork
子进程是父进程的副本,子进程获得父进程数据空间、堆、栈的副本,共享正文段。子进程和父进程都会继续执行fork之后的部分。fork之后子进程先执行还父进程先执行取决于调度算法。
int globvar=6;
char buf[]="a write to stdout\n";
int main(void)
{
int var;
pid_t pid;
var = 88;
//strlen不包含终止null字节,需要进行一次函数调用
//sizeof计算包含终止null的长度,sizeof是在编译时计算缓冲区长度。
if(write(STDOUT_FILENO, buf, sizeof(buf)-1) != sizeof(buf)-1)
err_sys("write error");
printf("before fork\n");
if((pid = fork()) < 0){
err_sys("fork error");
}else if(pid == 0){
globvar++;
var++;
}else{
sleep(2);
}
printf("pid=%ld,glob=%d,var=%d\n",(long)getpid(),globvar,var);
exit(0);
}
//标准输出连到终端设备,只得到一次printf输出,因为缓冲区由换行符冲洗
$ ./myfork
a write to stdout
before fork
pid=2114,glob=7,var=89
pid=2113,glob=6,var=88
//当输出重定向到一个文件,printf输出2次,因为fork时缓冲区数据也被复制到子进程。每个进程终止时缓冲区输出到各文件。
$ ./myfork >temp
$ cat temp
a write to stdout
before fork
pid=2116,glob=7,var=89
before fork
pid=2115,glob=6,var=88
fork父进程和子进程共享一个文件偏移量,文件表项位于内核
3、vfork
vfork在调用exec或exit之前和父进程在相同空间(父进程空间,不复制地址空间)运行。vfork保证子进程先运行,调用exec或exit后父进程才可能运行。
4、exit,_exit由exit调用,_exit是系统调用,exit是C库函数
内核为每个终止子进程保存一定量的信息,当其父进程调用wait或waitpid时可以得到这些信息。
僵尸进程:一个进程已经终止,但是其父进程没有做善后操作(获取终止进程的相关信息,释放它仍占用的资源)。
一个进程的父进程先于子进程终止,则子进程的父进程变为init(收养)。只要init进程的一个子进程终止,它就会调用一个wait函数取得其终止状态。
5、父进程执行wait和waitpid
一般有进程终止时内核向其父进程发送SIGCHLD信号,父进程再调用wait/waitpid。所有子进程都还在运行则阻塞;一个进程已终止则得到其终止状态并返回;没有任何子进程则出错返回。
#include <sys/wait.h>
//返回第一个终止的子进程ID,子进程信息存于statloc
pid_t wait(int *statloc);
//等待特定子进程
pid_t waitpid(pid_t pid, int *sataloc, int options);
//父进程先终止,子进程的父进程变为init(ID=1)
#include "apue.h"
#include <sys/wait.h>
int main()
{
pid_t pid;
if((pid = fork()) < 0)
err_sys("fork error");
else if(pid == 0){
if((pid = fork()) < 0)
err_sys("fork error");
else if(pid > 0)
exit(0);
sleep(2);
printf("second child,parent pid=%ld\n",(long)getppid());
exit(0);
}
if(waitpid(pid, NULL, 0) != pid)
err_sys("waitpid error");
exit(0);
}
6、exec
调用exec函数使新进程执行另一个程序。该新程序从一个新文件的main处开始执行(用磁盘上的一个新程序替代了当前进程的正文段、数据段、堆段、栈段)。如果找到的非可执行文件,而是一个shell脚本则调用/bin/sh
linux中只有execve是内核的系统调用,其他6个都是库函数
#include <unistd.h>
//l代表参数列表,v代表参数矢量(数组),e代表环境变量
//p代表第一个参数为文件名,在PATH路径中寻找
//f开头表示第一个参数为文件标识符
int execl(const char *pathname, const char *arg0,...);
int execv(const char *pathname, const char *arg[]);
int execle(const char *pathname, const char *arg0,..., char *const envp[]);
int execve(const char *pathname, const char *arg[], char *const envp[]);
int execlp(const char *filename, const char *arg0,...);
int execvp(const char *filename, const char *arg[]);
int fexecve(int fd, const char *arg[], char *const envp[]);
//在对应目录中分别编译两个文件
//exec调用的是可执行文件
#include "apue.h"
#include <sys/wait.h>
char *env_init[] = {"USER=unknown", "PATHA=/temp", NULL};
int main(void)
{
pid_t pid;
if((pid = fork()) < 0){
err_sys("fork error");
}else if(pid == 0){
if(execle("/home/john/temp/echoall", "echoall", "myarg1", "MYARG2", (char *)0, env_init) < 0)
err_sys("execle error");
}
if(waitpid(pid, NULL, 0) < 0)
err_sys("wait error");
if((pid = fork()) < 0){
err_sys("fork error");
}else if(pid == 0){
//execlp在PATH寻找该文件
if(execlp("echoall", "echoall", "only 1 arg", "MYARG2", (char *)0) < 0)
err_sys("execlp error");
}
exit(0);
}
int main(int argc, char *argv[])
{
int i;
char **ptr;
extern char **environ;
for(i = 0; i < argc; i++)
printf("argv[%d]: %s\n", i, argv[i]);
for(ptr = environ; *ptr != 0; ptr++)
printf("%s\n", *ptr);
exit(0);
}