简体   繁体   English

我可以将/ dev / sda用作普通的顺序文件吗?

[英]Can I use /dev/sda just as an ordinary sequential file?

I need to get better bulk write performance that I am able to do by using the partitioned and formatted SSD device with ext4 filesystem. 我需要通过使用带有ext4文件系统的分区和格式化SSD设备来获得更好的批量写入性能。 When I benchmark with dd command, I get somewhat 20 % of improvement of 当我使用dd命令进行基准测试时,我得到了20%的改进

time dd if=/dev/zero of=/dev/sdb count=1024 bs=1048576

in comparison to just 与刚刚相比

time dd if=/dev/zero of=/mnt/test.img count=1024 bs=1048576 && sync

where /mnt is my mounted /dev/sda1. 其中/ mnt是我的挂载/ dev / sda1。

Assuming the hard drive is dedicated exclusively to my application and I can set permissions for it, can I simply open the /dev/sda from my C++ application and use it as an ordinary file? 假设硬盘专用于我的应用程序,我可以设置它的权限,我可以简单地从我的C ++应用程序打开/ dev / sda并将其用作普通文件吗? I mean, write data from beginning, then open again and read: 我的意思是,从开始写入数据,然后再次打开并阅读:

  ofstream myfile;
  myfile.open ("/dev/sda");
  myfile << "Writing this to a file.\n";
  myfile.close();

and then reopen and read in the same spirit. 然后以同样的精神重新开放和阅读。 If there is not clear where there is the end of my writing, I can write the end of data marker myself. 如果不清楚写作的结束,我可以自己编写数据结束标记。

I would assume yes because it is expected to behave like a file. 我会假设是的,因为它的行为应该像文件一样。 However I would like to check if there are no significant hidden problems with it. 但是我想检查它是否没有明显的隐藏问题。

/dev/sda typically represents a block device . /dev/sda通常表示块设备 Contrast that with, eg, /dev/tty (a character device ) or /dev/zero (another character device ), /proc/self/fd/0 (a pseudo-file ), or (for example) /home/inetknght/file , a regular file . 与例如/dev/tty字符设备 )或/dev/zero (另一个字符设备 ), /proc/self/fd/0伪文件 )或(例如) /home/inetknght/file ,一个常规文件

Different devices have different characteristics. 不同的设备有不同的特征。 Block devices read and write in blocks. 块设备以块的形式读写。 The size of the block is dependent on the device itself. 块的大小取决于设备本身。 That might be emulated though; 这可能会被模仿; eg, you might have a disk image file added via hypervisor, and hypervisor emulates the block accessibility of it. 例如,您可能通过管理程序添加了磁盘映像文件,并且管理程序模拟了它的块可访问性。 A lot of block devices expose block sizes of 512 bytes or 4K bytes. 许多块设备暴露512字节或4K字节的块大小。 Some block devices are wrappers; 一些块设备是包装器; like the hypervisor, or also like a RAID setup. 像管理程序,或者像RAID设置。 Both will often configure a separate block size better suited to the controller's performance. 两者通常都会配置一个更适合控制器性能的独立块大小。

Contrast that with normal files which are usually simple data streams with an associated size. 与普通文件形成对比,普通文件通常是具有相关大小的简单数据流。 A file stream written on a block device has a lot of behind-the-scenes activity to translate between the two: how many blocks b are needed for data of size n ? 写在块设备上的文件流有很多幕后活动要在两者之间进行转换:大小为n数据需要多少块b That's what a filesystem does : generally translate blocks of data by allocating however many blocks are necessary for the size of the file, possibly by over-allocating. 这就是文件系统所做的事情 :通常通过分配来转换数据块,但是文件大小需要很多块,可能是通过过度分配。 Additional metadata about that is stored in the filesystem data tree which populates separate blocks on the device. 关于它的附加元数据存储在文件系统数据树中,该数据树填充设备上的单独块。

The performance improvement you're seeing is likely to be the removal of the filesystem. 您所看到的性能提升可能是删除文件系统。 Filesystems often have some (sometimes significant) overhead of use, but they simplify the lower level stuff they're built upon, such as block devices . 文件系统通常具有一些(有时很大的)使用开销,但它们简化了它们构建的较低级别的内容,例如块设备 Simple code is far easier to maintain. 简单的代码更容易维护。 Using a different filesystem will give you different performance characteristics. 使用不同的文件系统将为您提供不同的性能特征。 So you might not need the added complexity from going to a lower level. 因此,您可能不需要从更低级别增加复杂性。

