程序、进程、线程
进程产生的方式
进程号
每个进程在初始化的时候系统都分配了一个ID号,在LInux中进程号是唯一的,描述进程的ID号通常叫做PID(process ID),PID的变量类型为pid_t。
getpid(), getppid()函数介绍
getpid函数返回当前进程ID。getppid返回当前进程的父进程的ID。类型pid_t其实是一个typedef类型,定义为unsigned int。
头文件 sys/types.h unistd.h
函数原型
pid_t getpid(void);
pid_t getppid(void)
进程复制 fork函数
头文件 sys/types.h unistd.h
函数原型
pid_t fork(void);
fork函数的返回值是进程的ID;失败返回-1
fork的特点是执行一次,返回两次。在父进程和子进程中的返回值是不一样的,父进程中返回的是子进程的ID号,而子进程中则返回0。
#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>
int main(int argc, char *argv[]) {
pid_t pid, ppid;
pid = fork();
if(pid == -1) {
puts("产生进程失败");
}
else {
if(pid == 0) {
puts("这是子进程");
printf("子进程fork返回值 : %u\n",pid);
pid = getpid();
ppid = getppid();
printf("子进程的ID为 %u\n",pid);
printf("子进程父进程的ID为 %u\n",ppid);
}
else {
puts("这是父进程");
printf("父进程fork返回值 : %u\n",pid);
pid = getpid();
ppid = getppid();
printf("父进程的ID为 %u\n",pid);
printf("父进程的父进程的ID为 %u\n",ppid);
}
}
}
发现子进程的父进程的ID有时候是产生子进程的那个进程的ID,有时候却是 1。
system方式
system函数调用shell的外部命令在当前进程中开始另一个进程(这个函数是C语言中的函数)
头文件stdlib.h
函数原型
int system(const char *command);
system函数调用“/bin/sh-c command”执行特定的命令,阻塞当前进程直到command执行完毕。
执行system函数的时候会调用fork、execve、waitpid等函数,其中任意一个调用失败将导致system函数调用失败。system函数的返回值如下:
- 失败返回-1
- 当sh不能执行时返回127
- 成功,返回进程状态值
进程执行exec函数系列
在使用fork和system函数的时候,系统中都会建立一个新的进程,执行调用者的操作,而原来的进程还会存在,直到用户显示的退出。而exec族的函数与之前的fork和system函数不一样,exec函数会用新进程代替原有的进程,系统会从新进程开始运行,新进程的PID与原进程的PID相同。
exec函数族介绍
头文件 unistd.h
函数原型
extern char **environ;
int execl(const char *path, const char *arg, .../* (char *) NULL */);
int execlp(const char *file, const char *arg, .../* (char *) NULL */);
int execle(const char *path, const char *arg, .../*, (char *) NULL, char * const envp[] */);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
上述六个函数在linux man手册中都是属于(3)类型的,也就是库函数,其实他们都是对execve这个系统函数的封装。
execve函数
头文件 unistd.h
函数原型
int execve(const char *filename, char *const argv[], char *const envp[]);
上述exec函数族的作用是:在当前系统中根据指定的文件路径寻找可执行文件并用它来取代调用进程的内容。即在原来的进程内部执行一个可执行文件,这个文件既可以是二进制文件,也可以是可执行的脚本文件。
与fork函数不同,exec函数执行成功后不会返回,这是因为当前进程的空间和资源已经被占用了,这些资源包括代码段、数据段和堆栈等。它们都已经被新内容取代,而进程的ID等标识性的东西任然是原来的东西,即exec函数在原来进程的壳上运行了自己的东西。只有程序运行失败了系统才会返回-1。
使用exec函数的一种普遍的方法是先使用fork函数分叉进程,然后在新的进程中调用exec函数。linux系统针对上述过程专门进行了优化。由于fork的过程是对原有系统进行复制,然后建立子进程,这些过程都比较耗费时间。如果在fork系统调用之后进程exec系统调用,系统就不会进行系统复制,而是直接使用exec指定的参数来覆盖原有的进程。上述的方法在linux系统上叫做写时复制(copy ont write)
,即只有在造成系统的内容发生更改的时候才进行进程的真正更新。
execve函数的例子
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main(int argc, char *argv[]) {
pid_t pid ;
pid = getpid();
printf("进程的PID为 %d\n",pid);
if(execve("/bin/ls",argv, NULL) < 0) {
puts("创建进程出错");
}
puts("这里还会执行吗?");
}
所有用户态进程的产生进程init
在linux系统中所有进程都有父子或者堂兄第关系的,除了初始进程init,没有那个进程与其他进程完全独立。系统中每个进程都有一个父进程,新的进程不是被全新的创建,通常是从一个原有的进程进行复制或者克隆的。可以使用命令pstree查看系统中运行的进程之间的关系。我发现我的最初始的进程是systemd而不是init
网上搜了一下,systemd取代了init。参考资料
进程间通信与同步
在linux下多个进程之间的通信机制叫做IPC,它是多个进程之间相互沟通的一种方法。在Linux下有多种进程间的通信方法:半双工管道、FIFO(命名管道)、消息队列、信号量、共享内存等。使用这些通信机制可以为linux下的网络服务器开发提供灵活而又坚固的框架。
半双工管道
管道是一种把两个进程之间的标准输入和标准输出连接起来的机制。管道是一种历史悠久的进程间通信的方法,自unix操作系统诞生,管道就存在了。
由于管道仅仅是将某个进程的输出与另一个进程的输入相连接的单向通信的方法,因此称其为半双工。在shell中管道用 |
表示,例如ls -l | grep *.c
表示将ls -l的输出作为grep *.c的输入。管道在前一个进程中建立输入通道,在后一个进程中建立输出通道。
进程创建管道每次创建两个文件描述符来操作管道,其中一个作为输入,另一个作为输出。
把管道想象成一个文件。对管道的读写与一般的IO系统函数一致,使用write函数写入数据,read函数读出数据,某些特定的IO操作管道是不支持的,例如偏移函数lseek。
pipe函数介绍
创建管道的函数原型为