每个进程都有一个非负整型表示的唯一进程ID。(虽然是唯一的,但是进程ID是可以复用的,比如当一个进程终止时,其ID就成为复用的候选者,即唯一性是指当前场景下)
ID为0的进程通常是调度进程,常被称为交换进程,属于内核的一部分,其并不执行任何磁盘上的程序,也叫作系统进程。
ID为1的进程通常是init进程,可以通过终端命令 ps aux查看,一般为/sbin/init。init进程绝不会终止,他是一个普通的用户进程(不是系统进程),但是以root权限运行,init是所有孤儿进程的父进程。可参考ps命令的详细信息
除了进程ID,每个进程还有一些其他标识符,可通过以下函数查看
pid_t getpid(void); //获取进程id
pid_t getppid(void); //获取父进程id
uid_t getuid(void); //获取实际使用用户id,程序的执行者id
uid_t geteuid(void); //获取有效用户id,程序的拥有者id
gid_t getgid(void); //获取实际组id
gid_t getegid(void); //获取有效组id
如果进程B是由进程A开启的,那么我们把进程A叫做进程B的父进程,进程B叫做进程A的子进程。
创建子进程的函数为fork()和vfork()。
pid_t fork(void);
返回值:
失败返回-1,成功则返回两次(父进程返回子进程id,子进程返回0)
根据返回值的不同分别为子进程和父进程设计不同的分支
使用:
1.通过fork()创建的子进程,就是父进程的副本,他会把父进程的堆、栈、全局段、静态数据段、IO流的缓冲区都拷贝一份,父子进程共享代码段。
2.fork()函数调用成功后,父子进程就开始各自执行,它们的先后顺序是不确定的,但可以通过某些实现来保证(延时)。
3.当总进程数超过系统限制时,无法创建进程,此时fork()函数会返回失败。
共享:
父子进程不仅共享代码段,打开的文件也是共享的。
注意:
fork()执行前,只有父进程在运行,fork之后的代码父子进程都有机会执行,根据fork返回值来控制进入不同的分支。
演示一个有三级子进程的程序:
#include
#include
int main(int argc, char const *argv[])
{
if(fork())//father
{
printf("father pid:%d\n",getpid());
for(;;);
}
if(fork())//level 1 son
{
printf("son pid1:%d\n my father pid:%d\n",getpid(),getppid());
for(;;);
}
if (fork())//level 2 son
{
printf("son pid2:%d\n my father pid:%d\n",getpid(),getppid());
for(;;);
}
if (fork())//level 3 son
{
printf("son pid3:%d\n my father pid:%d\n",getpid(),getppid());
for(;;);
}
return 0;
}
fork()创建一个子进程成功时,父进程返回子进程ID,子进程返回0,因此第一个进入if(fork())判断条件的是父进程,同时通过一个死循环让父进程一直待在里面,另一边没有进入第一个if语句的为一级子进程,此时继续使用if(fork())让一级子进程(相较于二级子进程为其父进程)进入,而创建的二级子进程将会跳出第二个if,执行之后的语句,以此类推。
pid_t vfork(void);
使用:
1.vfork不能单独创建子进程,需要与excl函数族配合才能完成子进程的创建
2.它不会复制父进程的栈,堆、数据、全局等段,也不会共享代码段,而是通过excl函数调用一个程序直接启动,从而提高创建进程的效率
3.vfork()创建子进程保证先执行子进程,在执行父进程
int execl(const char *path, const char *arg, ...);
path:可执行文件的路径
arg:给可执行文件的参数,类似于命令行参数,必须以NULL结尾,第一个必须是可执行文件名,后续可自由添加
例:excl("path/a.out","a.out",...,NULL);
使用:
1.通过exec创建的子进程会替换掉父进程给的代码段,不拷贝父进程数据,会用新的可执行文件替换掉他们
2.exec只是加载一个可执行文件,并不创建进程,不会产生新的进程号
3.只有exec函数执行结束(无论成功与否),父进程才会继续执行
举个例子,首先我在路径为/home/zhizhen/Os/process/下写一个hello.c,gcc hello.c -o hello 得到可执行文件hello.o
#include
#include
int main(int argc, char const *argv[])
{
for (int i = 0; i < argc; ++i)
{
printf("%s\n",argv[i]);
}
while(1)
{
sleep(1);
printf("hello world!\n");
}
return 0;
}
然后新建一个vfork.c,vfork保证子进程先运行,在它调用exec或exit之后父进程才会恢复运行(如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁)。
#include
#include
int main(int argc, char const *argv[])
{
int pid = vfork();
if (0 == pid)
{
printf("son process:%d\n",getpid());
execl("/home/zhizhen/Os/process/hello","hello","nice to meet you",NULL);
}
while(1)
{
printf("father process:%d\n",getpid());
sleep(1);
}
return 0;
}
进程的正常退出:
1.从main退出,在main中执行return 0; (等效于调用exit())
2. _exit/_Exit(系统/标准C),这两个函数几乎没有区别
2.1 #include
2.2 #include
使用_exit/_Exit退出前会关闭所有打开的文件流,如果有子进程则会托付给init,然后向父进程发送SIGCHLD信号
此俩函数没有返回值
3.exit()
1.#include
2.exit结束前会调用通过atexit/on_exit注册的函数
3.#include
int atexit(void (*function)(void));
int on_exit(void (*function)(int , void *), void *arg);
4.最后一个线程正常结束
进程的异常终止:
1.进程调用abort函数
1.1段错误
1.2浮点异常(除0)
2.进程接受了某些信号
Ctrl+c
Ctrl+z
Ctrl+\
3.最后一个线程收到取消操作,而且线程收到回应