You might be able to write to a block device as if you were writing to streaming device. 可以 写入流设备一样写入设备。 If the underlying device is truly a block device though, then what will happen when you write a number of bytes which aren't divisible by the block size of the device? 如果底层设备确实是块设备,那么当你编写一些不能被设备的块大小整除的字节时会发生什么? Suppose the block size is 512 bytes (fairly typical, so is 4K) and you write 500 bytes. 假设块大小为512字节(相当典型,4K也是如此)并且您写入500字节。 What will the device do with the other 12 bytes? 该设备将对其他12个字节做什么? That depends on the device : it might overwrite with zeroes, it might leave alone, it might actually have written your data to a block-sized cache location and then those 12 bytes get whatever was in cache from a previous block in the same cache location. 这取决于设备 :它可能会用零覆盖,它可能会单独留下,它实际上可能已将数据写入块大小的缓存位置,然后这12个字节从同一缓存位置中的前一个块获取缓存中的任何内容。 This is just one example of simplifications that filesystems provide. 这只是文件系统提供的简化的一个例子

So: you've expressed a question about how raw device files work. 所以:您已经表达了有关原始设备文件如何工作的问题。 You've also said that you've got full access to the machine. 您还说过您可以完全访问该机器。 I think the best way for you to learn would be to just play with it and see what you discover. 我认为你学习的最好方法就是玩它并看看你发现了什么。

I happen to be in the middle of setting up a RAID in my spare time using some drives in USB enclosures. 我碰巧在闲暇时间使用USB机箱中的一些驱动器设置RAID。 Not exactly ideal, but I think it's fun. 不完全理想,但我认为这很有趣。 I'll demonstrate some basic functionality. 我将演示一些基本功能。 If I damage something, I'll just wipe it later. 如果我损坏某些东西,我会稍后擦拭它。 ;) ;)

firefly@firefly:~$ ls -lah /dev/sd*
brw-rw---- 1 root disk 8,  0 Apr 16 11:53 /dev/sda
brw-rw---- 1 root disk 8, 16 Apr 16 11:53 /dev/sdb
brw-rw---- 1 root disk 8, 32 Apr 16 11:54 /dev/sdc
brw-rw---- 1 root disk 8, 48 Apr 16 11:54 /dev/sdd

The four devices in my not-yet-setup-raid. 在我尚未设置的raid中的四个设备。 I'll pick on /dev/sda here. 我会在这里选择/dev/sda

file command is pretty handy for discovering general information about various files. file命令非常方便用于发现有关各种文件的一般信息。

firefly@firefly:~$ file /dev/sda
/dev/sda: block special (8/0)

...but it tells me nothing special about this file. ...但它告诉我这个文件没什么特别的。

Touch will tell me if I can write to the file. 触摸会告诉我是否可以写入文件。

firefly@firefly:~$ touch /dev/sda
touch: cannot touch '/dev/sda': Permission denied

You already knew you need special permissions to write to it. 您已经知道需要特殊权限才能写入它。 Glad I don't care about this machine, so I'll just drop right into root and try again. 很高兴我不关心这台机器,所以我会直接进入root并再试一次。 It's generally bad practice to run as root, but I'm on a system I literally don't care about and will wipe in my spare time anyway. 以root身份运行通常是不好的做法,但我在一个我根本不关心的系统上,无论如何都会在我的业余时间擦拭。

firefly@firefly:~$ sudo su -
root@firefly:~# touch /dev/sda
root@firefly:~# echo $?
0
root@firefly:~# ls -lah /dev/sd*
brw-rw---- 1 root disk 8,  0 Apr 18 04:45 /dev/sda
brw-rw---- 1 root disk 8, 16 Apr 16 11:53 /dev/sdb
brw-rw---- 1 root disk 8, 32 Apr 16 11:54 /dev/sdc
brw-rw---- 1 root disk 8, 48 Apr 16 11:54 /dev/sdd

Updated timestamp, and of course root is able to write to it. 更新的时间戳,当然root可以写入它。 A little bit of googleing and I discover there's a command /sbin/blockdev to let me read/write some block device ioctl s. 一点点googleing,我发现有一个命令/sbin/blockdev让我读/写一些块设备ioctl

That sounds cool. 听起来很酷。

root@firefly:~# blockdev --getiomin /dev/sda
4096
root@firefly:~# blockdev --getioopt /dev/sda
33553920
root@firefly:~# blockdev --getbsz /dev/sda
4096

