CRT的入口函数和python进线程池

https://www.cnblogs.com/findumars/p/5208462.html
我们写的程序在编译器上编译成为一个模块(可能是obj文件或者其他形式),然后连接器会将一些所需要的库文件和刚才编译器生成的文件进行连接,最终生成一个exe文件,在所连接的库文件中就包含CRT运行时库,在运行时库里有一个已经定义如下的函数:
(1)mainCRTStartup(wmainCRTStartup)  //使用/SUBSYSTEM:CONSOLE的应用程序
(2)WinMainCRTStartup(wWinMainCRTStartup)  //使用/SUBSYSTEM:WINDOWS的应用程序
(3)_DllMainCRTStartup  //调用DllMain(如果存在),DllMain必须用__stdcall来定义

函数main、Winmain、Dllmain是三种用户定义的入口点形式。如果程序是main()或_main()函数, 则连接器会使用mainCRTStartup连接到exe中。这些运行时程序库会进一步去调用其他函数,使得C/C++运行时库代码在静态非局部变量上调用构造函数和析构函数。
在微软的操作系统中连接器和加载器之间是有协议的,不然加载运行程序时不可能成功。
当运行某个.exe文件时(.exe文件是一个符合标准PE格式程序,加载一个PE文件也就是做一个内存映射,映射大文件和小文件是没有区别的),操作系统的加载程序首先为进程分配一个4GB的虚拟地址空间,然后把这个.exe程序所占用的磁盘空间作为虚拟内存映射到这个4GB的虚拟地址空间中。一般情况下会映射到0x00400000的位置。
1.执行.exe文件中的代码的时候,操作系统还是需要把存在于磁盘上的虚拟内存中的代码交换到物理内存中,也就是虚拟内存和物理内存会频繁的双向交换。
2.接着,系统在内核中创建进程对象和主线程对象以及其他内容。
3.然后操作系统的加载程序搜索PE文件的引入表,加载所有应用程序所使用的动态链接库。
4.操作系统执行PE文件首部指定地址处的代码,开始应用程序主线程的执行,首先被执行的代码不是MyApp中的WinMain函数而是被称为C Runtime startup code的WinMainCRTStartup函数,该函数是连接时由连接程序附加到文件MyApp.exe中的。该函数得到新进程的全部命令行指针和环境变量的指针,完成一些C运行时全局变量以及C运行时内存分配函数的初始化工作。使用C++时还需要执行全局类对象的构造函数,最后才是WinMainCRTStartup函数调用WinMain函数。

WinMainCRTStartup函数传给WinMain函数的4个参数分别为:hInstance、hPrevInstance、lpCmdline、nCmdShow。
hInstace是该进程所对应的应用程序当前实例的句柄。这个参数实际上是应用程序被加载到进程虚拟地址空间的地址,通常情况下对于大多数进程该参数总是0x00400000

lpCmdline命令行参数的指针。该指针指向一个以0结尾的字符串。

nCmdShow指定如何显示应用程序窗口。

操作系统装载完应用程序后,做完初始化工作就转到程序的入口点执行。程序的默认入口点由连接程序设置。连接器对控制台程序设置的函数入口就是mainCRTStartup,mainCRTStartup再调用main函数。具体设置哪个入口点是由连接器"/subsystem:"选项确定的,它告诉操作系统如何运行编译生成的.exe文件。
可以指定CONSOLE|WINDOWS|NATIVE|POSIX。

原博里有一句话说的是如果应用程序希望知道是否有另一个实例在运行,可以通过线程同步技术,创建一个具有唯一名称的互斥量,通过检测这个互斥量是否存在可以知道是否有另一个实例正在运行。同步技术也就是信号量这些。

 

关于进线程:

看一遍是真的记不住,要记下来我还得写个十几遍。
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6 d3a2e542c000/0014319272686365ec7ceaeca33428c914edf8f70cca383000
一个进程比如说一个word,可以同时进行打字、拼写检查、打印等事情,这些事情就是线程(子任务),线程没有自己的地址空间,他们包含在进程的地址空间中。线程上下文只包含一个堆栈、一个寄存器、一个优先权。线程的上下文包含在它进程的文本片段中,进城拥有的所有资源都属于线程,所有的线程共享进程的内存和资源。同一个进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段(运行时段,用来存放临时变量和局部变量),寄存器的内容。
父进程和子进程使用进程间通信机制,同一进程的线程通过读取和写入数据到进程变量来通信。
进程内的任何一个线程都可以通过销毁主线程来销毁进程。进程内的任何线程都可以销毁、挂起、恢复和更改其他线程的优先权。

进程池multiprocessing(代码可运行,python3):multiprocessing主要通过queue、pipe进行两个函数栈之间的交流。

线程池threadpool:https://www.cnblogs.com/lovychen/p/5411743.html

代码不解释了,自己手打几边跑个结果自己研究吧。总之大概意思就是线程池先按照调用函数的要求生成一系列请求,再将这些请求放到线程池中就开始运行了,pool.wait()会将我们hello1中return的东西作为result在print_ret中,从而打印出结果。

运行结果:(result和request.requestID返了)

扩展:多个参数,取自https://bbs.csdn.net/topics/391886273

def hello(m, n, o):
    """"""
    print "m = %s, n = %s, o = %s"%(m, n, o)

 
if __name__ == '__main__':
     
   # 方法1  
    lst_vars_1 = ['1', '2', '3']
    lst_vars_2 = ['4', '5', '6']
    func_var = [(lst_vars_1, None), (lst_vars_2, None)]
    # 方法2
    dict_vars_1 = {'m':'1', 'n':'2', 'o':'3'}
    dict_vars_2 = {'m':'4', 'n':'5', 'o':'6'}
    func_var = [(None, dict_vars_1), (None, dict_vars_2)]    
    # 也就是makeRequests这个方法接受list和dict两个类型的参数,因此可以传多个参数,这样下去json也能传了。
    # 这样的话print_ret也就是(request,result)打印出print("%r%s%s"%(request.requestID,result[0],result[1]))这样
    pool = threadpool.ThreadPool(2)
    requests = threadpool.makeRequests(hello, func_var)
    [pool.putRequest(req) for req in requests]
    pool.wait()        

进程与线程的文件资源:
https://blog.csdn.net/sinat_36024346/article/details/85098234
Linux操作系统用了3个数据结构来为每个进程管理它打开的文件资源:
每个进程有一张描述符表,包括fd0、fd1、fd2等等(每个进程都有自己独立的描述符表,它的表项是由进程打开的文件描述符来索引的,每个打开的描述符表项指向文件表中的一个表项)
描述符表指向的是打开文件表,比如fd0指向文件A,fd1指向文件B(所有进程共享这张表,表项包括当前位置,引用计数,以及一个v-node表对应表项的指针)
v-node(也叫i-node)表记录了当前打开的文件的信息(所有进程共享,包括文件访问、文件大小、文件类型,最主要的是记录了文件所在的块位置,以及占用的块的位置,一个扇区512字节,8个扇区组成一个块为4KB,HashMap用来将文件名称和inode节点索引保存在一起。)
父进程由于fork子进程会具有父进程的副本,所以文件的ref_cnt也会增加。

进程与线程的内存资源:https://blog.csdn.net/github_35124642/article/details/51570131

https://blog.csdn.net/tjh_i_can/article/details/80117005
每个进程新建都有4GB内存空间。新进程建立时会建立自己的内存空间,将进程中的数据、代码等从磁盘拷贝到自己的进程空间,task_struct也就是PCB,容纳了一个进程所有的信息,记录哪些地址有数据、哪些可读、哪些可写等等。程序中调用的malloc分配的其实是虚拟内存(用户态内存中是这种延迟分配的方式,内核态申请内存不会延迟分配),即为这块虚拟内存对应的页表项作相应设置,当进程真正访问到此数据时才引发缺页异常,从而获得一块真正的物理内存。虚拟空间到磁盘空间的映射也就是mmap,之前提到GPIO设备的时候有讲过。

if(mode==INPUT)
    *(gpio+fsel)=(*(gpio+fsel)&~(7<<shift));
else if(mode==OUTPUT)
    *(gpio+fsel)=(*(gpio+fsel)&~(7<<shift)) | (1<<shift);

if(value==LOW)
    *(gpio+gpioToGPCLR[pin])=1<<(pin & 31);
else
    *(gpio+gpioToGPSET[pin])=1<<(pin & 31);

这里的GPIO方式大概也就是内存映射mmap,关于mmap:

可以用mmap()实现共享内存的通信,常见为:
fd=open(name,flag,mode);
if(fd<0)
...
ptr=mmap(NULL,len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);

父子进程通信:父进程调用mmap(),然后调用fork(),这样子进程不仅可以继承父进程的mmap地址,也可以获得自己的mmap地址,这样父子进程的两个地址就可以通信。父子进程也就是一个mmap的映射。

munmap(void *addr,size_t len)//解除地址空间的映射关系,addr是调用mmap时返回的地址,len是映射区的大小。当映射关系解除后,对原来映射地址的访问将导致段错误。

msync(void *addr,size_t len,int flags)//进程在映射空间对共享内存内容的改变并不直接返回到磁盘文件中,往往在munmap()后才执行,因此这个函数可以是磁盘上的文件内容与共享存储区的内容保持一致。

#include<sys/mman.h>
#include<sys/types.h>
#include<fcntl.h>
#include<string.h>
#include<stdio.h>
#include<unistd.h>
#include<errno.h>
typedef struct{
    char name[4];//这里在内存空间是4
    int age;//这里在内存空间也是4,一共是8
}people;
void main(int argc,char **argv)
{
    int fd,i;
    people *p_map;
    char temp;
    fd=open(argv[1],O_CREAT|O_RDWR|O_TRUNC,00777);
    lseek(fd,sizeof(people)*5-1,SEEK_SET);
    write(fd,"",1);
    p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(p_map==(void*)-1)
    {
        fprintf(stderr,"mmap:%s\n",strerror(errno));
        return;
    }
    close(fd);
    temp='a';
    for(i=0;i<10;i++)
    {
        temp+=i;
        (*(p_map+i)).name[1]='\0';
        memcpy((*(p_map+i)).name,&temp,1);
        (*(p_map+i)).age=20+i;
    }
    printf("initializeover\n");
    sleep(10);
    munmap(p_map,sizeof(people)*10);
    printf("umapok\n");
}
#include<sys/mman.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<errno.h>
typedef struct{
    char name[4];
    int age;
}people;
void main(int argc char **argv)
{
    int fd,i;
    people *p_map;
    fd=open(argv[1],O_CREAT|O_RDWR,00777);
    p_map=(people*)mmap(NULL,sizeof(people)*10,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
    if(p_map==(void *)-1)
    {
        fprintf(stderr,"mmap:%s\n",strerror(errno));
        return;
    }
    for(i=0;i<10;i++)
    {
        printf("name:%s age%d;\n",(*(p_map+i)).name,(*(p_map+i)).age);
    }
    munmap(p_map,sizeof(people)*10);
}

再记录一次:虚拟存储器位于磁盘,物理存储器位于内存(DRAM)。
虚拟内存和物理内存是一种抽象的概念,意思是虚拟内存的虚拟的,物理内存是实际的。
虚拟地址在磁盘上,物理地址在内存中。虚拟地址通过地址转化也就是+offset成为一个物理地址。虚拟地址一旦不使用就不具有地址了而物理地址就算不使用也还是会有的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值