这个是停了周三黄翻梅讲的系统编程,借鉴她的PPT写的一些总结和当时问题的解决。
一:进程
进程就是一个程序的执行,是一个动态的存在,以及它所需要的所有系统资源的总称。还有PCB是进程存在的唯一标识,“PCB在则进程在,PCB亡则进程亡”。进程由三部分组成:代码段,数据段,PCB
整个linux的进程就是一棵树,树根是系统自己创建的,即0号进程,由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟贮存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号。。的若干终端注册进程getty。getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登陆过
程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来
的getty进程。再由shell直接或间接地产生其他进程
0号进程->1号内核进程->1号内核线程->1号用户进程(init进程)->getty进程->shell进程.
可以通过命令 ps 或 pstree 查看当前系统中的进程。
进程以字节编址,就是说没创建一个进程,操作系统为它分配4GB的虚拟进程地址空间,(为什么是4GB呢,因为在32位操作系统中一个指针的长度是4个字节,,那么4字节的指针的寻址能力0X00000000~0XFFFFFFFF,而0XFFFFFFFF就是4GB)。但是这4GB不是非要全部给进程,指的是进程需要多少就取多少来用,但是不能超过4GB。
1:子进程创建的原因
为保护地址空间中的内容可以考虑将那些需要对地址空间中的数据进行访问的操作部分放到另外一个进程的地址空间中运行,并且只允许其访问原进程地址空间中的相关数据。具体的,可在进程中通过CreateProcess()函数去创建一个子进程,子进程在全部处理过程中只对父进程地址空间中的相关数据进行访问,从而可以保护父进程地址空间中与当前子进程执行任务无关的数据。对于这种情况,子进程所体现出来的作用同函数和线程比较相似,
子进程可以看成是父进程在运行期间的一个过程。由父进程来掌握子进程的启动、执行和退出。
而多个进程可以“并发”执行
2:子进程的创建
Linux提供了三个函数来创建一个新的进程,分别是fork()、vfork()和clone(),内核中提供了与他们对应的三个系统调用,分别是sys_fork(),sys_vfork(),sys_clone(),而这三个系统调用其实最终都是通过调用一个叫做do_fork()的函数来完成一个进程的创建。
创建进程的时候是“写实拷贝”,而此拷贝,指的是该子进程在需要时才进行资源的拷贝。
提问:若父进程被杀死,该子进程还能拷贝该父进程的资源进行使用么?
回答:不能,子进程虽然拷贝父进程,但它跟父进程一样,都是独立存在的,而父进程被杀死,子进程仍然存在,只不过子进程不能使用还未拷贝过来的,当时存在于父进程中的资源,因为父进程已死。
*子进程调用exec()族函数去执行新的可执行程序时就不需要将父进程的资源拷贝给子进程。
fork()函数:
创建一个进程的系统调用很简单,只要调用fork()函数就可以了。
#include
pid_t fork();
该函数被调用一次,但返回两次,两次返回的区别是子进程返回值是0,而父进程返回值是新子进程的进程ID。将子进程的ID返回给父进程的理由是:因为一个进程的子进程可以多以一个,所以没有一个函数使用
一个进程可以获得其所有子进程的进程ID。使子进程得到的返回值是0的理由是:一个进程只会有一个父进程,所以子进程总是可以调用函数getppid获得父进程的进程ID。
vfork()函数:
和fork函数类似。但是二者的区别在于:当使用vfork创建子进程时,父进程将被暂时阻塞,而子进程则可以借用父进程的地址空间。这个状态将持续到子进程要么退出,要么调用exec(),至此父进程才继续执行。
clone()函数:
有选择性的继承父进程的资源,你可以选择像vfork一样和父进程共享一个虚存空间,从而使创造的是线程,你也可以不和父进程共享,你甚至可以选择创造出来的进程和父进程不再是父子关系,而是兄弟关系
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
fn:函数指针
child_stack:为子进程分配系统堆栈空间(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值),flags就是标志用来描述你需要从父进程继承那些资源, arg就是传给子进程的参数)。
3:一个进程如何启动另一个程序的执行呢?
在Linux中要使用exec函数族,系统调用execve()对当前进程进行替换,替换为一个指定的程序,其参数包括文件名(filename)、参数列表(argv)及环境变量(envp).exec函数族当然不止一个,但它们大致相同。在Linux中,exec族调用有5个函数:
int execl(const char *path,const char *arg,...);//希望接收以逗号分隔得参数列表,列表以NULL指针作为参数标志。
int execlp(const char *file,const char *arg,...);//是一个以NULL结尾的字符串数组指针
int execle(const char *path,const char *arg,...);//函数传递指定参数envp,允许改变子进程的环境,无后缀e时,子进程使用当前程序的环境。
int execv(const char *path,char *const argv[]);//希望接收到一个以NULL结尾的字符串数组的指针。
一个进程一旦调用exec类函数,它本身就"死亡了",系统把代码段替换成新程序的代码,废弃原来的数据段和堆栈段,并未新程序分配新的数据段与堆栈段,唯一留下的就是进程号,也就是说,对系统而言,还是同一个进程,不过已经是另一个程序了。
4:进程的结束
分为主动终止与被动终止两种方式,而这两种方式大多是情况下是最后调用函数do_exit()来完成进程的终止。
显示自愿终止:在进程中调用exit()函数。
隐式地自愿终止:从某个程序的主函数返回时。
在以下情况进程会被动终止:
1、当进程接收一个它既不能处理也不能忽略的信号或异常时;
2、当进程接收到SIGABRT或其他终止信号。
无论进程以哪一种方式终止,Linux并不是在进程终止的同时就将与进程相关的所有资源都释放掉,而是分为两步执行:
通过调用do_exit()函数释放掉与进程相关的大部分资源,并使进程处于ZMOBIE(僵死)状态,直到父进程调用wait()或waitpid()。若父进程调用了wait()或waitpid(),则此时进程描述符和所有该进程独享的资源都将释放掉。
若父进程先于子进程终止了,则内核必须首先为子进程找到一个新的父进程,方法是首先给子进程在当前进程组内找一个线程作为其父进程,如果该方法不行,就让init作为该进程的父进程。
wait()函数族有两个作用:
1、获取子进程终止的消息
2、清除子进程的所有独享资源
二:线程
进程是资源分配的最小单位,而线程是计算机中独立运行的最小单位,运行时占用很少的系统资源。
*线程所用的资源大部分都是共享进程的。
一个程序的独立运行称为一个进程,在进程里面并发执行的不同部分为线程,线程是进程的一个分支,比如switch语句是一个进程,则一个case分支就可以看成是一个线程。
1:引入线程的原因:
(1) 在多进程情况下,每个进程都有自己独立的地址空间,而在多线程情况下,统一进程内的线程共享进程的地址空间。因此创建一个新的进程时就要耗费时间来为其分配系统资源,而创建一个新的线程花费的时间则要少的多。
(2)在系统调度方面,用于进程地址空间独立而线程共享地址空间,线程间的切换速度要远远快过进程间的切换速度
(3)在通信机制方面,进程间的数据空间相互独立,彼此通信要以专门的通信方式进程,通信时必须经过操作系统,而统一进程内的多个线程共享数据空间,一个线程的数据可以直接提供给其他线程使用,而不必经过操作系统,因此线程间的通信更加方便和省时。(节约资源,节约时间)。
线程的私有数据信息:
如线程号、寄存器、堆栈、信号掩码、优先级、线程私有的存储空间。
2:线程的创建函数:
线程的创建函数:pthread_create()
函数原型为:
#include
int pthread_create(pthread_t *thread,pthread_attr_t *attr,void *(*start_routine)(void*),void *arg);
thread:该参数是一个指针,当线程创建成功时,用来返回创建线程的ID。
attr:该参数用于指定线程的属性,NULL表示使用默认属性。
start_routine:该参数为一个函数指针,指向线程创建后要调用的函数,这个线程调用的函数也称为线程函数;
arg:该参数指向传递给线程函数的参数。
3:线程的终止:
线程终止:
Linux下有两种方式可以使线程终止,第一种是通过return从线程函数返回,第二种是通过调用函数
pthread_exit()函数是线程退出。当一个进程终止时里面所有的线程也就终止了,因为线程是依赖进程而存在的。
阅读(1972) | 评论(0) | 转发(0) |