Nice! 太好了! So I've discovered that my block device has a block size of 4K (indicated by blockdev --getbsz and supported by blockdev --getiomin ). 所以我发现我的块设备具有4K的块大小(以表示blockdev --getbsz和支持blockdev --getiomin )。 I'm not sure about that --getioopt reporting just under 32MiB to be the optimal IO size. 我不知道这一点--getioopt只是32MiB 规定的报告是最佳的IO大小。 That's kinda weird. 这有点奇怪。 I'm not going to worry about it. 我不会担心它。

Okay, let's step back for a moment. 好吧,让我们退一步吧。

dd on the other hand copies blocks of information . 另一方面, dd复制信息块 That's perfect for block devices! 这对于块设备来说非常完美! But your question about treating the block device as a file would be better suited by actually treating it as a file . 但是您将块设备视为文件的问题更适合将其视为文件 So stop using dd . 所以停止使用dd

What do I get if I read raw data from the device? 如果我从设备读取原始数据,我会得到什么? Remember raw data is garbled on a text console, so I'll pipe it through xxd to provide a hexdump. 请记住原始数据在文本控制台上是乱码,所以我将通过xxd将其传输以提供hexdump。

root@firefly:~# head -c 100 /dev/sda | xxd
00000000: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000010: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000020: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000030: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000060: 0000 0000                                ....

So here's some secret sauce: head normally reads the first 10 lines . 所以这里有一些秘密的酱: head通常读取前10 I changed it to read the first 100 bytes. 我改变它来读取前100个字节。 Since the drive was freshly formatted to zeroes, head by itself would have read the entire device because it does not contain a single newline. 自驾车是新格式化到零, head本身就具有读取整个设备,因为它不包含一个换行符。 That would have taken hours (it's an 8TB spinning disk). 这可能需要数小时(这是一个8TB的旋转磁盘)。

So let's have some fun with this super-large "file" : 让我们对这个超大的“文件”有一些乐趣:

root@firefly:~# echo "hello world" > /dev/sda && head -c 16 /dev/sda | xxd
00000000: 6865 6c6c 6f20 776f 726c 640a 0000 0000  hello world.....

Neat. 整齐。 Echoing to the device overwrote the first zeroes with hello world. 回应设备用hello world覆盖了第一个零。 Echo's not quite dd , so that sounds fun. Echo不是dd ,听起来很有趣。

root@firefly:~# echo "goodbye" > /dev/sda && head -c 16 /dev/sda | xxd
00000000: 676f 6f64 6279 650a 726c 640a 0000 0000  goodbye.rld.....

You can see writing "goodbye" overwrote only part of hello wo rld . 你可以看到写着“再见”改写仅你好WO RLD的一部分。 That's fine; 没关系; I expected it. 我期待它。 You should be aware of your block device's behavior: it might have overwritten everything else in the same block with zeroes. 您应该知道块设备的行为:它可能用零覆盖了同一块中的所有其他内容。

Clearly bash and echo seem to work just fine with the device file. 很明显,bash和echo似乎可以正常使用设备文件。 I wonder about other languages? 我想知道其他语言? Your question is tagged with [C++] so let's try that: 你的问题用[C ++]标记,所以让我们试试:

root@firefly:~# g++ -x c++ -std=c++17 - <<EOF
> #include <cerrno>
> #include <cstdlib>
> #include <cstring>
> #include <fstream>
> #include <iostream>
> 
> int main(){
>     std::fstream f{"/dev/sda", std::ios_base::binary};
>     if ( false == f.good() ){
>         // C++ standard library does not let you inspect _why_ a failure occurred
>         // to get that we would have to use ::open() and check errno.
>         auto err = errno;
>         std::cerr << "unable to open /dev/sda: " << err << ": " << strerror(err) << std::endl;
>         std::cerr << f.good() << f.bad() << f.eof() << f.fail() << std::endl;
>         return EXIT_FAILURE;
>     }
>     std::cout << "opened!" << std::endl;
>     return EXIT_SUCCESS;
> }
> EOF
root@firefly:~# ./a.out 
unable to open /dev/sda: 0: Success
0001

