今天在公司有同事提出了一个问题:
class A:
def FuncA(self):
pass
a = A()
a.FuncA = a.FuncA
以上代码是否会造成内存泄漏问题?
另外根据这个问题涉及到的相关特性,再思考下这个问题:
class Descr:
def __init__(self):
self._instance = None
self._val = 0
def __get__(self, instance, owner):
print("descr.__get__")
self._instance = instance
return self._val
def __set__(self, instance, value):
print("descr.__set__")
self._instance = instance
self._val = value
class A:
obj = Descr()
a = A()
a.obj = a.obj
以上代码是否会造成内存泄漏?如果把__set__
去掉呢?
一般来说,我们都是在文件的头部进行导入,常见的形式有:
import A
from A import Func
这两种导入方式对应的底层逻辑是一致的。
如果我们导入的是模块名,那么当文件被加载时会根据模块名从sys.modules
中查询,如果对应的模块还未被初始化,那么首先就会进行模块的初始化。如果模块已经初始化完,那么就会把对应模块对象的引用拷贝当前的名字空间,即拷贝到globals()
字典中,对应模块对象的引用计数会 + 1。后续当我们再从当前文件使用导入的模块时会直接从名字空间中查找,也就是说引用的拷贝只会发生在文件的第一次加载,因此具有较高的效率。
而如果我们导入的是模块中的类/函数/属性等,那么会先根据模块名查找到模块对象,再从模块对象的__dict__
数据结构中查找到目标类对象/函数对象,然后把这个类对象/函数对象的引用拷贝到当前名字空间。后续再使用导入的类/函数时也不会再次导入,因此也是具有较高的效率。
首先需要关注的是 linux 系统的文件系统结构,当我们使用 open() 函数打开一个文件时,底层会创建如下的文件结构:
在每个进程的 task_struct 结构中,包含有文件描述符表,int 类型的 fd 就是这个表的下标位置。一个文件每被打开一次,系统就会为其创建一个 file 结构,用以标识连接信息,主要包含文件状态(可读、可写、可读写),当前文件的偏移量,引用计数,指向 inode 的指针等。使用 fork() 可以复制父进程的文件描述符,父子进程会共用 file 结构, file 结构会增加引用计数。inode 是对底层不同文件模式的一种抽象,以提供统一的操作方式。如下图所示:
bind
函数在用户层的函数签名如下:
int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
/*
int sockfd:通过socket()函数申请的套接字
const struct sockaddr *myaddr:本地端地址信息结构体,这里使用的是sockaddr_in结构体去转换为sockaddr结构体类型。其中端口号如果绑定为0,则意味着让系统帮我们自动分配一个,一般来说我们会主动分配一个确定的端口号,只有在发起connect时才会让系统替我们自动分配
socklen_t addrlen:结构体的长度,通过sizeof()计算即可
返回值:成功返回0,失败返回-1
*/
对应的系统函数是:
SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
{
return __sys_bind(fd, umyaddr, addrlen);
}
int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
{
struct socket *sock;
struct sockaddr_storage address;
int err, fput_needed;
sock = sockfd_lookup_light(fd, &err, &fput_needed); // 根据fd找到socket结构体
if (sock) {
// .....
if (!err)
err = sock->ops->bind(sock,
(struct sockaddr *)
&address, addrlen); // 调用的是&inet_stream_ops中的inet_bind函数
}
fput_light(sock->file, fput_needed);
}
return err;
}
服务端的第三次挥手是在调用了 close/shutdown 函数之后发出的,这里的逻辑过程是和第一次挥手类似的,借助 tcp_close_state 函数使 TCP 的状态由CLOSE_WAIT
转变为LAST_ACK
状态,并发出FIN
报文。
在服务端进入稳定状态之后,处理数据包的顶层函数为 tcp_rcv_established ,其中该函数根据一些条件划分为了快处理和慢处理两种方式,再通过 tcp_data_queue 放入队列,当该数据包是FIN
包,那么会进入 tcp_fin 函数: