比较晚了,大概2个月没写c++了吧,最近一直又在写golang了,但是如果你要问我最喜欢的语言排行,那这个顺序肯定是
c++ > php > golang > python
php尽管经常被黑,但是自己每个周吧,都会用php写写网站后台;毕竟工作久了,你会发现语言根本无所谓的,只是一个工具。
技术也许真的只是个青春饭,但是当下还是在做;再就是兴趣,我从初中就讨厌语文,英语,喜欢数学;数学就是这样1+1 = 2;结果总是固定的,不像语文黑的说成白的,白的也可以说成黑的这就是我讨厌语文的原因;进入正题~
设备号相关 翻阅: <<<
stat函数相关: <
最近遇见一个问题,是关于硬链接的,硬链接呢,跨文件系统是不可以使用的,比如说
zhanglei@ubuntu:~$ df -h Filesystem Size Used Avail Use% Mounted on udev 1.9G 0 1.9G 0% /dev tmpfs 389M 1.9M 388M 1% /run /dev/sda5 39G 30G 7.9G 79% / tmpfs 1.9G 0 1.9G 0% /dev/shm tmpfs 5.0M 4.0K 5.0M 1% /run/lock tmpfs 1.9G 0 1.9G 0% /sys/fs/cgroup /dev/loop0 128K 128K 0 100% /snap/bare/5 /dev/loop1 220M 220M 0 100% /snap/code/95 /dev/loop2 112M 112M 0 100% /snap/core/12941 /dev/loop3 111M 111M 0 100% /snap/core/12834 /dev/loop4 228M 228M 0 100% /snap/code/96 /dev/loop5 44M 44M 0 100% /snap/snapd/15177 /dev/loop6 55M 55M 0 100% /snap/snap-store/558 /dev/loop7 66M 66M 0 100% /snap/gtk-common-themes/1519 /dev/loop8 62M 62M 0 100% /snap/core20/1405 /dev/loop9 45M 45M 0 100% /snap/snapd/15534 /dev/loop10 249M 249M 0 100% /snap/gnome-3-38-2004/99 /dev/loop11 62M 62M 0 100% /snap/core20/1434 /dev/sda1 511M 4.0K 511M 1% /boot/efi tmpfs 389M 24K 389M 1% /run/user/1000
如果我想看这个文件是隶属于哪个文件系统的呢?
df 里有这个功能
zhanglei@ubuntu:~$ df -h /home/zhanglei/ Filesystem Size Used Avail Use% Mounted on /dev/sda5 39G 30G 7.9G 79% /
我的解决方案是读 df 这个二进制 的源码,参考kernel 的源码,我认为是最好的解决方案。
df的源码位于 linux coreutils 里面,他不属于kernel的源码,是kernel上层重要的封装,coreutils有很多重要的工具比如说ls ln 等等工具的实现
df的源码位于coreutils
GitHub - coreutils/coreutils: upstream mirror
coreutils 依赖gunlib 底层库
GitHub - coreutils/gnulib: upstream mirror
df的实现在df.c里面,我们看核心代码,df.c 在main 函数里调用了
mount_list =
read_file_system_list ((fs_select_list != NULL
|| fs_exclude_list != NULL
|| print_type
|| field_data[FSTYPE_FIELD].used
|| show_local_fs));
这个api去读了/proc/mounts
# define MOUNTED "/proc/mounts"
read_file_system_list 关于 /proc/mounts 部分核心实现
{
struct mntent *mnt;
char const *table = MOUNTED;
fp = setmntent (table, "r");
if (fp == NULL)
return NULL;
while ((mnt = getmntent (fp)))
{
bool bind = hasmntopt (mnt, "bind");
me = xmalloc (sizeof *me);
me->me_devname = xstrdup (mnt->mnt_fsname);
me->me_mountdir = xstrdup (mnt->mnt_dir);
me->me_mntroot = NULL;
me->me_type = xstrdup (mnt->mnt_type);
me->me_type_malloced = 1;
me->me_dummy = ME_DUMMY (me->me_devname, me->me_type, bind);
me->me_remote = ME_REMOTE (me->me_devname, me->me_type);
me->me_dev = dev_from_mount_options (mnt->mnt_opts);
*mtail = me;
mtail = &me->me_next;
}
if (endmntent (fp) == 0)
goto free_then_fail;
}
}
/proc/mounts 读取出来后,会刷新到内存 struct mount_entry * 里。
df.c相关部分实现
df 查找文件系统相关的核心代码
if (best_match
&& (stat (best_match->me_mountdir, &disk_stats) != 0
|| disk_stats.st_dev != statp->st_dev))
best_match = NULL;
核心思路是 读取到/proc/mounts 后找到挂载的目录
sysfs /sys sysfs rw,nosuid,nodev,noexec,relatime 0 0 proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0 udev /dev devtmpfs rw,nosuid,noexec,relatime,size=1957852k,nr_inodes=489463,mode=755,inode64 0 0 devpts /dev/pts devpts rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0 tmpfs /run tmpfs rw,nosuid,nodev,noexec,relatime,size=398268k,mode=755,inode64 0 0
然后调用stat 函数,stat函数核心数据结构
struct stat
{
__dev_t st_dev;
#ifndef __x86_64__
unsigned short int __pad1;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
__ino_t st_ino;
#else
__ino_t __st_ino;
#endif
#ifndef __x86_64__
__mode_t st_mode;
__nlink_t st_nlink;
#else
__nlink_t st_nlink;
__mode_t st_mode;
#endif
__uid_t st_uid;
__gid_t st_gid;
#ifdef __x86_64__
int __pad0;
#endif
__dev_t st_rdev;
#ifndef __x86_64__
unsigned short int __pad2;
#endif
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
__off_t st_size;
#else
__off64_t st_size;
#endif
__blksize_t st_blksize;
#if defined __x86_64__ || !defined __USE_FILE_OFFSET64
__blkcnt_t st_blocks;
#else
__blkcnt64_t st_blocks;
#endif
#ifdef __USE_XOPEN2K8
struct timespec st_atim;
struct timespec st_mtim;
struct timespec st_ctim;
# define st_atime st_atim.tv_sec
# define st_mtime st_mtim.tv_sec
# define st_ctime st_ctim.tv_sec
#else
__time_t st_atime;
__syscall_ulong_t st_atimensec;
__time_t st_mtime;
__syscall_ulong_t st_mtimensec;
__time_t st_ctime;
__syscall_ulong_t st_ctimensec;
#endif
#ifdef __x86_64__
__syscall_slong_t __glibc_reserved[3];
#else
# ifndef __USE_FILE_OFFSET64
unsigned long int __glibc_reserved4;
unsigned long int __glibc_reserved5;
# else
__ino64_t st_ino;
# endif
#endif
};
df 核心逻辑是通过/proc/mounts 读到所有磁盘挂载的目录,然后调用stat 去拿到目录的st_dev,然后去把硬链接的src 的st_dev 和 mount 目录的st_dev 做比较,如果相同的话就代表挂载到相应的filesystem
再次简单回顾下设备id,取自<
二、主设备号和次设备号 ls -l /dev 我们看到主设备号是 用户之后的第一列 次设备 是第二列 主设备号由用户使用 次设备号由内核使用 linux内核中有宏定义中 主设备号是MAJOR宏 次设备是MINOR宏
c 语言 简单实现
#include#include #include int main() { struct stat tmp; struct stat sda5; stat("/", &sda5); stat("/home/zhanglei", &tmp); printf("sda5:%ld;tmp:%ldn", sda5.st_dev, tmp.st_dev); return 0; }
我们发现设备id是相同的
sda5:2053;tmp:2053
golang 核心查找逻辑实现
package source
import (
"fmt"
"os"
"path/filepath"
"github.com/pkg/errors"
"math/rand"
"syscall"
)
type HardLinkStat struct {
hardlinkDir string
info *FileInfo
}
func (handle *HardLinkStat) Init(hardlinkDir string) error {
file, err := GetFileInfo(hardlinkDir)
if err != nil {
return errors.New(fmt.Sprintf("(%s) GetFileInfo error!", hardlinkDir))
}
handle.hardlinkDir = hardlinkDir
handle.info = file
return nil
}
func (handle *HardLinkStat) CheckAndHardLinkByFileInfo(pth string, fileInfo os.FileInfo) (pthLink string, err error) {
stat, ok := fileInfo.Sys().(*syscall.Stat_t)
if !ok {
err = errors.Wrapf(err, "os.Link failed")
return
}
if stat.Dev != handle.info.DevId {
err = errors.Wrapf(err, "os.Link failed,error:%s", "stat.Dev != handle.info.DevId")
return
}
return handle.HardLink(pth)
}
func (handle *HardLinkStat) HardLink(pth string) (pthLink string, err error) {
// create a temp file which hand-links to the stream file
ext := filepath.Ext(pth)
dir := filepath.Dir(pth)
for {
pthLink = filepath.Join(dir, fmt.Sprintf("%08x.hardlink%s", rand.Uint32(), ext))
if err = os.Link(pth, pthLink); err == nil {
return
} else if !errors.Is(err, os.ErrExist) {
err = errors.Wrapf(err, "os.Link failed")
return
}
}
}