There's a bit of information here. 这里有一些信息。 First: compiling an application, providing the source code using a bash heredoc . 第一:编译应用程序,使用bash heredoc提供源代码。 That's good to know for Linux users and developers. 对Linux用户和开发人员来说,这是件好事。 If you're unfamiliar with it, then you can un-quote everything between EOF, save to a file, and compile that. 如果你不熟悉它,那么你可以取消引用EOF之间的所有内容,保存到文件中,然后编译它。

However, the important bit is that opening the file using std::fstream failed . 但是,重要的是使用std::fstream打开文件失败 Whoa now! 哇现在! We saw echo worked just fine! 我们看到echo工作得很好! Why the difference?! 为什么不同?! I suspect it goes back to what I said about block devices being different. 我怀疑它可以追溯到我所说的块设备不同。 But I don't know the answer to that . 我不知道答案 I suspect that getting errno will tell me more information. 我怀疑获得errno会告诉我更多信息。 Let's try that: 我们试试看:

root@firefly:~# g++ -x c++ -std=c++17 - <<EOF
> #include <cerrno>
> #include <cstdio>
> #include <cstdlib>
> #include <cstring>
> #include <fstream>
> #include <functional>
> #include <iostream>
> #include <memory>
> 
> using FILEPTR = std::unique_ptr<std::FILE, decltype(&::std::fclose)>;
> 
> int main(){
>     FILEPTR f{nullptr, &::std::fclose};
>     // Remember, C-style has no concept of text mode vs binary mode.
>     f.reset(std::fopen("/dev/sda", "w+"));
>     if ( nullptr == f ){
>         auto err = errno;
>         std::cerr << "unable to open /dev/sda: " << err << ": " << strerror(err) << std::endl;
>         return EXIT_FAILURE;
>     }
>     std::cout << "opened!" << std::endl;
>     return EXIT_SUCCESS;
> }
> EOF
root@firefly:~# ./a.out 
opened!

Whoa, wait a minute: that worked . 哇,等一下:那很有效 So std::fstream could not open the block device but std::fopen() could ?! 所以std::fstream无法打开块设备但是std::fopen()可以吗? That honestly doesn't make a lot of sense to me. 老实说,这对我来说并没有多大意义。 Hopefully someone else can help out here. 希望其他人可以在这里帮忙。 But I imagine this should point you in the right direction. 但我想这应该指向正确的方向。 I'll leave you with a quick read/write example: 我会给你一个快速的读/写示例:

root@firefly:~# g++ -x c++ -std=c++17 - <<EOF
> extern "C" {
> #include <unistd.h>
> } // extern "C"
> 
> #include <algorithm>
> #include <array>
> #include <cerrno>
> #include <cstdio>
> #include <cstdlib>
> #include <cstring>
> #include <fstream>
> #include <functional>
> #include <iostream>
> #include <memory>
> #include <string_view>
> 
> using FILEPTR = std::unique_ptr<std::FILE, decltype(&::std::fclose)>;
> 
> int main(){
>     FILEPTR f{nullptr, &::std::fclose};
>     // Remember, C-style has no concept of text mode vs binary mode.
>     f.reset(std::fopen("/dev/sda", "w+"));
>     if ( nullptr == f ){
>         auto err = errno;
>         std::cerr << "unable to open /dev/sda: " << err << ": " << strerror(err) << std::endl;
>         return EXIT_FAILURE;
>     }
>     std::cout << "opened!" << std::endl;
> 
>     std::cout << "ftell(): " << std::ftell(f.get()) << '\n';
>     if ( 0 != std::fseek(f.get(), 0, SEEK_END) ) {
>         auto err = errno;
>         std::cerr << "unable to fseek(): " << err << ": " << std::strerror(err) << std::endl;
>         return EXIT_FAILURE;
>     }
>     std::cout << "ftell(SEEK_END): " << std::ftell(f.get()) << '\n';
>     std::rewind(f.get());
> 
>     // I thought about putting it on the stack, but it might exceed stack
>     // size on some platforms.
>     using buffer_type = std::array<char, 4096>;
>     using bufferptr = std::unique_ptr<buffer_type>;
>     bufferptr buffer = std::make_unique<buffer_type>();
>     if (gethostname(buffer->data(), buffer->size()) < 0) {
>         // using string_view to ensure the null byte gets written
>         auto s = std::string_view{"unable to get hostname\0"};
>         std::fwrite(s.data(), 1u, s.size(), f.get());
>     } else {
>         // ugh. boost::asio makes this simpler but I'll leave it to you to figure out.
>         if ( buffer->end() == std::find(buffer->begin(), buffer->end(), '\0') ){
>             std::cout << "buffer truncated" << std::endl;
>             buffer->back() = '\0';
>         }
>         std::fwrite(buffer->data(), 1u, buffer->size(), f.get());
>     }
>     if ( 0 != std::fflush(f.get()) ) {
>         int err = errno;
>         std::cerr << "fflush() failed: " << err << ": " << std::strerror(err) << std::endl;
>         return EXIT_FAILURE;
>     }
>     std::rewind(f.get());
> 
>     // reset our local internal buffer
>     std::fill(buffer->begin(), buffer->end(), '\0');
> 
>     // read into it
>     std::fread(buffer->data(), 1u, buffer->size(), f.get());
> 
>     // find where the disk's zeroes start. if we truncated, then it should start
>     // literally on the last byte in teh buffer, since we set that manually.
>     std::string_view read_message{buffer->data(), (std::size_t)std::distance(buffer->begin(), std::find(buffer->begin(), buffer->end(), '\0'))};
>     std::cout << read_message << std::endl;
> 
>     return EXIT_SUCCESS;
> }
> EOF
root@firefly:~# ./a.out 
opened!
ftell(): 0
ftell(SEEK_END): 8001563222016
firefly

Perfect. 完善。 So it was able to discover the drive advertises 8TB but is closer to 7.2TiB (that's marketing departments love the difference between Terabyte and Tebibyte ) . 所以它能够发现8TB的广告宣传但更接近7.2TiB(营销部门喜欢Terabyte和Tebibyte之间的差异)。 I was able to successfully write and read-back the system hostname using C++. 我能够使用C ++成功编写和回读系统主机名。 And I've (briefly) touched on some information for you to learn about performance tuning block devices. 我(简要地)提到了一些信息,供您了解性能调整块设备。 I am curious about what kind of performance you get out of std::FILE* , or if you discover something different. 我很好奇你从std::FILE*获得了什么样的表现,或者你发现了不同的东西。

You're going into a level low enough where it will likely get harder to find simple answers to questions. 你将进入一个足够低的水平,在那里找到简单的问题答案可能会变得更加困难。 What other limitations are there when directly using a block device? 直接使用块设备有什么其他限制? I'm pretty sure (though not 100%) that the C++ standard library is dealing with my read/write not aligning to the disk's block size (via std::FILE* ). 很确定 (尽管不是100%)C ++标准库正在处理我的读/写不对齐磁盘的块大小(通过std::FILE* )。 That's cool. 这很酷。 But it leaves me wondering: how can I turn that off to try to get even more performance? 但它让我感到疑惑:如何才能将其关闭以试图获得更高的性能? My first guess would be to use ::open() , ::read() , ::write() , etc with native file descriptors. 我的第一个猜测是使用::open()::read()::write()等与本机文件描述符。 That would throw away a lot of syntactic sugar that's already been well-tested; 这会丢掉很多已经经过充分测试的语法糖; I'm not sure I'd want to reinvent the wheel here. 我不确定我是否想在这里重新发明轮子。 Indeed, the manual page for ::open() specifically calls out some information related to dealing with block devices , such as buffering (which could also be what's handling the block alignment issues, but I am not sure). 实际上, ::open()的手册页专门调出了一些与处理块设备有关的信息 ,比如缓冲(这也可能是处理块对齐问题的原因,但我不确定)。

So the tl;dr is that it's complicated . 所以tl; dr就是它很复杂 Yes, you can read/write to it (given sufficient permissions). 是的,你可以读/写它(给予足够的权限)。 No, not everything works "right" if you're expecting it to work like a regular file. 不,如果您期望它像常规文件一样工作,那么一切都不对“正确”。 Specifically, it seems std::fstream might not work with block devices, but std::FILE* does. 具体来说,似乎std::fstream可能不适用于块设备,但std::FILE*可以。 And specifically, you will need to manually deal with framing your data . 具体而言, 您需要手动处理数据框架 And if you use C-level IO functions, it will no doubt work but will have even more limitations or performance complications. 如果你使用C级IO功能,它无疑会起作用,但会有更多限制或性能并发症。 This whole reply assumes you're using Linux; 整个回复假定您使用的是Linux; a different OS could have different behavior. 不同的操作系统可能有不同的行为。 And of course different block devices may also have different behavior (I'm using spinning rust but you mentioned using an SSD). 当然, 不同的块设备也可能有不同的行为 (我使用旋转生锈,但你提到使用SSD)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM